Repository: gchq/CyberChef Branch: master Commit: 607acbd24e64 Files: 871 Total size: 5.9 MB Directory structure: gitextract_priqvjog/ ├── .cspell.json ├── .devcontainer/ │ └── devcontainer.json ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ ├── feature-request.md │ │ └── operation-request.md │ ├── ISSUE_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── master.yml │ ├── pull_requests.yml │ └── releases.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Gruntfile.js ├── LICENSE ├── README.md ├── SECURITY.md ├── babel.config.js ├── eslint.config.mjs ├── nightwatch.json ├── package.json ├── postcss.config.js ├── src/ │ ├── core/ │ │ ├── Chef.mjs │ │ ├── ChefWorker.js │ │ ├── Dish.mjs │ │ ├── Ingredient.mjs │ │ ├── Operation.mjs │ │ ├── Recipe.mjs │ │ ├── Utils.mjs │ │ ├── config/ │ │ │ ├── Categories.json │ │ │ └── scripts/ │ │ │ ├── generateConfig.mjs │ │ │ ├── generateOpsIndex.mjs │ │ │ ├── newMinorVersion.mjs │ │ │ └── newOperation.mjs │ │ ├── dishTypes/ │ │ │ ├── DishBigNumber.mjs │ │ │ ├── DishByteArray.mjs │ │ │ ├── DishFile.mjs │ │ │ ├── DishHTML.mjs │ │ │ ├── DishJSON.mjs │ │ │ ├── DishListFile.mjs │ │ │ ├── DishNumber.mjs │ │ │ ├── DishString.mjs │ │ │ ├── DishType.mjs │ │ │ └── index.mjs │ │ ├── errors/ │ │ │ ├── DishError.mjs │ │ │ ├── ExcludedOperationError.mjs │ │ │ ├── OperationError.mjs │ │ │ └── index.mjs │ │ ├── lib/ │ │ │ ├── Arithmetic.mjs │ │ │ ├── AudioBytes.mjs │ │ │ ├── AudioMetaSchema.mjs │ │ │ ├── AudioParsers.mjs │ │ │ ├── BCD.mjs │ │ │ ├── Bacon.mjs │ │ │ ├── Base32.mjs │ │ │ ├── Base45.mjs │ │ │ ├── Base58.mjs │ │ │ ├── Base64.mjs │ │ │ ├── Base85.mjs │ │ │ ├── Base92.mjs │ │ │ ├── Bech32.mjs │ │ │ ├── BigIntUtils.mjs │ │ │ ├── Binary.mjs │ │ │ ├── BitwiseOp.mjs │ │ │ ├── Blowfish.mjs │ │ │ ├── Bombe.mjs │ │ │ ├── Braille.mjs │ │ │ ├── CanvasComponents.mjs │ │ │ ├── Charts.mjs │ │ │ ├── ChrEnc.mjs │ │ │ ├── CipherSaber2.mjs │ │ │ ├── Ciphers.mjs │ │ │ ├── Code.mjs │ │ │ ├── Colossus.mjs │ │ │ ├── ConvertCoordinates.mjs │ │ │ ├── Crypt.mjs │ │ │ ├── DateTime.mjs │ │ │ ├── Decimal.mjs │ │ │ ├── Delim.mjs │ │ │ ├── Enigma.mjs │ │ │ ├── Extract.mjs │ │ │ ├── FileSignatures.mjs │ │ │ ├── FileType.mjs │ │ │ ├── FlowControl.mjs │ │ │ ├── FuzzyMatch.mjs │ │ │ ├── Hash.mjs │ │ │ ├── Hex.mjs │ │ │ ├── IP.mjs │ │ │ ├── JA4.mjs │ │ │ ├── JWT.mjs │ │ │ ├── LS47.mjs │ │ │ ├── LZNT1.mjs │ │ │ ├── LZString.mjs │ │ │ ├── LoremIpsum.mjs │ │ │ ├── Lorenz.mjs │ │ │ ├── Magic.mjs │ │ │ ├── Modhex.mjs │ │ │ ├── PGP.mjs │ │ │ ├── Protobuf.mjs │ │ │ ├── Protocol.mjs │ │ │ ├── PublicKey.mjs │ │ │ ├── QRCode.mjs │ │ │ ├── RC6.mjs │ │ │ ├── RSA.mjs │ │ │ ├── Rotate.mjs │ │ │ ├── SIGABA.mjs │ │ │ ├── SM2.mjs │ │ │ ├── SM4.mjs │ │ │ ├── Salsa20.mjs │ │ │ ├── Sort.mjs │ │ │ ├── Stream.mjs │ │ │ ├── TLS.mjs │ │ │ ├── TLVParser.mjs │ │ │ ├── Typex.mjs │ │ │ ├── XXTEA.mjs │ │ │ └── Zlib.mjs │ │ ├── operations/ │ │ │ ├── A1Z26CipherDecode.mjs │ │ │ ├── A1Z26CipherEncode.mjs │ │ │ ├── ADD.mjs │ │ │ ├── AESDecrypt.mjs │ │ │ ├── AESEncrypt.mjs │ │ │ ├── AESKeyUnwrap.mjs │ │ │ ├── AESKeyWrap.mjs │ │ │ ├── AMFDecode.mjs │ │ │ ├── AMFEncode.mjs │ │ │ ├── AND.mjs │ │ │ ├── AddLineNumbers.mjs │ │ │ ├── AddTextToImage.mjs │ │ │ ├── Adler32Checksum.mjs │ │ │ ├── AffineCipherDecode.mjs │ │ │ ├── AffineCipherEncode.mjs │ │ │ ├── AlternatingCaps.mjs │ │ │ ├── AnalyseHash.mjs │ │ │ ├── AnalyseUUID.mjs │ │ │ ├── Argon2.mjs │ │ │ ├── Argon2Compare.mjs │ │ │ ├── AtbashCipher.mjs │ │ │ ├── AvroToJSON.mjs │ │ │ ├── BLAKE2b.mjs │ │ │ ├── BLAKE2s.mjs │ │ │ ├── BLAKE3.mjs │ │ │ ├── BSONDeserialise.mjs │ │ │ ├── BSONSerialise.mjs │ │ │ ├── BaconCipherDecode.mjs │ │ │ ├── BaconCipherEncode.mjs │ │ │ ├── Bcrypt.mjs │ │ │ ├── BcryptCompare.mjs │ │ │ ├── BcryptParse.mjs │ │ │ ├── BifidCipherDecode.mjs │ │ │ ├── BifidCipherEncode.mjs │ │ │ ├── BitShiftLeft.mjs │ │ │ ├── BitShiftRight.mjs │ │ │ ├── BlowfishDecrypt.mjs │ │ │ ├── BlowfishEncrypt.mjs │ │ │ ├── BlurImage.mjs │ │ │ ├── Bombe.mjs │ │ │ ├── Bzip2Compress.mjs │ │ │ ├── Bzip2Decompress.mjs │ │ │ ├── CBORDecode.mjs │ │ │ ├── CBOREncode.mjs │ │ │ ├── CMAC.mjs │ │ │ ├── CRCChecksum.mjs │ │ │ ├── CSSBeautify.mjs │ │ │ ├── CSSMinify.mjs │ │ │ ├── CSSSelector.mjs │ │ │ ├── CSVToJSON.mjs │ │ │ ├── CTPH.mjs │ │ │ ├── CaesarBoxCipher.mjs │ │ │ ├── CaretMdecode.mjs │ │ │ ├── CartesianProduct.mjs │ │ │ ├── CetaceanCipherDecode.mjs │ │ │ ├── CetaceanCipherEncode.mjs │ │ │ ├── ChaCha.mjs │ │ │ ├── ChangeIPFormat.mjs │ │ │ ├── ChiSquare.mjs │ │ │ ├── CipherSaber2Decrypt.mjs │ │ │ ├── CipherSaber2Encrypt.mjs │ │ │ ├── CitrixCTX1Decode.mjs │ │ │ ├── CitrixCTX1Encode.mjs │ │ │ ├── Colossus.mjs │ │ │ ├── Comment.mjs │ │ │ ├── CompareCTPHHashes.mjs │ │ │ ├── CompareSSDEEPHashes.mjs │ │ │ ├── ConditionalJump.mjs │ │ │ ├── ContainImage.mjs │ │ │ ├── ConvertArea.mjs │ │ │ ├── ConvertCoordinateFormat.mjs │ │ │ ├── ConvertDataUnits.mjs │ │ │ ├── ConvertDistance.mjs │ │ │ ├── ConvertImageFormat.mjs │ │ │ ├── ConvertLeetSpeak.mjs │ │ │ ├── ConvertMass.mjs │ │ │ ├── ConvertSpeed.mjs │ │ │ ├── ConvertToNATOAlphabet.mjs │ │ │ ├── CountOccurrences.mjs │ │ │ ├── CoverImage.mjs │ │ │ ├── CropImage.mjs │ │ │ ├── DESDecrypt.mjs │ │ │ ├── DESEncrypt.mjs │ │ │ ├── DNSOverHTTPS.mjs │ │ │ ├── DateTimeDelta.mjs │ │ │ ├── DechunkHTTPResponse.mjs │ │ │ ├── DecodeNetBIOSName.mjs │ │ │ ├── DecodeText.mjs │ │ │ ├── DefangIPAddresses.mjs │ │ │ ├── DefangURL.mjs │ │ │ ├── DeriveEVPKey.mjs │ │ │ ├── DeriveHKDFKey.mjs │ │ │ ├── DerivePBKDF2Key.mjs │ │ │ ├── DetectFileType.mjs │ │ │ ├── Diff.mjs │ │ │ ├── DisassembleARM.mjs │ │ │ ├── DisassembleX86.mjs │ │ │ ├── DitherImage.mjs │ │ │ ├── Divide.mjs │ │ │ ├── DropBytes.mjs │ │ │ ├── DropNthBytes.mjs │ │ │ ├── ECDSASign.mjs │ │ │ ├── ECDSASignatureConversion.mjs │ │ │ ├── ECDSAVerify.mjs │ │ │ ├── ELFInfo.mjs │ │ │ ├── EncodeNetBIOSName.mjs │ │ │ ├── EncodeText.mjs │ │ │ ├── Enigma.mjs │ │ │ ├── Entropy.mjs │ │ │ ├── EscapeString.mjs │ │ │ ├── EscapeUnicodeCharacters.mjs │ │ │ ├── ExpandAlphabetRange.mjs │ │ │ ├── ExtractAudioMetadata.mjs │ │ │ ├── ExtractDates.mjs │ │ │ ├── ExtractDomains.mjs │ │ │ ├── ExtractEXIF.mjs │ │ │ ├── ExtractEmailAddresses.mjs │ │ │ ├── ExtractFilePaths.mjs │ │ │ ├── ExtractFiles.mjs │ │ │ ├── ExtractHashes.mjs │ │ │ ├── ExtractID3.mjs │ │ │ ├── ExtractIPAddresses.mjs │ │ │ ├── ExtractLSB.mjs │ │ │ ├── ExtractMACAddresses.mjs │ │ │ ├── ExtractRGBA.mjs │ │ │ ├── ExtractURLs.mjs │ │ │ ├── FangURL.mjs │ │ │ ├── FernetDecrypt.mjs │ │ │ ├── FernetEncrypt.mjs │ │ │ ├── FileTree.mjs │ │ │ ├── Filter.mjs │ │ │ ├── FindReplace.mjs │ │ │ ├── FlaskSessionDecode.mjs │ │ │ ├── FlaskSessionSign.mjs │ │ │ ├── FlaskSessionVerify.mjs │ │ │ ├── Fletcher16Checksum.mjs │ │ │ ├── Fletcher32Checksum.mjs │ │ │ ├── Fletcher64Checksum.mjs │ │ │ ├── Fletcher8Checksum.mjs │ │ │ ├── FlipImage.mjs │ │ │ ├── Fork.mjs │ │ │ ├── FormatMACAddresses.mjs │ │ │ ├── FrequencyDistribution.mjs │ │ │ ├── FromBCD.mjs │ │ │ ├── FromBase.mjs │ │ │ ├── FromBase32.mjs │ │ │ ├── FromBase45.mjs │ │ │ ├── FromBase58.mjs │ │ │ ├── FromBase62.mjs │ │ │ ├── FromBase64.mjs │ │ │ ├── FromBase85.mjs │ │ │ ├── FromBase92.mjs │ │ │ ├── FromBech32.mjs │ │ │ ├── FromBinary.mjs │ │ │ ├── FromBraille.mjs │ │ │ ├── FromCaseInsensitiveRegex.mjs │ │ │ ├── FromCharcode.mjs │ │ │ ├── FromDecimal.mjs │ │ │ ├── FromFloat.mjs │ │ │ ├── FromHTMLEntity.mjs │ │ │ ├── FromHex.mjs │ │ │ ├── FromHexContent.mjs │ │ │ ├── FromHexdump.mjs │ │ │ ├── FromMessagePack.mjs │ │ │ ├── FromModhex.mjs │ │ │ ├── FromMorseCode.mjs │ │ │ ├── FromOctal.mjs │ │ │ ├── FromPunycode.mjs │ │ │ ├── FromQuotedPrintable.mjs │ │ │ ├── FromUNIXTimestamp.mjs │ │ │ ├── FuzzyMatch.mjs │ │ │ ├── GOSTDecrypt.mjs │ │ │ ├── GOSTEncrypt.mjs │ │ │ ├── GOSTHash.mjs │ │ │ ├── GOSTKeyUnwrap.mjs │ │ │ ├── GOSTKeyWrap.mjs │ │ │ ├── GOSTSign.mjs │ │ │ ├── GOSTVerify.mjs │ │ │ ├── GenerateAllChecksums.mjs │ │ │ ├── GenerateAllHashes.mjs │ │ │ ├── GenerateDeBruijnSequence.mjs │ │ │ ├── GenerateECDSAKeyPair.mjs │ │ │ ├── GenerateHOTP.mjs │ │ │ ├── GenerateImage.mjs │ │ │ ├── GenerateLoremIpsum.mjs │ │ │ ├── GeneratePGPKeyPair.mjs │ │ │ ├── GenerateQRCode.mjs │ │ │ ├── GenerateRSAKeyPair.mjs │ │ │ ├── GenerateTOTP.mjs │ │ │ ├── GenerateUUID.mjs │ │ │ ├── GenericCodeBeautify.mjs │ │ │ ├── GetAllCasings.mjs │ │ │ ├── GetTime.mjs │ │ │ ├── GroupIPAddresses.mjs │ │ │ ├── Gunzip.mjs │ │ │ ├── Gzip.mjs │ │ │ ├── HAS160.mjs │ │ │ ├── HASSHClientFingerprint.mjs │ │ │ ├── HASSHServerFingerprint.mjs │ │ │ ├── HMAC.mjs │ │ │ ├── HTMLToText.mjs │ │ │ ├── HTTPRequest.mjs │ │ │ ├── HammingDistance.mjs │ │ │ ├── HaversineDistance.mjs │ │ │ ├── Head.mjs │ │ │ ├── HeatmapChart.mjs │ │ │ ├── HexDensityChart.mjs │ │ │ ├── HexToObjectIdentifier.mjs │ │ │ ├── HexToPEM.mjs │ │ │ ├── IPv6TransitionAddresses.mjs │ │ │ ├── ImageBrightnessContrast.mjs │ │ │ ├── ImageFilter.mjs │ │ │ ├── ImageHueSaturationLightness.mjs │ │ │ ├── ImageOpacity.mjs │ │ │ ├── IndexOfCoincidence.mjs │ │ │ ├── InvertImage.mjs │ │ │ ├── JA3Fingerprint.mjs │ │ │ ├── JA3SFingerprint.mjs │ │ │ ├── JA4Fingerprint.mjs │ │ │ ├── JA4ServerFingerprint.mjs │ │ │ ├── JPathExpression.mjs │ │ │ ├── JSONBeautify.mjs │ │ │ ├── JSONMinify.mjs │ │ │ ├── JSONToCSV.mjs │ │ │ ├── JSONtoYAML.mjs │ │ │ ├── JWKToPem.mjs │ │ │ ├── JWTDecode.mjs │ │ │ ├── JWTSign.mjs │ │ │ ├── JWTVerify.mjs │ │ │ ├── JavaScriptBeautify.mjs │ │ │ ├── JavaScriptMinify.mjs │ │ │ ├── JavaScriptParser.mjs │ │ │ ├── Jq.mjs │ │ │ ├── Jsonata.mjs │ │ │ ├── Jump.mjs │ │ │ ├── Keccak.mjs │ │ │ ├── LMHash.mjs │ │ │ ├── LS47Decrypt.mjs │ │ │ ├── LS47Encrypt.mjs │ │ │ ├── LZ4Compress.mjs │ │ │ ├── LZ4Decompress.mjs │ │ │ ├── LZMACompress.mjs │ │ │ ├── LZMADecompress.mjs │ │ │ ├── LZNT1Decompress.mjs │ │ │ ├── LZStringCompress.mjs │ │ │ ├── LZStringDecompress.mjs │ │ │ ├── Label.mjs │ │ │ ├── LevenshteinDistance.mjs │ │ │ ├── Lorenz.mjs │ │ │ ├── LuhnChecksum.mjs │ │ │ ├── MD2.mjs │ │ │ ├── MD4.mjs │ │ │ ├── MD5.mjs │ │ │ ├── MD6.mjs │ │ │ ├── MIMEDecoding.mjs │ │ │ ├── Magic.mjs │ │ │ ├── Mean.mjs │ │ │ ├── Median.mjs │ │ │ ├── Merge.mjs │ │ │ ├── MicrosoftScriptDecoder.mjs │ │ │ ├── MultipleBombe.mjs │ │ │ ├── Multiply.mjs │ │ │ ├── MurmurHash3.mjs │ │ │ ├── NOT.mjs │ │ │ ├── NTHash.mjs │ │ │ ├── NormaliseImage.mjs │ │ │ ├── NormaliseUnicode.mjs │ │ │ ├── Numberwang.mjs │ │ │ ├── OR.mjs │ │ │ ├── ObjectIdentifierToHex.mjs │ │ │ ├── OffsetChecker.mjs │ │ │ ├── OpticalCharacterRecognition.mjs │ │ │ ├── PEMToHex.mjs │ │ │ ├── PEMToJWK.mjs │ │ │ ├── PGPDecrypt.mjs │ │ │ ├── PGPDecryptAndVerify.mjs │ │ │ ├── PGPEncrypt.mjs │ │ │ ├── PGPEncryptAndSign.mjs │ │ │ ├── PGPVerify.mjs │ │ │ ├── PHPDeserialize.mjs │ │ │ ├── PHPSerialize.mjs │ │ │ ├── PLISTViewer.mjs │ │ │ ├── PadLines.mjs │ │ │ ├── ParseASN1HexString.mjs │ │ │ ├── ParseCSR.mjs │ │ │ ├── ParseColourCode.mjs │ │ │ ├── ParseDateTime.mjs │ │ │ ├── ParseIPRange.mjs │ │ │ ├── ParseIPv4Header.mjs │ │ │ ├── ParseIPv6Address.mjs │ │ │ ├── ParseObjectIDTimestamp.mjs │ │ │ ├── ParseQRCode.mjs │ │ │ ├── ParseSSHHostKey.mjs │ │ │ ├── ParseTCP.mjs │ │ │ ├── ParseTLSRecord.mjs │ │ │ ├── ParseTLV.mjs │ │ │ ├── ParseUDP.mjs │ │ │ ├── ParseUNIXFilePermissions.mjs │ │ │ ├── ParseURI.mjs │ │ │ ├── ParseUserAgent.mjs │ │ │ ├── ParseX509CRL.mjs │ │ │ ├── ParseX509Certificate.mjs │ │ │ ├── PlayMedia.mjs │ │ │ ├── PowerSet.mjs │ │ │ ├── ProtobufDecode.mjs │ │ │ ├── ProtobufEncode.mjs │ │ │ ├── PseudoRandomIntegerGenerator.mjs │ │ │ ├── PseudoRandomNumberGenerator.mjs │ │ │ ├── PubKeyFromCert.mjs │ │ │ ├── PubKeyFromPrivKey.mjs │ │ │ ├── RAKE.mjs │ │ │ ├── RC2Decrypt.mjs │ │ │ ├── RC2Encrypt.mjs │ │ │ ├── RC4.mjs │ │ │ ├── RC4Drop.mjs │ │ │ ├── RC6Decrypt.mjs │ │ │ ├── RC6Encrypt.mjs │ │ │ ├── RIPEMD.mjs │ │ │ ├── ROT13.mjs │ │ │ ├── ROT13BruteForce.mjs │ │ │ ├── ROT47.mjs │ │ │ ├── ROT47BruteForce.mjs │ │ │ ├── ROT8000.mjs │ │ │ ├── RSADecrypt.mjs │ │ │ ├── RSAEncrypt.mjs │ │ │ ├── RSASign.mjs │ │ │ ├── RSAVerify.mjs │ │ │ ├── Rabbit.mjs │ │ │ ├── RailFenceCipherDecode.mjs │ │ │ ├── RailFenceCipherEncode.mjs │ │ │ ├── RandomizeColourPalette.mjs │ │ │ ├── RawDeflate.mjs │ │ │ ├── RawInflate.mjs │ │ │ ├── Register.mjs │ │ │ ├── RegularExpression.mjs │ │ │ ├── RemoveDiacritics.mjs │ │ │ ├── RemoveEXIF.mjs │ │ │ ├── RemoveLineNumbers.mjs │ │ │ ├── RemoveNullBytes.mjs │ │ │ ├── RemoveWhitespace.mjs │ │ │ ├── RenderImage.mjs │ │ │ ├── RenderMarkdown.mjs │ │ │ ├── ResizeImage.mjs │ │ │ ├── Return.mjs │ │ │ ├── Reverse.mjs │ │ │ ├── RisonDecode.mjs │ │ │ ├── RisonEncode.mjs │ │ │ ├── RotateImage.mjs │ │ │ ├── RotateLeft.mjs │ │ │ ├── RotateRight.mjs │ │ │ ├── SHA0.mjs │ │ │ ├── SHA1.mjs │ │ │ ├── SHA2.mjs │ │ │ ├── SHA3.mjs │ │ │ ├── SIGABA.mjs │ │ │ ├── SM2Decrypt.mjs │ │ │ ├── SM2Encrypt.mjs │ │ │ ├── SM3.mjs │ │ │ ├── SM4Decrypt.mjs │ │ │ ├── SM4Encrypt.mjs │ │ │ ├── SQLBeautify.mjs │ │ │ ├── SQLMinify.mjs │ │ │ ├── SSDEEP.mjs │ │ │ ├── SUB.mjs │ │ │ ├── Salsa20.mjs │ │ │ ├── ScanForEmbeddedFiles.mjs │ │ │ ├── ScatterChart.mjs │ │ │ ├── Scrypt.mjs │ │ │ ├── SeriesChart.mjs │ │ │ ├── SetDifference.mjs │ │ │ ├── SetIntersection.mjs │ │ │ ├── SetUnion.mjs │ │ │ ├── Shake.mjs │ │ │ ├── SharpenImage.mjs │ │ │ ├── ShowBase64Offsets.mjs │ │ │ ├── ShowOnMap.mjs │ │ │ ├── Shuffle.mjs │ │ │ ├── Sleep.mjs │ │ │ ├── Snefru.mjs │ │ │ ├── Sort.mjs │ │ │ ├── Split.mjs │ │ │ ├── SplitColourChannels.mjs │ │ │ ├── StandardDeviation.mjs │ │ │ ├── Streebog.mjs │ │ │ ├── Strings.mjs │ │ │ ├── StripHTMLTags.mjs │ │ │ ├── StripHTTPHeaders.mjs │ │ │ ├── StripIPv4Header.mjs │ │ │ ├── StripTCPHeader.mjs │ │ │ ├── StripUDPHeader.mjs │ │ │ ├── Subsection.mjs │ │ │ ├── Substitute.mjs │ │ │ ├── Subtract.mjs │ │ │ ├── Sum.mjs │ │ │ ├── SwapCase.mjs │ │ │ ├── SwapEndianness.mjs │ │ │ ├── SymmetricDifference.mjs │ │ │ ├── SyntaxHighlighter.mjs │ │ │ ├── TCPIPChecksum.mjs │ │ │ ├── Tail.mjs │ │ │ ├── TakeBytes.mjs │ │ │ ├── TakeNthBytes.mjs │ │ │ ├── Tar.mjs │ │ │ ├── Template.mjs │ │ │ ├── TextEncodingBruteForce.mjs │ │ │ ├── TextIntegerConverter.mjs │ │ │ ├── ToBCD.mjs │ │ │ ├── ToBase.mjs │ │ │ ├── ToBase32.mjs │ │ │ ├── ToBase45.mjs │ │ │ ├── ToBase58.mjs │ │ │ ├── ToBase62.mjs │ │ │ ├── ToBase64.mjs │ │ │ ├── ToBase85.mjs │ │ │ ├── ToBase92.mjs │ │ │ ├── ToBech32.mjs │ │ │ ├── ToBinary.mjs │ │ │ ├── ToBraille.mjs │ │ │ ├── ToCamelCase.mjs │ │ │ ├── ToCaseInsensitiveRegex.mjs │ │ │ ├── ToCharcode.mjs │ │ │ ├── ToDecimal.mjs │ │ │ ├── ToFloat.mjs │ │ │ ├── ToHTMLEntity.mjs │ │ │ ├── ToHex.mjs │ │ │ ├── ToHexContent.mjs │ │ │ ├── ToHexdump.mjs │ │ │ ├── ToKebabCase.mjs │ │ │ ├── ToLowerCase.mjs │ │ │ ├── ToMessagePack.mjs │ │ │ ├── ToModhex.mjs │ │ │ ├── ToMorseCode.mjs │ │ │ ├── ToOctal.mjs │ │ │ ├── ToPunycode.mjs │ │ │ ├── ToQuotedPrintable.mjs │ │ │ ├── ToSnakeCase.mjs │ │ │ ├── ToTable.mjs │ │ │ ├── ToUNIXTimestamp.mjs │ │ │ ├── ToUpperCase.mjs │ │ │ ├── TranslateDateTimeFormat.mjs │ │ │ ├── TripleDESDecrypt.mjs │ │ │ ├── TripleDESEncrypt.mjs │ │ │ ├── Typex.mjs │ │ │ ├── UNIXTimestampToWindowsFiletime.mjs │ │ │ ├── URLDecode.mjs │ │ │ ├── URLEncode.mjs │ │ │ ├── UnescapeString.mjs │ │ │ ├── UnescapeUnicodeCharacters.mjs │ │ │ ├── UnicodeTextFormat.mjs │ │ │ ├── Unique.mjs │ │ │ ├── Untar.mjs │ │ │ ├── Unzip.mjs │ │ │ ├── VarIntDecode.mjs │ │ │ ├── VarIntEncode.mjs │ │ │ ├── ViewBitPlane.mjs │ │ │ ├── VigenèreDecode.mjs │ │ │ ├── VigenèreEncode.mjs │ │ │ ├── Whirlpool.mjs │ │ │ ├── WindowsFiletimeToUNIXTimestamp.mjs │ │ │ ├── XKCDRandomNumber.mjs │ │ │ ├── XMLBeautify.mjs │ │ │ ├── XMLMinify.mjs │ │ │ ├── XOR.mjs │ │ │ ├── XORBruteForce.mjs │ │ │ ├── XORChecksum.mjs │ │ │ ├── XPathExpression.mjs │ │ │ ├── XSalsa20.mjs │ │ │ ├── XXTEADecrypt.mjs │ │ │ ├── XXTEAEncrypt.mjs │ │ │ ├── YAMLToJSON.mjs │ │ │ ├── YARARules.mjs │ │ │ ├── Zip.mjs │ │ │ ├── ZlibDeflate.mjs │ │ │ └── ZlibInflate.mjs │ │ └── vendor/ │ │ ├── DisassembleX86-64.mjs │ │ ├── gost/ │ │ │ ├── gostCipher.mjs │ │ │ ├── gostCoding.mjs │ │ │ ├── gostCrypto.mjs │ │ │ ├── gostDigest.mjs │ │ │ ├── gostEngine.mjs │ │ │ ├── gostRandom.mjs │ │ │ └── gostSign.mjs │ │ └── remove-exif.mjs │ ├── node/ │ │ ├── File.mjs │ │ ├── NodeDish.mjs │ │ ├── NodeRecipe.mjs │ │ ├── api.mjs │ │ ├── apiUtils.mjs │ │ ├── config/ │ │ │ ├── excludedOperations.mjs │ │ │ └── scripts/ │ │ │ └── generateNodeIndex.mjs │ │ ├── repl.mjs │ │ └── wrapper.js │ └── web/ │ ├── App.mjs │ ├── HTMLCategory.mjs │ ├── HTMLIngredient.mjs │ ├── HTMLOperation.mjs │ ├── Manager.mjs │ ├── html/ │ │ └── index.html │ ├── index.js │ ├── static/ │ │ ├── fonts/ │ │ │ └── bmfonts/ │ │ │ ├── Roboto72White.fnt │ │ │ ├── RobotoBlack72White.fnt │ │ │ ├── RobotoMono72White.fnt │ │ │ └── RobotoSlab72White.fnt │ │ ├── ga.html │ │ ├── images/ │ │ │ └── IMAGE_LICENCES.md │ │ ├── sitemap.mjs │ │ └── structuredData.json │ ├── stylesheets/ │ │ ├── components/ │ │ │ ├── _button.css │ │ │ ├── _list.css │ │ │ ├── _operation.css │ │ │ └── _pane.css │ │ ├── index.css │ │ ├── index.js │ │ ├── layout/ │ │ │ ├── _banner.css │ │ │ ├── _controls.css │ │ │ ├── _io.css │ │ │ ├── _modals.css │ │ │ ├── _operations.css │ │ │ ├── _recipe.css │ │ │ └── _structure.css │ │ ├── operations/ │ │ │ ├── diff.css │ │ │ └── json.css │ │ ├── preloader.css │ │ ├── themes/ │ │ │ ├── _classic.css │ │ │ ├── _dark.css │ │ │ ├── _geocities.css │ │ │ ├── _solarizedDark.css │ │ │ └── _solarizedLight.css │ │ └── utils/ │ │ ├── _general.css │ │ └── _overrides.css │ ├── utils/ │ │ ├── copyOverride.mjs │ │ ├── editorUtils.mjs │ │ ├── fileDetails.mjs │ │ ├── htmlWidget.mjs │ │ ├── sidePanel.mjs │ │ └── statusBar.mjs │ ├── waiters/ │ │ ├── BackgroundWorkerWaiter.mjs │ │ ├── BindingsWaiter.mjs │ │ ├── ControlsWaiter.mjs │ │ ├── HighlighterWaiter.mjs │ │ ├── InputWaiter.mjs │ │ ├── OperationsWaiter.mjs │ │ ├── OptionsWaiter.mjs │ │ ├── OutputWaiter.mjs │ │ ├── RecipeWaiter.mjs │ │ ├── SeasonalWaiter.mjs │ │ ├── TabWaiter.mjs │ │ ├── TimingWaiter.mjs │ │ ├── WindowWaiter.mjs │ │ └── WorkerWaiter.mjs │ └── workers/ │ ├── DishWorker.mjs │ ├── InputWorker.mjs │ ├── LoaderWorker.js │ └── ZipWorker.mjs ├── tests/ │ ├── browser/ │ │ ├── 00_nightwatch.js │ │ ├── 01_io.js │ │ ├── 02_ops.js │ │ └── browserUtils.js │ ├── lib/ │ │ ├── TestRegister.mjs │ │ └── utils.mjs │ ├── node/ │ │ ├── assertionHandler.mjs │ │ ├── consumers/ │ │ │ ├── cjs-consumer.js │ │ │ └── esm-consumer.mjs │ │ ├── index.mjs │ │ └── tests/ │ │ ├── Categories.mjs │ │ ├── Dish.mjs │ │ ├── File.mjs │ │ ├── NodeDish.mjs │ │ ├── Utils.mjs │ │ ├── lib/ │ │ │ └── BigIntUtils.mjs │ │ ├── nodeApi.mjs │ │ └── operations.mjs │ ├── operations/ │ │ ├── index.mjs │ │ └── tests/ │ │ ├── A1Z26CipherDecode.mjs │ │ ├── AESKeyWrap.mjs │ │ ├── AlternatingCaps.mjs │ │ ├── AvroToJSON.mjs │ │ ├── BCD.mjs │ │ ├── BLAKE2b.mjs │ │ ├── BLAKE2s.mjs │ │ ├── BLAKE3.mjs │ │ ├── BSON.mjs │ │ ├── BaconCipher.mjs │ │ ├── Base32.mjs │ │ ├── Base45.mjs │ │ ├── Base58.mjs │ │ ├── Base62.mjs │ │ ├── Base64.mjs │ │ ├── Base85.mjs │ │ ├── Base92.mjs │ │ ├── Bech32.mjs │ │ ├── BitwiseOp.mjs │ │ ├── Bombe.mjs │ │ ├── ByteRepr.mjs │ │ ├── CBORDecode.mjs │ │ ├── CBOREncode.mjs │ │ ├── CMAC.mjs │ │ ├── CRCChecksum.mjs │ │ ├── CSV.mjs │ │ ├── CaesarBoxCipher.mjs │ │ ├── CaretMdecode.mjs │ │ ├── CartesianProduct.mjs │ │ ├── CetaceanCipherDecode.mjs │ │ ├── CetaceanCipherEncode.mjs │ │ ├── ChaCha.mjs │ │ ├── ChangeIPFormat.mjs │ │ ├── CharEnc.mjs │ │ ├── Charts.mjs │ │ ├── CipherSaber2.mjs │ │ ├── Ciphers.mjs │ │ ├── Code.mjs │ │ ├── Colossus.mjs │ │ ├── Comment.mjs │ │ ├── Compress.mjs │ │ ├── ConditionalJump.mjs │ │ ├── ConvertCoordinateFormat.mjs │ │ ├── ConvertLeetSpeak.mjs │ │ ├── ConvertToNATOAlphabet.mjs │ │ ├── Crypt.mjs │ │ ├── DateTime.mjs │ │ ├── DefangIP.mjs │ │ ├── DisassembleARM.mjs │ │ ├── DropNthBytes.mjs │ │ ├── ECDSA.mjs │ │ ├── ELFInfo.mjs │ │ ├── Enigma.mjs │ │ ├── ExtractAudioMetadata.mjs │ │ ├── ExtractEmailAddresses.mjs │ │ ├── ExtractHashes.mjs │ │ ├── ExtractIPAddresses.mjs │ │ ├── Fernet.mjs │ │ ├── FileTree.mjs │ │ ├── FlaskSession.mjs │ │ ├── FletcherChecksum.mjs │ │ ├── Float.mjs │ │ ├── Fork.mjs │ │ ├── FromDecimal.mjs │ │ ├── GOST.mjs │ │ ├── GenerateAllChecksums.mjs │ │ ├── GenerateAllHashes.mjs │ │ ├── GenerateDeBruijnSequence.mjs │ │ ├── GenerateQRCode.mjs │ │ ├── GetAllCasings.mjs │ │ ├── Gunzip.mjs │ │ ├── Gzip.mjs │ │ ├── HASSH.mjs │ │ ├── HKDF.mjs │ │ ├── Hash.mjs │ │ ├── HaversineDistance.mjs │ │ ├── Hex.mjs │ │ ├── Hexdump.mjs │ │ ├── IPv6Transition.mjs │ │ ├── Image.mjs │ │ ├── IndexOfCoincidence.mjs │ │ ├── JA3Fingerprint.mjs │ │ ├── JA3SFingerprint.mjs │ │ ├── JA4.mjs │ │ ├── JSONBeautify.mjs │ │ ├── JSONMinify.mjs │ │ ├── JSONtoCSV.mjs │ │ ├── JSONtoYAML.mjs │ │ ├── JWK.mjs │ │ ├── JWTDecode.mjs │ │ ├── JWTSign.mjs │ │ ├── JWTVerify.mjs │ │ ├── Jsonata.mjs │ │ ├── Jump.mjs │ │ ├── LS47.mjs │ │ ├── LZNT1Decompress.mjs │ │ ├── LZString.mjs │ │ ├── LevenshteinDistance.mjs │ │ ├── Lorenz.mjs │ │ ├── LuhnChecksum.mjs │ │ ├── MIMEDecoding.mjs │ │ ├── MS.mjs │ │ ├── Magic.mjs │ │ ├── Media.mjs │ │ ├── Modhex.mjs │ │ ├── MorseCode.mjs │ │ ├── MultipleBombe.mjs │ │ ├── MurmurHash3.mjs │ │ ├── NTLM.mjs │ │ ├── NetBIOS.mjs │ │ ├── NormaliseUnicode.mjs │ │ ├── OTP.mjs │ │ ├── PEMtoHex.mjs │ │ ├── PGP.mjs │ │ ├── PHP.mjs │ │ ├── PHPSerialize.mjs │ │ ├── ParseCSR.mjs │ │ ├── ParseIPRange.mjs │ │ ├── ParseObjectIDTimestamp.mjs │ │ ├── ParseQRCode.mjs │ │ ├── ParseSSHHostKey.mjs │ │ ├── ParseTCP.mjs │ │ ├── ParseTLSRecord.mjs │ │ ├── ParseTLV.mjs │ │ ├── ParseUDP.mjs │ │ ├── ParseX509CRL.mjs │ │ ├── PowerSet.mjs │ │ ├── Protobuf.mjs │ │ ├── PubKeyFromCert.mjs │ │ ├── PubKeyFromPrivKey.mjs │ │ ├── RAKE.mjs │ │ ├── RC6.mjs │ │ ├── RSA.mjs │ │ ├── Rabbit.mjs │ │ ├── Regex.mjs │ │ ├── Register.mjs │ │ ├── RisonEncodeDecode.mjs │ │ ├── Rotate.mjs │ │ ├── SIGABA.mjs │ │ ├── SM2.mjs │ │ ├── SM4.mjs │ │ ├── SQLBeautify.mjs │ │ ├── Salsa20.mjs │ │ ├── SeqUtils.mjs │ │ ├── SetDifference.mjs │ │ ├── SetIntersection.mjs │ │ ├── SetUnion.mjs │ │ ├── Shuffle.mjs │ │ ├── SplitColourChannels.mjs │ │ ├── StrUtils.mjs │ │ ├── StripIPv4Header.mjs │ │ ├── StripTCPHeader.mjs │ │ ├── StripUDPHeader.mjs │ │ ├── Subsection.mjs │ │ ├── SwapCase.mjs │ │ ├── SymmetricDifference.mjs │ │ ├── TakeNthBytes.mjs │ │ ├── Template.mjs │ │ ├── TextEncodingBruteForce.mjs │ │ ├── TextIntegerConverter.mjs │ │ ├── ToFromInsensitiveRegex.mjs │ │ ├── TranslateDateTimeFormat.mjs │ │ ├── Typex.mjs │ │ ├── URLEncodeDecode.mjs │ │ ├── UnescapeString.mjs │ │ ├── Unicode.mjs │ │ ├── XORChecksum.mjs │ │ ├── XSalsa20.mjs │ │ ├── XXTEA.mjs │ │ └── YARA.mjs │ └── samples/ │ ├── Audio.mjs │ ├── Ciphers.mjs │ ├── Executables.mjs │ └── Images.mjs └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cspell.json ================================================ { "version": "0.2", "language": "en,en-gb", "words": [], "dictionaries": [ "npm", "softwareTerms", "node", "html", "css", "bash", "en-gb", "misc" ], "ignorePaths": ["package.json", "package-lock.json", "node_modules"] } ================================================ FILE: .devcontainer/devcontainer.json ================================================ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node { "name": "CyberChef", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bookworm", // Features to add to the dev container. More info: https://containers.dev/features. "features": { "ghcr.io/devcontainers/features/github-cli": "latest" }, // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [8080], // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": { "npm": "bash -c \"sudo chown node node_modules && npm install\"" }, "containerEnv": { "DISPLAY": ":99" }, "mounts": [ "source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume" ], // Configure tool-specific properties. "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "GitHub.vscode-github-actions" ] } } // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" } ================================================ FILE: .dockerignore ================================================ node_modules build ================================================ FILE: .editorconfig ================================================ # top-most EditorConfig file root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 4 [{package.json,.travis.yml,nightwatch.json}] indent_style = space indent_size = 2 [.github/**.yml] indent_style = space indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing Take 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). There 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)'. Before your contributions can be accepted, you must: - Sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef) - Push your changes to your fork. - Submit a pull request. ## Coding conventions * Indentation: Each block should consist of 4 spaces * Object/namespace identifiers: CamelCase * Function/variable names: camelCase * Constants: UNDERSCORE_UPPER_CASE * Source code encoding: UTF-8 (without BOM) * All source files must end with a newline * Line endings: UNIX style (\n) ## Design Principles 1. 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. 2. 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. 3. 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. 4. 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. With these principles in mind, any changes or additions to CyberChef should keep it: - Standalone - Efficient - As small as possible ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug report about: Create a report to help us improve title: 'Bug report: ' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behaviour or a link to the recipe / input used to cause the bug: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behaviour** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (if relevant, please complete the following information):** - OS: [e.g. Windows] - Browser: [e.g. chrome 72, firefox 60] - CyberChef version: [e.g. 9.7.14] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.md ================================================ --- name: Feature request about: Suggest an idea for the project title: 'Feature request: ' labels: feature assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. E.g. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/operation-request.md ================================================ --- name: Operation request about: Suggest a new operation title: 'Operation request: ' labels: operation assignees: '' --- ## Summary ### Example Input ### Example Output ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ================================================ FILE: .github/dependabot.yml ================================================ # See the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: # # Check for minor/patch versions only on a weekly basis - we are likely to be able to # merge these routinely. Major versions we'll check for and update manually. # - package-ecosystem: 'npm' directory: '/' versioning-strategy: increase schedule: interval: 'weekly' day: 'friday' time: '03:00' timezone: Europe/London commit-message: prefix: 'chore (deps): ' ignore: # we'll do any major version updates manually - dependency-name: '*' update-types: ['version-update:semver-major'] # packages we can't currently update # see issue #2214 for rationale for each of these - dependency-name: '@xmldom/xmldom' versions: [ '>=0.9.0' ] - dependency-name: 'bcryptjs' versions: [ '>=3.0.0' ] - dependency-name: 'bootstrap' versions: [ '>=5.0.0' ] - dependency-name: 'bson' versions: [ '>=5.0.0' ] - dependency-name: 'cbor' versions: [ '>=10.0.0' ] - dependency-name: 'cspell' versions: [ '>=9.0.0' ] - dependency-name: 'eslint' versions: [ '>=10.0.0' ] - dependency-name: 'eslint-plugin-jsdoc' versions: [ '>=51.0.0' ] - dependency-name: 'fernet' versions: [ '>=0.4.0' ] - dependency-name: 'geodesy' versions: [ '>=2.0.0' ] - dependency-name: 'otpauth' versions: [ '>=9.4.0' ] - dependency-name: 'webpack-dev-server' versions: [ '>=5.1.0' ] groups: # # Grouping so we don't get a seperate PR for every patch version. # patch-updates: applies-to: version-updates patterns: - '*' update-types: - 'patch' # Can't enable this until we are using Node 24 as the latest actions all require this version # - package-ecosystem: "github-actions" # # Workflow files stored in the default location of `.github/workflows`; no need to # # specify `/.github/workflows` for `directory` # directory: '/' # schedule: # interval: 'weekly' # day: 'friday' # time: '03:00' # timezone: Europe/London # commit-message: # prefix: 'chore (deps): ' ================================================ FILE: .github/workflows/master.yml ================================================ name: "Master Build, Test & Deploy" on: workflow_dispatch: push: branches: - master permissions: contents: read jobs: main: permissions: contents: write pages: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set node version uses: actions/setup-node@v6 with: node-version: 18 registry-url: "https://registry.npmjs.org" - name: Install run: | export DETECT_CHROMEDRIVER_VERSION=true npm install npm run setheapsize - name: Lint run: npx grunt lint - name: Unit Tests run: | npm test npm run testnodeconsumer - name: Production Build if: success() run: npx grunt prod --msg="" - name: Generate sitemap run: npx grunt exec:sitemap - name: UI Tests if: success() run: | sudo apt-get install xvfb xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui - name: Prepare for GitHub Pages if: success() run: npx grunt copy:ghPages - name: Deploy to GitHub Pages if: success() && github.ref == 'refs/heads/master' uses: crazy-max/ghaction-github-pages@v3 with: target_branch: gh-pages build_dir: ./build/prod env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/pull_requests.yml ================================================ name: "Pull Requests" permissions: contents: read on: workflow_dispatch: pull_request: types: [synchronize, opened, reopened] jobs: main: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set node version uses: actions/setup-node@v6 with: node-version: 18 registry-url: "https://registry.npmjs.org" - name: Install run: | export DETECT_CHROMEDRIVER_VERSION=true npm install npm run setheapsize - name: Lint run: npx grunt lint - name: Unit Tests run: | npm test npm run testnodeconsumer - name: Production Build if: success() run: npx grunt prod - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Production Image Build if: success() id: build-image uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm64 - name: UI Tests if: success() run: | sudo apt-get install xvfb xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui ================================================ FILE: .github/workflows/releases.yml ================================================ name: "Releases" on: workflow_dispatch: push: tags: - "v*" permissions: contents: read env: REGISTRY: ghcr.io REGISTRY_USER: ${{ github.actor }} REGISTRY_PASSWORD: ${{ github.token }} IMAGE_NAME: ${{ github.repository }} jobs: main: permissions: packages: write contents: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set node version uses: actions/setup-node@v6 with: node-version: 18 registry-url: "https://registry.npmjs.org" - name: Install run: | export DETECT_CHROMEDRIVER_VERSION=true npm ci npm run setheapsize - name: Lint run: npx grunt lint - name: Unit Tests run: | npm test npm run testnodeconsumer - name: Production Build run: npx grunt prod - name: UI Tests run: | sudo apt-get install xvfb xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Image Metadata id: image-metadata uses: docker/metadata-action@v4 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{version}} - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ env.REGISTRY_USER }} password: ${{ env.REGISTRY_PASSWORD }} - name: Publish to GHCR uses: docker/build-push-action@v6 with: context: . push: true tags: ${{ steps.image-metadata.outputs.tags }} labels: ${{ steps.image-metadata.outputs.labels }} platforms: linux/amd64,linux/arm64 - name: Upload Release Assets id: upload-release-assets uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: build/prod/*.zip tag: ${{ github.ref }} overwrite: true file_glob: true 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." npm-publish: permissions: id-token: write contents: read needs: main runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set node version uses: actions/setup-node@v6 with: node-version: 18 registry-url: "https://registry.npmjs.org" - name: Install run: npm ci - name: Create machine generated files run: npm run node - name: Reset node version ready for publish uses: actions/setup-node@v6 with: node-version: ^24.5 registry-url: "https://registry.npmjs.org" - name: Publish to NPM run: npm publish ================================================ FILE: .gitignore ================================================ node_modules npm-debug.log travis.log build .vscode .idea .*.swp src/core/config/modules/* src/core/config/OperationConfig.json src/core/operations/index.mjs src/node/config/OperationConfig.json src/node/index.mjs **/*.DS_Store tests/browser/output/* .node-version ================================================ FILE: .npmignore ================================================ node_modules npm-debug.log travis.log build/* !build/node .vscode .github ================================================ FILE: .nvmrc ================================================ 18 ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## Versioning CyberChef uses the [semver](https://semver.org/) system to manage versioning: `..`. - 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. - MINOR version changes usually mean the addition of new operations or reasonably significant new features. - PATCH versions are used for bug fixes and any other small tweaks that modify or improve existing capabilities. All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master). ## Details ### [10.22.0] - 2026-02-11 - Separate npm publish out into separate job and run with Node 24.5 [@GCHQDeveloper581] | [#2188] - Fixed Percent delimiter for hex encoding [@beneri] [@C85297] | [#2137] - Added the ability to paste one or more Images from the Clipboard [@t-martine] [@a3957273] [@C85297] | [#1876] - Quoted Printable - consistent reference to 'email' [@wesinator] | [#2186] - Fix freeze when output text decoding fails [@Raka-loah] | [#1573] - Update Browserslist DB [@C85297] | [#2183] - Add contents write permission to releases workflow [@C85297] | [#2182] - Fix release workflow permissions [@C85297] | [#2181] ### [10.21.0] - 2026-02-05 - Fix import operations with special chars in them [@d98762625] [@jg42526] | [#1040] - Remove custom CodeQL workflow [@C85297] | [#2176] - Fix code scanning warnings in workflows [@GCHQDeveloper581] | [#2177] - Use NPM trusted publishing [@C85297] [@GCHQDeveloper581] | [#2174] - Fix: Correctly parse xxd odd byte hexdumps [@ThomasNotTom] [@GCHQDeveloper581] | [#2058] - Update Sitemap URLs to Use Valid Paths in sitemap.mjs [@rbpi] [@C85297] | [#1861] - Use recommended GitHub Actions to build image [@AlexGustafsson] [@C85297] | [#2055] - Remove version 10 message from banner [@C85297] | [#2169] - Bump form-data from 4.0.1 to 4.0.5 | [#2175] - Bump node-forge from 1.3.1 to 1.3.3 | [#2173] - Update crypto browserify [@C85297] | [#2172] - Update kbpgp package (resolves #2135) [@GCHQDeveloper581] | [#2136] - Fix the processing of ALPNs for JA4 to align with new specification update [@tuliperis] | [#2165] - Add Bech32 and Bech32m encoding/decoding operations [@thomasxm] | [#2159] - Exclude Delete character from hex dump output [@mikecat] [@C85297] | [#2086] - Tiny typo fix in "To Base85" operation [@twostraws] | [#2118] - Bump jsonpath-plus [@C85297] | [#2166] ### [10.20.0] - 2026-01-28 - Fixed Optical Character Recognition and added tests [@n1474335] | [ab37c1e] - Fixed JA4 version fallback value [@n1474335] | [7a5225c] - Updated chromedriver [@n1474335] | [0e82e4b] - Fixed RSA Sign and Verify character encodings [@n1474335] | [895a929] - Updated chromedriver [@n1474335] | [d3adfc7] - Added message format arg to RSA Verify operation [@n1474335] | [47c85a1] - Add operation for parsing X.509 CRLs [@robinsandhu] | [#1887] - Fix typo in description of JWT Sign recipe [@GuilhermoReadonly] | [#1961] - Corrected path to generateNodeIndex.mjs [@simonarnell] | [#1959] - Add 'header' ingredient to JWT Sign operation [@RandomByte] | [#1957] - Add Parse TLS record operation [@c65722] | [#1936] - Automatically detect chrome driver version [@gchq] | [#1972] - Add Strip UDP header operation [@c65722] | [#1900] - Add Strip TCP header operation [@c65722] | [#1898] - Webpack compress with gzip and brotli [@max0x53] | [#1955] - add offset field to 'Add Line Numbers' operation [@Adamkadaban] | [#1866] - Disable flakey URL test [@a3957273] | [#1973] - Add Strip IPv4 header operation [@c65722] | [#1899] - IPv6 Transition Operation [@jb30795] | [#1780] - fix: Blowfish - ignore IV length in ECB mode [@FranciscoPombal] | [#1902] - Add 'Drop nth bytes' operation [@Oshawk] | [#1914] - Add 'Take nth bytes' operation [@Oshawk] | [#1915] - Add Leet Speak [@bartblaze] | [#1971] - Fix Generate TOTP & HOPT [@exactlyaron] | [#1966] - Updated luhn checksum operation to work with different bases [@k3ach] | [#1933] - automatically theme mode based on user preference [@vs4vijay] | [#1921] - fix: DES/Triple DES - misleading error messages [@FranciscoPombal] | [#1904] - fix: ROT13 - shifting numbers by negative amounts [@FranciscoPombal] | [#1903] - Introduce Yubico's Modhex for Conversion [@linuxgemini] | [#1105] - Feature: MIME RFC2047 Decoding [@MShwed] | [#630] - CC-1889 add _ option [@depperm] | [#1977] - chore(root): add cspell [@evenstensberg] | [#1976] - Preserve uppercase for Leet Speak [@bartblaze] | [#1981] - Load the user's preferred color scheme if the URL contains an invalid theme [@0xh3xa] | [#2007] - Add SM2 Encrypt and Decrypt Operations [@flakjacket95] | [#1909] - Support jq as an operation. [@zhzy0077] | [#1604] - Add fingerprints to the 'Parse X.509 certificate' operation [@JSCU-CNI] | [#1863] - Added a JSON to YAML and a YAML to JSON operation [@ccarpo] | [#1286] - Add CRC Operation [@r4mos] | [#1993] - Bug Fix: selected theme not loading when refreshing [@0xh3xa] | [#2006] - Fix(RecipeWaiter): sanitize user input in addOperation to prevent XSS [@0xh3xa] | [#2014] - Docker multiplatform build support [@PathToLife] | [#1974] - Add Base32 Hex Extended Alphabet and Base32 Tests. [@peterc-s] | [#1991] - Add ECB/NoPadding and CBC/NoPadding support to AES encryption [@plvie] | [#2013] - Add new operation: PHP Serialize [@brun0ne] | [#1548] - Push input through postmessage [@kenduguay1] | [#1992] - Add jsonata query operation [@jonking-ajar] | [#1587] - Re-enable Npm Release in github workflows [@PathToLife] | [#2031] - Add to ECDSA Verify the message format [@r4mos] | [#2027] - Added alternating caps functionality [@sw5678] | [#1897] - XOR Checksum operation added [@jg42526] | [#2035] - Add GenerateAllChecksums operation * Remove checksums from GenerateAllHashes operation [@es45411] | [66d445c] - Update GenerateAllChecksums infoURL [@es45411] | [#2037] - Add toggle "+" character to URLDecode operation [@es45411] | [#2040] - Workaround for Safari load bug [@GCHQDeveloper94872] | [#2038] - Updated Dockerfile to correctly build on ARM64 platforms [@Sma-Das] | [#2042] - 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] - Remove trim from rail fence [@Odyhibit] | [#1986] - Fix email regex [@ericli-splunk] | [#2025] - Add Blake3 hashing [@xumptex] | [#2023] - Use defaultIndex instead of 0 in transformArgs [@bartvanandel] | [#2015] - Add "Generate UUID" and "Analyse UUID" operations [@bartvanandel] | [#2011] - Add new operation: Template [@kendallgoto] | [#2021] - Add more clear build instructions [@remingtr] | [#1873] - Show On Map updated to use leaflet over WikiMedia [@0xff1ce] | [#1884] - Fixed ToDecimal signed logic [@starplanet] | [#1545] - Use BigInt for encoding/decoding VarInt [@mikecat] | [#1978] ### [10.19.0] - 2024-06-21 - Add support for ECDSA and DSA in 'Parse CSR' [@robinsandhu] | [#1828] - Fix typos in SIGABA.mjs [@eltociear] | [#1834] ### [10.18.0] - 2024-04-24 - Added 'XXTEA Encrypt' and 'XXTEA Decrypt' operations [@n1474335] | [0a353ee] ### [10.17.0] - 2024-04-13 - Fix unit test 'expectOutput' implementation [@zb3] | [#1783] - Add accessibility labels for icons [@e218736] | [#1743] - Add focus styling for keyboard navigation [@e218736] | [#1739] - Add support for operation option hiding [@TheZ3ro] | [#541] - Improve efficiency of RAKE implementation [@sw5678] | [#1751] - Require (a, 26) to be coprime in 'Affine Encode' [@EvieHarv] | [#1788] - Added 'JWK to PEM' operation [@cplussharp] | [#1277] - Added 'PEM to JWK' operation [@cplussharp] | [#1277] - Added 'Public Key from Certificate' operation [@cplussharp] | [#1642] - Added 'Public Key from Private Key' operation [@cplussharp] | [#1642] ### [10.16.0] - 2024-04-12 - Added 'JA4Server Fingerprint' operation [@n1474335] | [#1789] ### [10.15.0] - 2024-04-02 - Fix Ciphersaber2 key concatenation [@zb3] | [#1765] - Fix DeriveEVPKey's array parsing [@zb3] | [#1767] - Fix JWT operations [@a3957273] | [#1769] - Added 'Parse Certificate Signing Request' operation [@jkataja] | [#1504] - Added 'Extract Hash Values' operation [@MShwed] | [#512] - Added 'DateTime Delta' operation [@tomgond] | [#1732] ### [10.14.0] - 2024-03-31 - Added 'To Float' and 'From Float' operations [@tcode2k16] | [#1762] - Fix ChaCha raw export option [@joostrijneveld] | [#1606] - Update x86 disassembler vendor library [@evanreichard] | [#1197] - Allow variable Blowfish key sizes [@cbeuw] | [#933] - Added 'XXTEA' operation [@devcydo] | [#1361] ### [10.13.0] - 2024-03-30 - Added 'FangURL' operation [@breakersall] [@arnydo] | [#1591] [#654] ### [10.12.0] - 2024-03-29 - Added 'Salsa20' and 'XSalsa20' operation [@joostrijneveld] | [#1750] ### [10.11.0] - 2024-03-29 - Add HEIC/HEIF file signatures [@simonw] | [#1757] - Update xmldom to fix medium security vulnerability [@chriswhite199] | [#1752] - Update JSONWebToken to fix medium security vulnerability [@chriswhite199] | [#1753] ### [10.10.0] - 2024-03-27 - Added 'JA4 Fingerprint' operation [@n1474335] | [#1759] ### [10.9.0] - 2024-03-26 - Line ending sequences and UTF-8 character encoding are now detected automatically [@n1474335] | [65ffd8d] ### [10.8.0] - 2024-02-13 - Add official Docker images [@AshCorr] | [#1699] ### [10.7.0] - 2024-02-09 - Added 'File Tree' operation [@sw5678] | [#1667] - Added 'RISON' operation [@sg5506844] | [#1555] - Added 'MurmurHash3' operation [@AliceGrey] | [#1694] ### [10.6.0] - 2024-02-03 - Updated 'Forensics Wiki' URLs to new domain [@a3957273] | [#1703] - Added 'LZNT1 Decompress' operation [@0xThiebaut] | [#1675] - Updated 'Regex Expression' UUID matcher [@cnotin] | [#1678] - Removed duplicate 'hover' message within baking info [@KevinSJ] | [#1541] ### [10.5.0] - 2023-07-14 - Added GOST Encrypt, Decrypt, Sign, Verify, Key Wrap, and Key Unwrap operations [@n1474335] | [#592] ### [10.4.0] - 2023-03-24 - Added 'Generate De Bruijn Sequence' operation [@gchq77703] | [#493] ### [10.3.0] - 2023-03-24 - Added 'Argon2' and 'Argon2 compare' operations [@Xenonym] | [#661] ### [10.2.0] - 2023-03-23 - Added 'Derive HKDF key' operation [@mikecat] | [#1528] ### [10.1.0] - 2023-03-23 - Added 'Levenshtein Distance' operation [@mikecat] | [#1498] - Added 'Swap case' operation [@mikecat] | [#1499] ## [10.0.0] - 2023-03-22 - [Full details explained here](https://github.com/gchq/CyberChef/wiki/Character-encoding,-EOL-separators,-and-editor-features) - Status bars added to the Input and Output [@n1474335] | [#1405] - Character encoding selection added to the Input and Output [@n1474335] | [#1405] - End of line separator selection added to the Input and Output [@n1474335] | [#1405] - Non-printable characters are rendered as control character pictures [@n1474335] | [#1405] - Loaded files can now be edited in the Input [@n1474335] | [#1405] - Various editor features added such as multiple selections and bracket matching [@n1474335] | [#1405] - Contextual help added, activated by pressing F1 while hovering over features [@n1474335] | [#1405] - Many, many UI tests added for I/O features and operations [@n1474335] | [#1405]
Click to expand v9 minor versions ### [9.55.0] - 2022-12-09 - Added 'AMF Encode' and 'AMF Decode' operations [@n1474335] | [760eff4] ### [9.54.0] - 2022-11-25 - Added 'Rabbit' operation [@mikecat] | [#1450] ### [9.53.0] - 2022-11-25 - Added 'AES Key Wrap' and 'AES Key Unwrap' operations [@mikecat] | [#1456] ### [9.52.0] - 2022-11-25 - Added 'ChaCha' operation [@joostrijneveld] | [#1466] ### [9.51.0] - 2022-11-25 - Added 'CMAC' operation [@mikecat] | [#1457] ### [9.50.0] - 2022-11-25 - Added 'Shuffle' operation [@mikecat] | [#1472] ### [9.49.0] - 2022-11-11 - Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83] ### [9.48.0] - 2022-10-14 - Added 'LM Hash' and 'NT Hash' operations [@n1474335] [@brun0ne] | [#1427] ### [9.47.0] - 2022-10-14 - Added 'LZMA Decompress' and 'LZMA Compress' operations [@mattnotmitt] | [#1421] ### [9.46.0] - 2022-07-08 - Added 'Cetacean Cipher Encode' and 'Cetacean Cipher Decode' operations [@valdelaseras] | [#1308] ### [9.45.0] - 2022-07-08 - Added 'ROT8000' operation [@thomasleplus] | [#1250] ### [9.44.0] - 2022-07-08 - Added 'LZString Compress' and 'LZString Decompress' operations [@crespyl] | [#1266] ### [9.43.0] - 2022-07-08 - Added 'ROT13 Brute Force' and 'ROT47 Brute Force' operations [@mikecat] | [#1264] ### [9.42.0] - 2022-07-08 - Added 'LS47 Encrypt' and 'LS47 Decrypt' operations [@n1073645] | [#951] ### [9.41.0] - 2022-07-08 - Added 'Caesar Box Cipher' operation [@n1073645] | [#1066] ### [9.40.0] - 2022-07-08 - Added 'P-list Viewer' operation [@n1073645] | [#906] ### [9.39.0] - 2022-06-09 - Added 'ELF Info' operation [@n1073645] | [#1364] ### [9.38.0] - 2022-05-30 - Added 'Parse TCP' operation [@n1474335] | [a895d1d] ### [9.37.0] - 2022-03-29 - 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189] - NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189] ### [9.36.0] - 2022-03-29 - 'SIGABA' operation added [@hettysymes] | [#934] ### [9.35.0] - 2022-03-28 - 'To Base45' and 'From Base45' operations added [@t-8ch] | [#1242] ### [9.34.0] - 2022-03-28 - 'Get All Casings' operation added [@n1073645] | [#1065] ### [9.33.0] - 2022-03-25 - Updated to support Node 17 [@n1474335] [@john19696] [@t-8ch] | [[#1326] [#1313] [#1244] - Improved CJS and ESM module support [@d98762625] | [#1037] ### [9.32.0] - 2021-08-18 - 'Protobuf Encode' operation added and decode operation modified to allow decoding with full and partial schemas [@n1474335] | [dd18e52] ### [9.31.0] - 2021-08-10 - 'HASSH Client Fingerprint' and 'HASSH Server Fingerprint' operations added [@n1474335] | [e9ca4dc] ### [9.30.0] - 2021-08-10 - 'JA3S Fingerprint' operation added [@n1474335] | [289a417] ### [9.29.0] - 2021-07-28 - 'JA3 Fingerprint' operation added [@n1474335] | [9a33498] ### [9.28.0] - 2021-03-26 - 'CBOR Encode' and 'CBOR Decode' operations added [@Danh4] | [#999] ### [9.27.0] - 2021-02-12 - 'Fuzzy Match' operation added [@n1474335] | [8ad18b] ### [9.26.0] - 2021-02-11 - 'Get Time' operation added [@n1073645] [@n1474335] | [#1045] ### [9.25.0] - 2021-02-11 - 'Extract ID3' operation added [@n1073645] [@n1474335] | [#1006] ### [9.24.0] - 2021-02-02 - 'SM3' hashing function added along with more configuration options for other hashing operations [@n1073645] [@n1474335] | [#1022] ### [9.23.0] - 2021-02-01 - Various RSA operations added to encrypt, decrypt, sign, verify and generate keys [@mattnotmitt] [@GCHQ77703] | [#652] ### [9.22.0] - 2021-02-01 - 'Unicode Text Format' operation added [@mattnotmitt] | [#1083] ### [9.21.0] - 2020-06-12 - Node API now exports `magic` operation [@d98762625] | [#1049] ### [9.20.0] - 2020-03-27 - 'Parse ObjectID Timestamp' operation added [@dmfj] | [#987] ### [9.19.0] - 2020-03-24 - 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) ### [9.18.0] - 2020-03-13 - 'Convert to NATO alphabet' operation added [@MarvinJWendt] | [#674] ### [9.17.0] - 2020-03-13 - 'Generate Image' operation added [@pointhi] | [#683] ### [9.16.0] - 2020-03-06 - 'Colossus' operation added [@VirtualColossus] | [#917] ### [9.15.0] - 2020-03-05 - 'CipherSaber2 Encrypt' and 'CipherSaber2 Decrypt' operations added [@n1073645] | [#952] ### [9.14.0] - 2020-03-05 - 'Luhn Checksum' operation added [@n1073645] | [#965] ### [9.13.0] - 2020-02-13 - 'Rail Fence Cipher Encode' and 'Rail Fence Cipher Decode' operations added [@Flavsditz] | [#948] ### [9.12.0] - 2019-12-20 - 'Normalise Unicode' operation added [@matthieuxyz] | [#912] ### [9.11.0] - 2019-11-06 - Implemented CFB, OFB, and CTR modes for Blowfish operations [@cbeuw] | [#653] ### [9.10.0] - 2019-11-06 - 'Lorenz' operation added [@VirtualColossus] | [#528] ### [9.9.0] - 2019-11-01 - Added support for 109 more character encodings [@n1474335] ### [9.8.0] - 2019-10-31 - 'Avro to JSON' operation added [@jarrodconnolly] | [#865] ### [9.7.0] - 2019-09-13 - 'Optical Character Recognition' operation added [@MShwed] [@n1474335] | [#632] ### [9.6.0] - 2019-09-04 - 'Bacon Cipher Encode' and 'Bacon Cipher Decode' operations added [@kassi] | [#500] ### [9.5.0] - 2019-09-04 - Various Steganography operations added: 'Extract LSB', 'Extract RGBA', 'Randomize Colour Palette', and 'View Bit Plane' [@Ge0rg3] | [#625] ### [9.4.0] - 2019-08-30 - 'Render Markdown' operation added [@j433866] | [#627] ### [9.3.0] - 2019-08-30 - 'Show on map' operation added [@j433866] | [#477] ### [9.2.0] - 2019-08-23 - 'Parse UDP' operation added [@h345983745] | [#614] ### [9.1.0] - 2019-08-22 - 'Parse SSH Host Key' operation added [@j433866] | [#595] - 'Defang IP Addresses' operation added [@h345983745] | [#556]
## [9.0.0] - 2019-07-09 - [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] - 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] - 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] - Light and dark Solarized themes added [@j433866] | [#566]
Click to expand v8 minor versions ### [8.38.0] - 2019-07-03 - 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530] ### [8.37.0] - 2019-07-03 - 'CRC-8 Checksum' operation added [@MShwed] | [#591] ### [8.36.0] - 2019-07-03 - 'PGP Verify' operation added [@artemisbot] | [#585] ### [8.35.0] - 2019-07-03 - 'Sharpen Image', 'Convert Image Format' and 'Add Text To Image' operations added [@j433866] | [#515] ### [8.34.0] - 2019-06-28 - Various new visualisations added to the 'Entropy' operation [@MShwed] | [#535] - Efficiency improvements made to the 'Entropy' operation for large file support [@n1474335] ### [8.33.0] - 2019-06-27 - 'Bzip2 Compress' operation added and 'Bzip2 Decompress' operation greatly improved [@artemisbot] | [#531] ### [8.32.0] - 2019-06-27 - 'Index of Coincidence' operation added [@Ge0rg3] | [#571] ### [8.31.0] - 2019-04-12 - 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] ### [8.30.0] - 2019-04-12 - 'Decode Protobuf' operation added [@n1474335] | [#533] ### [8.29.0] - 2019-03-31 - 'BLAKE2s' and 'BLAKE2b' hashing operations added [@h345983745] | [#525] ### [8.28.0] - 2019-03-31 - 'Heatmap Chart', 'Hex Density Chart', 'Scatter Chart' and 'Series Chart' operation added [@artemisbot] [@tlwr] | [#496] [#143] ### [8.27.0] - 2019-03-14 - 'Enigma', 'Typex', 'Bombe' and 'Multiple Bombe' operations added [@s2224834] | [#516] - See [this wiki article](https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex) for a full explanation of these operations. - New Bombe-style loading animation added for long-running operations [@n1474335] - New operation argument types added: `populateMultiOption` and `argSelector` [@n1474335] ### [8.26.0] - 2019-03-09 - Various image manipulation operations added [@j433866] | [#506] ### [8.25.0] - 2019-03-09 - 'Extract Files' operation added and more file formats supported [@n1474335] | [#440] ### [8.24.0] - 2019-02-08 - 'DNS over HTTPS' operation added [@h345983745] | [#489] ### [8.23.1] - 2019-01-18 - 'Convert co-ordinate format' operation added [@j433866] | [#476] ### [8.23.0] - 2019-01-18 - 'YARA Rules' operation added [@artemisbot] | [#468] ### [8.22.0] - 2019-01-10 - 'Subsection' operation added [@j433866] | [#467] ### [8.21.0] - 2019-01-10 - 'To Case Insensitive Regex' and 'From Case Insensitive Regex' operations added [@masq] | [#461] ### [8.20.0] - 2019-01-09 - 'Generate Lorem Ipsum' operation added [@klaxon1] | [#455] ### [8.19.0] - 2018-12-30 - UI test suite added to confirm that the app loads correctly in a reasonable time and that various operations from each module can be run [@n1474335] | [#458] ### [8.18.0] - 2018-12-26 - 'Split Colour Channels' operation added [@artemisbot] | [#449] ### [8.17.0] - 2018-12-25 - 'Generate QR Code' and 'Parse QR Code' operations added [@j433866] | [#448] ### [8.16.0] - 2018-12-19 - 'Play Media' operation added [@anthony-arnold] | [#446] ### [8.15.0] - 2018-12-18 - 'Text Encoding Brute Force' operation added [@Cynser] | [#439] ### [8.14.0] - 2018-12-18 - 'To Base62' and 'From Base62' operations added [@tcode2k16] | [#443] ### [8.13.0] - 2018-12-15 - 'A1Z26 Cipher Encode' and 'A1Z26 Cipher Decode' operations added [@jarmovanlenthe] | [#441] ### [8.12.0] - 2018-11-21 - 'Citrix CTX1 Encode' and 'Citrix CTX1 Decode' operations added [@bwhitn] | [#428] ### [8.11.0] - 2018-11-13 - 'CSV to JSON' and 'JSON to CSV' operations added [@n1474335] | [#277] ### [8.10.0] - 2018-11-07 - 'Remove Diacritics' operation added [@klaxon1] | [#387] ### [8.9.0] - 2018-11-07 - 'Defang URL' operation added [@arnydo] | [#394] ### [8.8.0] - 2018-10-10 - 'Parse TLV' operation added [@GCHQ77703] | [#351] ### [8.7.0] - 2018-08-31 - 'JWT Sign', 'JWT Verify' and 'JWT Decode' operations added [@GCHQ77703] | [#348] ### [8.6.0] - 2018-08-29 - 'To Geohash' and 'From Geohash' operations added [@GCHQ77703] | [#344] ### [8.5.0] - 2018-08-23 - 'To Braille' and 'From Braille' operations added [@n1474335] | [#255] ### [8.4.0] - 2018-08-23 - 'To Base85' and 'From Base85' operations added [@PenguinGeorge] | [#340] ### [8.3.0] - 2018-08-21 - 'To MessagePack' and 'From MessagePack' operations added [@artemisbot] | [#338] ### [8.2.0] - 2018-08-21 - Information links added to most operations, accessible in the description popover [@PenguinGeorge] | [#298] ### [8.1.0] - 2018-08-19 - 'Dechunk HTTP response' operation added [@sevzero] | [#311]
## [8.0.0] - 2018-08-05 - 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] - Operation architecture restructured to make adding new operations a lot simpler [@n1474335] | [#284] - A script has been added to aid in the creation of new operations by running `npm run newop` [@n1474335] | [#284] - 'Magic' operation added - [automated detection of encoded data](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) [@n1474335] | [#239] - UI updated to use [Bootstrap Material Design](https://fezvrasta.github.io/bootstrap-material-design/) [@n1474335] | [#248] - `JSON`, `File` and `List` Dish types added [@n1474335] | [#284] - `OperationError` type added for better handling of errors thrown by operations [@d98762625] | [#296] - 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] - Set operations added [@d98762625] | [#281] - 'To Table' operation added [@JustAnotherMark] | [#294] - 'Haversine distance' operation added [@Dachande663] | [#325] - Started keeping a changelog [@n1474335] ## [7.0.0] - 2017-12-28 - Added support for loading, processing and downloading files up to 500MB [@n1474335] | [#224] ## [6.0.0] - 2017-09-19 - 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] - 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] ## [5.0.0] - 2017-03-30 - Webpack build process configured with Babel transpilation and ES6 imports and exports [@n1474335] | [#95] ## [4.0.0] - 2016-11-28 - Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306) [10.22.0]: https://github.com/gchq/CyberChef/releases/tag/v10.22.0 [10.21.0]: https://github.com/gchq/CyberChef/releases/tag/v10.21.0 [10.20.0]: https://github.com/gchq/CyberChef/releases/tag/v10.20.0 [10.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0 [10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0 [10.17.0]: https://github.com/gchq/CyberChef/releases/tag/v10.17.0 [10.16.0]: https://github.com/gchq/CyberChef/releases/tag/v10.16.0 [10.15.0]: https://github.com/gchq/CyberChef/releases/tag/v10.15.0 [10.14.0]: https://github.com/gchq/CyberChef/releases/tag/v10.14.0 [10.13.0]: https://github.com/gchq/CyberChef/releases/tag/v10.13.0 [10.12.0]: https://github.com/gchq/CyberChef/releases/tag/v10.12.0 [10.11.0]: https://github.com/gchq/CyberChef/releases/tag/v10.11.0 [10.10.0]: https://github.com/gchq/CyberChef/releases/tag/v10.10.0 [10.9.0]: https://github.com/gchq/CyberChef/releases/tag/v10.9.0 [10.8.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0 [10.7.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0 [10.6.0]: https://github.com/gchq/CyberChef/releases/tag/v10.6.0 [10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0 [10.4.0]: https://github.com/gchq/CyberChef/releases/tag/v10.4.0 [10.3.0]: https://github.com/gchq/CyberChef/releases/tag/v10.3.0 [10.2.0]: https://github.com/gchq/CyberChef/releases/tag/v10.2.0 [10.1.0]: https://github.com/gchq/CyberChef/releases/tag/v10.1.0 [10.0.0]: https://github.com/gchq/CyberChef/releases/tag/v10.0.0 [9.55.0]: https://github.com/gchq/CyberChef/releases/tag/v9.55.0 [9.54.0]: https://github.com/gchq/CyberChef/releases/tag/v9.54.0 [9.53.0]: https://github.com/gchq/CyberChef/releases/tag/v9.53.0 [9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0 [9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0 [9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0 [9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0 [9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0 [9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0 [9.46.0]: https://github.com/gchq/CyberChef/releases/tag/v9.46.0 [9.45.0]: https://github.com/gchq/CyberChef/releases/tag/v9.45.0 [9.44.0]: https://github.com/gchq/CyberChef/releases/tag/v9.44.0 [9.43.0]: https://github.com/gchq/CyberChef/releases/tag/v9.43.0 [9.42.0]: https://github.com/gchq/CyberChef/releases/tag/v9.42.0 [9.41.0]: https://github.com/gchq/CyberChef/releases/tag/v9.41.0 [9.40.0]: https://github.com/gchq/CyberChef/releases/tag/v9.40.0 [9.39.0]: https://github.com/gchq/CyberChef/releases/tag/v9.39.0 [9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0 [9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0 [9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0 [9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0 [9.34.0]: https://github.com/gchq/CyberChef/releases/tag/v9.34.0 [9.33.0]: https://github.com/gchq/CyberChef/releases/tag/v9.33.0 [9.32.0]: https://github.com/gchq/CyberChef/releases/tag/v9.32.0 [9.31.0]: https://github.com/gchq/CyberChef/releases/tag/v9.31.0 [9.30.0]: https://github.com/gchq/CyberChef/releases/tag/v9.30.0 [9.29.0]: https://github.com/gchq/CyberChef/releases/tag/v9.29.0 [9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0 [9.27.0]: https://github.com/gchq/CyberChef/releases/tag/v9.27.0 [9.26.0]: https://github.com/gchq/CyberChef/releases/tag/v9.26.0 [9.25.0]: https://github.com/gchq/CyberChef/releases/tag/v9.25.0 [9.24.0]: https://github.com/gchq/CyberChef/releases/tag/v9.24.0 [9.23.0]: https://github.com/gchq/CyberChef/releases/tag/v9.23.0 [9.22.0]: https://github.com/gchq/CyberChef/releases/tag/v9.22.0 [9.21.0]: https://github.com/gchq/CyberChef/releases/tag/v9.21.0 [9.20.0]: https://github.com/gchq/CyberChef/releases/tag/v9.20.0 [9.19.0]: https://github.com/gchq/CyberChef/releases/tag/v9.19.0 [9.18.0]: https://github.com/gchq/CyberChef/releases/tag/v9.18.0 [9.17.0]: https://github.com/gchq/CyberChef/releases/tag/v9.17.0 [9.16.0]: https://github.com/gchq/CyberChef/releases/tag/v9.16.0 [9.15.0]: https://github.com/gchq/CyberChef/releases/tag/v9.15.0 [9.14.0]: https://github.com/gchq/CyberChef/releases/tag/v9.14.0 [9.13.0]: https://github.com/gchq/CyberChef/releases/tag/v9.13.0 [9.12.0]: https://github.com/gchq/CyberChef/releases/tag/v9.12.0 [9.11.0]: https://github.com/gchq/CyberChef/releases/tag/v9.11.0 [9.10.0]: https://github.com/gchq/CyberChef/releases/tag/v9.10.0 [9.9.0]: https://github.com/gchq/CyberChef/releases/tag/v9.9.0 [9.8.0]: https://github.com/gchq/CyberChef/releases/tag/v9.8.0 [9.7.0]: https://github.com/gchq/CyberChef/releases/tag/v9.7.0 [9.6.0]: https://github.com/gchq/CyberChef/releases/tag/v9.6.0 [9.5.0]: https://github.com/gchq/CyberChef/releases/tag/v9.5.0 [9.4.0]: https://github.com/gchq/CyberChef/releases/tag/v9.4.0 [9.3.0]: https://github.com/gchq/CyberChef/releases/tag/v9.3.0 [9.2.0]: https://github.com/gchq/CyberChef/releases/tag/v9.2.0 [9.1.0]: https://github.com/gchq/CyberChef/releases/tag/v9.1.0 [9.0.0]: https://github.com/gchq/CyberChef/releases/tag/v9.0.0 [8.38.0]: https://github.com/gchq/CyberChef/releases/tag/v8.38.0 [8.37.0]: https://github.com/gchq/CyberChef/releases/tag/v8.37.0 [8.36.0]: https://github.com/gchq/CyberChef/releases/tag/v8.36.0 [8.35.0]: https://github.com/gchq/CyberChef/releases/tag/v8.35.0 [8.34.0]: https://github.com/gchq/CyberChef/releases/tag/v8.34.0 [8.33.0]: https://github.com/gchq/CyberChef/releases/tag/v8.33.0 [8.32.0]: https://github.com/gchq/CyberChef/releases/tag/v8.32.0 [8.31.0]: https://github.com/gchq/CyberChef/releases/tag/v8.31.0 [8.30.0]: https://github.com/gchq/CyberChef/releases/tag/v8.30.0 [8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0 [8.28.0]: https://github.com/gchq/CyberChef/releases/tag/v8.28.0 [8.27.0]: https://github.com/gchq/CyberChef/releases/tag/v8.27.0 [8.26.0]: https://github.com/gchq/CyberChef/releases/tag/v8.26.0 [8.25.0]: https://github.com/gchq/CyberChef/releases/tag/v8.25.0 [8.24.0]: https://github.com/gchq/CyberChef/releases/tag/v8.24.0 [8.23.1]: https://github.com/gchq/CyberChef/releases/tag/v8.23.1 [8.23.0]: https://github.com/gchq/CyberChef/releases/tag/v8.23.0 [8.22.0]: https://github.com/gchq/CyberChef/releases/tag/v8.22.0 [8.21.0]: https://github.com/gchq/CyberChef/releases/tag/v8.21.0 [8.20.0]: https://github.com/gchq/CyberChef/releases/tag/v8.20.0 [8.19.0]: https://github.com/gchq/CyberChef/releases/tag/v8.19.0 [8.18.0]: https://github.com/gchq/CyberChef/releases/tag/v8.18.0 [8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0 [8.16.0]: https://github.com/gchq/CyberChef/releases/tag/v8.16.0 [8.15.0]: https://github.com/gchq/CyberChef/releases/tag/v8.15.0 [8.14.0]: https://github.com/gchq/CyberChef/releases/tag/v8.14.0 [8.13.0]: https://github.com/gchq/CyberChef/releases/tag/v8.13.0 [8.12.0]: https://github.com/gchq/CyberChef/releases/tag/v8.12.0 [8.11.0]: https://github.com/gchq/CyberChef/releases/tag/v8.11.0 [8.10.0]: https://github.com/gchq/CyberChef/releases/tag/v8.10.0 [8.9.0]: https://github.com/gchq/CyberChef/releases/tag/v8.9.0 [8.8.0]: https://github.com/gchq/CyberChef/releases/tag/v8.8.0 [8.7.0]: https://github.com/gchq/CyberChef/releases/tag/v8.7.0 [8.6.0]: https://github.com/gchq/CyberChef/releases/tag/v8.6.0 [8.5.0]: https://github.com/gchq/CyberChef/releases/tag/v8.5.0 [8.4.0]: https://github.com/gchq/CyberChef/releases/tag/v8.4.0 [8.3.0]: https://github.com/gchq/CyberChef/releases/tag/v8.3.0 [8.2.0]: https://github.com/gchq/CyberChef/releases/tag/v8.2.0 [8.1.0]: https://github.com/gchq/CyberChef/releases/tag/v8.1.0 [8.0.0]: https://github.com/gchq/CyberChef/releases/tag/v8.0.0 [7.0.0]: https://github.com/gchq/CyberChef/releases/tag/v7.0.0 [6.0.0]: https://github.com/gchq/CyberChef/releases/tag/v6.0.0 [5.0.0]: https://github.com/gchq/CyberChef/releases/tag/v5.0.0 [4.0.0]: https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306 [@n1474335]: https://github.com/n1474335 [@d98762625]: https://github.com/d98762625 [@j433866]: https://github.com/j433866 [@n1073645]: https://github.com/n1073645 [@GCHQ77703]: https://github.com/GCHQ77703 [@h345983745]: https://github.com/h345983745 [@s2224834]: https://github.com/s2224834 [@artemisbot]: https://github.com/artemisbot [@tlwr]: https://github.com/tlwr [@picapi]: https://github.com/picapi [@Dachande663]: https://github.com/Dachande663 [@JustAnotherMark]: https://github.com/JustAnotherMark [@sevzero]: https://github.com/sevzero [@PenguinGeorge]: https://github.com/PenguinGeorge [@arnydo]: https://github.com/arnydo [@klaxon1]: https://github.com/klaxon1 [@bwhitn]: https://github.com/bwhitn [@jarmovanlenthe]: https://github.com/jarmovanlenthe [@tcode2k16]: https://github.com/tcode2k16 [@Cynser]: https://github.com/Cynser [@anthony-arnold]: https://github.com/anthony-arnold [@masq]: https://github.com/masq [@Ge0rg3]: https://github.com/Ge0rg3 [@MShwed]: https://github.com/MShwed [@kassi]: https://github.com/kassi [@jarrodconnolly]: https://github.com/jarrodconnolly [@VirtualColossus]: https://github.com/VirtualColossus [@cbeuw]: https://github.com/cbeuw [@matthieuxyz]: https://github.com/matthieuxyz [@Flavsditz]: https://github.com/Flavsditz [@pointhi]: https://github.com/pointhi [@MarvinJWendt]: https://github.com/MarvinJWendt [@dmfj]: https://github.com/dmfj [@mattnotmitt]: https://github.com/mattnotmitt [@Danh4]: https://github.com/Danh4 [@john19696]: https://github.com/john19696 [@t-8ch]: https://github.com/t-8ch [@hettysymes]: https://github.com/hettysymes [@swesven]: https://github.com/swesven [@mikecat]: https://github.com/mikecat [@crespyl]: https://github.com/crespyl [@thomasleplus]: https://github.com/thomasleplus [@valdelaseras]: https://github.com/valdelaseras [@brun0ne]: https://github.com/brun0ne [@joostrijneveld]: https://github.com/joostrijneveld [@Xenonym]: https://github.com/Xenonym [@gchq77703]: https://github.com/gchq77703 [@a3957273]: https://github.com/a3957273 [@0xThiebaut]: https://github.com/0xThiebaut [@cnotin]: https://github.com/cnotin [@KevinSJ]: https://github.com/KevinSJ [@sw5678]: https://github.com/sw5678 [@sg5506844]: https://github.com/sg5506844 [@AliceGrey]: https://github.com/AliceGrey [@AshCorr]: https://github.com/AshCorr [@simonw]: https://github.com/simonw [@chriswhite199]: https://github.com/chriswhite199 [@breakersall]: https://github.com/breakersall [@evanreichard]: https://github.com/evanreichard [@devcydo]: https://github.com/devcydo [@zb3]: https://github.com/zb3 [@jkataja]: https://github.com/jkataja [@tomgond]: https://github.com/tomgond [@e218736]: https://github.com/e218736 [@TheZ3ro]: https://github.com/TheZ3ro [@EvieHarv]: https://github.com/EvieHarv [@cplussharp]: https://github.com/cplussharp [@robinsandhu]: https://github.com/robinsandhu [@eltociear]: https://github.com/eltociear [@GuilhermoReadonly]: https://github.com/GuilhermoReadonly [@simonarnell]: https://github.com/simonarnell [@RandomByte]: https://github.com/RandomByte [@c65722]: https://github.com/c65722 [@c65722]: https://github.com/c65722 [@c65722]: https://github.com/c65722 [@max0x53]: https://github.com/max0x53 [@Adamkadaban]: https://github.com/Adamkadaban [@c65722]: https://github.com/c65722 [@jb30795]: https://github.com/jb30795 [@FranciscoPombal]: https://github.com/FranciscoPombal [@Oshawk]: https://github.com/Oshawk [@Oshawk]: https://github.com/Oshawk [@bartblaze]: https://github.com/bartblaze [@exactlyaron]: https://github.com/exactlyaron [@k3ach]: https://github.com/k3ach [@vs4vijay]: https://github.com/vs4vijay [@FranciscoPombal]: https://github.com/FranciscoPombal [@FranciscoPombal]: https://github.com/FranciscoPombal [@linuxgemini]: https://github.com/linuxgemini [@depperm]: https://github.com/depperm [@evenstensberg]: https://github.com/evenstensberg [@bartblaze]: https://github.com/bartblaze [@0xh3xa]: https://github.com/0xh3xa [@flakjacket95]: https://github.com/flakjacket95 [@zhzy0077]: https://github.com/zhzy0077 [@JSCU-CNI]: https://github.com/JSCU-CNI [@ccarpo]: https://github.com/ccarpo [@r4mos]: https://github.com/r4mos [@0xh3xa]: https://github.com/0xh3xa [@0xh3xa]: https://github.com/0xh3xa [@PathToLife]: https://github.com/PathToLife [@peterc-s]: https://github.com/peterc-s [@plvie]: https://github.com/plvie [@kenduguay1]: https://github.com/kenduguay1 [@jonking-ajar]: https://github.com/jonking-ajar [@PathToLife]: https://github.com/PathToLife [@r4mos]: https://github.com/r4mos [@jg42526]: https://github.com/jg42526 [@es45411]: https://github.com/es45411 [@gchq]: https://github.com/gchq [@gchqdev364]: https://github.com/gchqdev364 [@GCHQDeveloper94872]: https://github.com/GCHQDeveloper94872 [@Sma-Das]: https://github.com/Sma-Das [@gchq]: https://github.com/gchq [@Odyhibit]: https://github.com/Odyhibit [@ericli-splunk]: https://github.com/ericli-splunk [@xumptex]: https://github.com/xumptex [@bartvanandel]: https://github.com/bartvanandel [@bartvanandel]: https://github.com/bartvanandel [@kendallgoto]: https://github.com/kendallgoto [@remingtr]: https://github.com/remingtr [@0xff1ce]: https://github.com/0xff1ce [@starplanet]: https://github.com/starplanet [@C85297]: https://github.com/C85297 [@GCHQDeveloper581]: https://github.com/GCHQDeveloper581 [@ThomasNotTom]: https://github.com/ThomasNotTom [@rbpi]: https://github.com/rbpi [@AlexGustafsson]: https://github.com/AlexGustafsson [@tuliperis]: https://github.com/tuliperis [@thomasxm]: https://github.com/thomasxm [@twostraws]: https://github.com/twostraws [@beneri]: https://github.com/beneri [@t-martine]: https://github.com/t-martine [@wesinator]: https://github.com/wesinator [@Raka-loah]: https://github.com/Raka-loah [8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7 [9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513 [289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe [e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8 [dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da [a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737 [31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff [760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1 [65ffd8d]: https://github.com/gchq/CyberChef/commit/65ffd8d65d88eb369f6f61a5d1d0f807179bffb7 [0a353ee]: https://github.com/gchq/CyberChef/commit/0a353eeb378b9ca5d49e23c7dfc175ae07107b08 [66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 [ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 [965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 [a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac [7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 [5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 [0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 [d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 [895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 [270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 [d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 [47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 [3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 [66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 [ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 [965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 [a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac [7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 [5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 [0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 [d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 [895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 [270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 [d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 [47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 [3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 [66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 [ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 [965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 [a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac [7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 [5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 [0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 [d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 [895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 [270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 [d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 [47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 [3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 [66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 [#95]: https://github.com/gchq/CyberChef/pull/299 [#173]: https://github.com/gchq/CyberChef/pull/173 [#143]: https://github.com/gchq/CyberChef/pull/143 [#224]: https://github.com/gchq/CyberChef/pull/224 [#239]: https://github.com/gchq/CyberChef/pull/239 [#248]: https://github.com/gchq/CyberChef/pull/248 [#255]: https://github.com/gchq/CyberChef/issues/255 [#277]: https://github.com/gchq/CyberChef/issues/277 [#281]: https://github.com/gchq/CyberChef/pull/281 [#284]: https://github.com/gchq/CyberChef/pull/284 [#291]: https://github.com/gchq/CyberChef/pull/291 [#294]: https://github.com/gchq/CyberChef/pull/294 [#296]: https://github.com/gchq/CyberChef/pull/296 [#298]: https://github.com/gchq/CyberChef/pull/298 [#311]: https://github.com/gchq/CyberChef/pull/311 [#325]: https://github.com/gchq/CyberChef/pull/325 [#338]: https://github.com/gchq/CyberChef/pull/338 [#340]: https://github.com/gchq/CyberChef/pull/340 [#344]: https://github.com/gchq/CyberChef/pull/344 [#348]: https://github.com/gchq/CyberChef/pull/348 [#351]: https://github.com/gchq/CyberChef/pull/351 [#387]: https://github.com/gchq/CyberChef/pull/387 [#394]: https://github.com/gchq/CyberChef/pull/394 [#428]: https://github.com/gchq/CyberChef/pull/428 [#439]: https://github.com/gchq/CyberChef/pull/439 [#440]: https://github.com/gchq/CyberChef/pull/440 [#441]: https://github.com/gchq/CyberChef/pull/441 [#443]: https://github.com/gchq/CyberChef/pull/443 [#446]: https://github.com/gchq/CyberChef/pull/446 [#448]: https://github.com/gchq/CyberChef/pull/448 [#449]: https://github.com/gchq/CyberChef/pull/449 [#455]: https://github.com/gchq/CyberChef/pull/455 [#458]: https://github.com/gchq/CyberChef/pull/458 [#461]: https://github.com/gchq/CyberChef/pull/461 [#467]: https://github.com/gchq/CyberChef/pull/467 [#468]: https://github.com/gchq/CyberChef/pull/468 [#476]: https://github.com/gchq/CyberChef/pull/476 [#477]: https://github.com/gchq/CyberChef/pull/477 [#489]: https://github.com/gchq/CyberChef/pull/489 [#496]: https://github.com/gchq/CyberChef/pull/496 [#500]: https://github.com/gchq/CyberChef/pull/500 [#506]: https://github.com/gchq/CyberChef/pull/506 [#515]: https://github.com/gchq/CyberChef/pull/515 [#516]: https://github.com/gchq/CyberChef/pull/516 [#525]: https://github.com/gchq/CyberChef/pull/525 [#528]: https://github.com/gchq/CyberChef/pull/528 [#530]: https://github.com/gchq/CyberChef/pull/530 [#531]: https://github.com/gchq/CyberChef/pull/531 [#533]: https://github.com/gchq/CyberChef/pull/533 [#535]: https://github.com/gchq/CyberChef/pull/535 [#556]: https://github.com/gchq/CyberChef/pull/556 [#566]: https://github.com/gchq/CyberChef/pull/566 [#571]: https://github.com/gchq/CyberChef/pull/571 [#585]: https://github.com/gchq/CyberChef/pull/585 [#591]: https://github.com/gchq/CyberChef/pull/591 [#595]: https://github.com/gchq/CyberChef/pull/595 [#614]: https://github.com/gchq/CyberChef/pull/614 [#625]: https://github.com/gchq/CyberChef/pull/625 [#627]: https://github.com/gchq/CyberChef/pull/627 [#632]: https://github.com/gchq/CyberChef/pull/632 [#652]: https://github.com/gchq/CyberChef/pull/652 [#653]: https://github.com/gchq/CyberChef/pull/653 [#674]: https://github.com/gchq/CyberChef/pull/674 [#683]: https://github.com/gchq/CyberChef/pull/683 [#865]: https://github.com/gchq/CyberChef/pull/865 [#906]: https://github.com/gchq/CyberChef/pull/906 [#912]: https://github.com/gchq/CyberChef/pull/912 [#917]: https://github.com/gchq/CyberChef/pull/917 [#934]: https://github.com/gchq/CyberChef/pull/934 [#948]: https://github.com/gchq/CyberChef/pull/948 [#951]: https://github.com/gchq/CyberChef/pull/951 [#952]: https://github.com/gchq/CyberChef/pull/952 [#965]: https://github.com/gchq/CyberChef/pull/965 [#966]: https://github.com/gchq/CyberChef/pull/966 [#987]: https://github.com/gchq/CyberChef/pull/987 [#999]: https://github.com/gchq/CyberChef/pull/999 [#1006]: https://github.com/gchq/CyberChef/pull/1006 [#1022]: https://github.com/gchq/CyberChef/pull/1022 [#1037]: https://github.com/gchq/CyberChef/pull/1037 [#1045]: https://github.com/gchq/CyberChef/pull/1045 [#1049]: https://github.com/gchq/CyberChef/pull/1049 [#1065]: https://github.com/gchq/CyberChef/pull/1065 [#1066]: https://github.com/gchq/CyberChef/pull/1066 [#1083]: https://github.com/gchq/CyberChef/pull/1083 [#1189]: https://github.com/gchq/CyberChef/pull/1189 [#1242]: https://github.com/gchq/CyberChef/pull/1242 [#1244]: https://github.com/gchq/CyberChef/pull/1244 [#1313]: https://github.com/gchq/CyberChef/pull/1313 [#1326]: https://github.com/gchq/CyberChef/pull/1326 [#1364]: https://github.com/gchq/CyberChef/pull/1364 [#1264]: https://github.com/gchq/CyberChef/pull/1264 [#1266]: https://github.com/gchq/CyberChef/pull/1266 [#1250]: https://github.com/gchq/CyberChef/pull/1250 [#1308]: https://github.com/gchq/CyberChef/pull/1308 [#1405]: https://github.com/gchq/CyberChef/pull/1405 [#1421]: https://github.com/gchq/CyberChef/pull/1421 [#1427]: https://github.com/gchq/CyberChef/pull/1427 [#1472]: https://github.com/gchq/CyberChef/pull/1472 [#1457]: https://github.com/gchq/CyberChef/pull/1457 [#1466]: https://github.com/gchq/CyberChef/pull/1466 [#1456]: https://github.com/gchq/CyberChef/pull/1456 [#1450]: https://github.com/gchq/CyberChef/pull/1450 [#1498]: https://github.com/gchq/CyberChef/pull/1498 [#1499]: https://github.com/gchq/CyberChef/pull/1499 [#1528]: https://github.com/gchq/CyberChef/pull/1528 [#661]: https://github.com/gchq/CyberChef/pull/661 [#493]: https://github.com/gchq/CyberChef/pull/493 [#592]: https://github.com/gchq/CyberChef/issues/592 [#1703]: https://github.com/gchq/CyberChef/issues/1703 [#1675]: https://github.com/gchq/CyberChef/issues/1675 [#1678]: https://github.com/gchq/CyberChef/issues/1678 [#1541]: https://github.com/gchq/CyberChef/issues/1541 [#1667]: https://github.com/gchq/CyberChef/issues/1667 [#1555]: https://github.com/gchq/CyberChef/issues/1555 [#1694]: https://github.com/gchq/CyberChef/issues/1694 [#1699]: https://github.com/gchq/CyberChef/issues/1699 [#1757]: https://github.com/gchq/CyberChef/issues/1757 [#1752]: https://github.com/gchq/CyberChef/issues/1752 [#1753]: https://github.com/gchq/CyberChef/issues/1753 [#1750]: https://github.com/gchq/CyberChef/issues/1750 [#1591]: https://github.com/gchq/CyberChef/issues/1591 [#654]: https://github.com/gchq/CyberChef/issues/654 [#1762]: https://github.com/gchq/CyberChef/issues/1762 [#1606]: https://github.com/gchq/CyberChef/issues/1606 [#1197]: https://github.com/gchq/CyberChef/issues/1197 [#933]: https://github.com/gchq/CyberChef/issues/933 [#1361]: https://github.com/gchq/CyberChef/issues/1361 [#1765]: https://github.com/gchq/CyberChef/issues/1765 [#1767]: https://github.com/gchq/CyberChef/issues/1767 [#1769]: https://github.com/gchq/CyberChef/issues/1769 [#1759]: https://github.com/gchq/CyberChef/issues/1759 [#1504]: https://github.com/gchq/CyberChef/issues/1504 [#512]: https://github.com/gchq/CyberChef/issues/512 [#1732]: https://github.com/gchq/CyberChef/issues/1732 [#1789]: https://github.com/gchq/CyberChef/issues/1789 [#1040]: https://github.com/gchq/CyberChef/pull/1040 [#2176]: https://github.com/gchq/CyberChef/pull/2176 [#2177]: https://github.com/gchq/CyberChef/pull/2177 [#2174]: https://github.com/gchq/CyberChef/pull/2174 [#2058]: https://github.com/gchq/CyberChef/pull/2058 [#1861]: https://github.com/gchq/CyberChef/pull/1861 [#2055]: https://github.com/gchq/CyberChef/pull/2055 [#2169]: https://github.com/gchq/CyberChef/pull/2169 [#2175]: https://github.com/gchq/CyberChef/pull/2175 [#2173]: https://github.com/gchq/CyberChef/pull/2173 [#2172]: https://github.com/gchq/CyberChef/pull/2172 [#2136]: https://github.com/gchq/CyberChef/pull/2136 [#2165]: https://github.com/gchq/CyberChef/pull/2165 [#2159]: https://github.com/gchq/CyberChef/pull/2159 [#2086]: https://github.com/gchq/CyberChef/pull/2086 [#2118]: https://github.com/gchq/CyberChef/pull/2118 [#2166]: https://github.com/gchq/CyberChef/pull/2166 [#2188]: https://github.com/gchq/CyberChef/pull/2188 [#2137]: https://github.com/gchq/CyberChef/pull/2137 [#1876]: https://github.com/gchq/CyberChef/pull/1876 [#2186]: https://github.com/gchq/CyberChef/pull/2186 [#1573]: https://github.com/gchq/CyberChef/pull/1573 [#2183]: https://github.com/gchq/CyberChef/pull/2183 [#2182]: https://github.com/gchq/CyberChef/pull/2182 [#2181]: https://github.com/gchq/CyberChef/pull/2181 ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In 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. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project 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. Project 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. ## Scope This 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. ## Enforcement Instances 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. Project 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. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: Dockerfile ================================================ ##################################### # Build the app to a static website # ##################################### # Modifier --platform=$BUILDPLATFORM limits the platform to "BUILDPLATFORM" during buildx multi-platform builds # This is because npm "chromedriver" package is not compatiable with all platforms # For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation FROM --platform=$BUILDPLATFORM node:18-alpine AS builder WORKDIR /app COPY package.json . COPY package-lock.json . # Install dependencies # --ignore-scripts prevents postinstall script (which runs grunt) as it depends on files other than package.json RUN npm ci --ignore-scripts # Copy files needed for postinstall and build COPY . . # npm postinstall runs grunt, which depends on files other than package.json RUN npm run postinstall # Build the app RUN npm run build ######################################### # Package static build files into nginx # ######################################### FROM nginx:stable-alpine AS cyberchef LABEL maintainer="GCHQ " COPY --from=builder /app/build/prod /usr/share/nginx/html/ ================================================ FILE: Gruntfile.js ================================================ "use strict"; const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; const glob = require("glob"); const path = require("path"); const nodeFlags = "--experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-warnings --no-deprecation"; /** * Grunt configuration for building the app in various formats. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ module.exports = function (grunt) { grunt.file.defaultEncoding = "utf8"; grunt.file.preserveBOM = false; // Tasks grunt.registerTask("dev", "A persistent task which creates a development build whenever source files are modified.", ["clean:dev", "clean:config", "exec:generateConfig", "concurrent:dev"]); grunt.registerTask("prod", "Creates a production-ready build. Use the --msg flag to add a compile message.", [ "eslint", "clean:prod", "clean:config", "exec:generateConfig", "findModules", "webpack:web", "copy:standalone", "zip:standalone", "clean:standalone", "exec:calcDownloadHash", "chmod" ]); grunt.registerTask("node", "Compiles CyberChef into a single NodeJS module.", [ "clean:node", "clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex" ]); grunt.registerTask("configTests", "A task which configures config files in preparation for tests to be run. Use `npm test` to run tests.", [ "clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex" ]); grunt.registerTask("testui", "A task which runs all the UI tests in the tests directory. The prod task must already have been run.", ["connect:prod", "exec:browserTests"]); grunt.registerTask("testnodeconsumer", "A task which checks whether consuming CJS and ESM apps work with the CyberChef build", ["exec:setupNodeConsumers", "exec:testCJSNodeConsumer", "exec:testESMNodeConsumer", "exec:teardownNodeConsumers"]); grunt.registerTask("default", "Lints the code base", ["eslint", "exec:repoSize"]); grunt.registerTask("lint", "eslint"); grunt.registerTask("findModules", "Finds all generated modules and updates the entry point list for Webpack", function(arg1, arg2) { const moduleEntryPoints = listEntryModules(); grunt.log.writeln(`Found ${Object.keys(moduleEntryPoints).length} modules.`); grunt.config.set("webpack.web.entry", Object.assign({ main: "./src/web/index.js" }, moduleEntryPoints)); }); // Load tasks provided by each plugin grunt.loadNpmTasks("grunt-eslint"); grunt.loadNpmTasks("grunt-webpack"); grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-contrib-copy"); grunt.loadNpmTasks("grunt-contrib-watch"); grunt.loadNpmTasks("grunt-chmod"); grunt.loadNpmTasks("grunt-exec"); grunt.loadNpmTasks("grunt-concurrent"); grunt.loadNpmTasks("grunt-contrib-connect"); grunt.loadNpmTasks("grunt-zip"); // Project configuration const compileYear = grunt.template.today("UTC:yyyy"), compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC", pkg = grunt.file.readJSON("package.json"), webpackConfig = require("./webpack.config.js"), BUILD_CONSTANTS = { COMPILE_YEAR: JSON.stringify(compileYear), COMPILE_TIME: JSON.stringify(compileTime), COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""), PKG_VERSION: JSON.stringify(pkg.version), }, moduleEntryPoints = listEntryModules(), nodeConsumerTestPath = "~/tmp-cyberchef", /** * Configuration for Webpack production build. Defined as a function so that it * can be recalculated when new modules are generated. */ webpackProdConf = () => { return { mode: "production", target: "web", entry: Object.assign({ main: "./src/web/index.js" }, moduleEntryPoints), output: { path: __dirname + "/build/prod", filename: chunkData => { return chunkData.chunk.name === "main" ? "assets/[name].js": "[name].js"; }, globalObject: "this" }, resolve: { alias: { "./config/modules/OpModules.mjs": "./config/modules/Default.mjs" } }, plugins: [ new webpack.DefinePlugin(BUILD_CONSTANTS), new HtmlWebpackPlugin({ filename: "index.html", template: "./src/web/html/index.html", chunks: ["main"], compileYear: compileYear, compileTime: compileTime, version: pkg.version, minify: { removeComments: true, collapseWhitespace: true, minifyJS: true, minifyCSS: true } }), new BundleAnalyzerPlugin({ analyzerMode: "static", reportFilename: "BundleAnalyzerReport.html", openAnalyzer: false }), ] }; }; /** * Generates an entry list for all the modules. */ function listEntryModules() { const entryModules = {}; glob.sync("./src/core/config/modules/*.mjs").forEach(file => { const basename = path.basename(file); if (basename !== "Default.mjs" && basename !== "OpModules.mjs") entryModules["modules/" + basename.split(".mjs")[0]] = path.resolve(file); }); return entryModules; } /** * Detects the correct delimiter to use to chain shell commands together * based on the current OS. * * @param {string[]} cmds * @returns {string} */ function chainCommands(cmds) { const win = process.platform === "win32"; if (!win) { return cmds.join(";"); } return cmds // && means that subsequent commands will not be executed if the // previous one fails. & would coninue on a fail .join("&&") // Windows does not support \n properly .replace(/\n/g, "\\n"); } grunt.initConfig({ clean: { dev: ["build/dev/*"], prod: ["build/prod/*"], node: ["build/node/*"], config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"], nodeConfig: ["src/node/index.mjs", "src/node/config/OperationConfig.json"], standalone: ["build/prod/CyberChef*.html"] }, eslint: { configs: ["*.{js,mjs}"], core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"], web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"], node: ["src/node/**/*.{js,mjs}"], tests: ["tests/**/*.{js,mjs}"], }, webpack: { options: webpackConfig, myConfig: webpackConfig, web: webpackProdConf(), }, "webpack-dev-server": { options: webpackConfig, start: { mode: "development", target: "web", entry: Object.assign({ main: "./src/web/index.js" }, moduleEntryPoints), resolve: { alias: { "./config/modules/OpModules.mjs": "./config/modules/Default.mjs" } }, devServer: { port: grunt.option("port") || 8080, client: { logging: "error", overlay: true }, hot: "only" }, plugins: [ new webpack.DefinePlugin(BUILD_CONSTANTS), new HtmlWebpackPlugin({ filename: "index.html", template: "./src/web/html/index.html", chunks: ["main"], compileYear: compileYear, compileTime: compileTime, version: pkg.version, }) ] } }, zip: { standalone: { cwd: "build/prod/", src: [ "build/prod/**/*", "!build/prod/index.html", "!build/prod/BundleAnalyzerReport.html", ], dest: `build/prod/CyberChef_v${pkg.version}.zip` } }, connect: { prod: { options: { port: grunt.option("port") || 8000, base: "build/prod/" } } }, copy: { ghPages: { options: { process: function (content, srcpath) { if (srcpath.indexOf("index.html") >= 0) { // Add Google Analytics code to index.html content = content.replace("", grunt.file.read("src/web/static/ga.html") + ""); // Add Structured Data for SEO content = content.replace("", ""); return grunt.template.process(content, srcpath); } else { return content; } }, noProcess: ["**", "!**/*.html"] }, files: [ { src: ["build/prod/index.html"], dest: "build/prod/index.html" } ] }, standalone: { options: { process: function (content, srcpath) { if (srcpath.indexOf("index.html") >= 0) { // Replace download link with version number content = content.replace(/]+>Download CyberChef.+?<\/a>/, `Version ${pkg.version}`); return grunt.template.process(content, srcpath); } else { return content; } }, noProcess: ["**", "!**/*.html"] }, files: [ { src: ["build/prod/index.html"], dest: `build/prod/CyberChef_v${pkg.version}.html` } ] } }, chmod: { build: { options: { mode: "755", }, src: ["build/**/*", "build/"] } }, watch: { config: { files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"], tasks: ["exec:generateNodeIndex", "exec:generateConfig"] } }, concurrent: { dev: ["watch:config", "webpack-dev-server:start"], options: { logConcurrentOutput: true } }, exec: { calcDownloadHash: { command: function () { switch (process.platform) { case "darwin": return chainCommands([ `shasum -a 256 build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`, `sed -i '' -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html` ]); default: return chainCommands([ `sha256sum build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`, `sed -i -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html` ]); } }, }, repoSize: { command: chainCommands([ "git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'", "du -hs | egrep -o '^[^\t]*' | xargs printf '%b\trepository size\n'" ]), stderr: false }, cleanGit: { command: "git gc --prune=now --aggressive" }, sitemap: { command: `node ${nodeFlags} src/web/static/sitemap.mjs > build/prod/sitemap.xml`, sync: true }, generateConfig: { command: chainCommands([ "echo '\n--- Regenerating config files. ---'", "echo [] > src/core/config/OperationConfig.json", `node ${nodeFlags} src/core/config/scripts/generateOpsIndex.mjs`, `node ${nodeFlags} src/core/config/scripts/generateConfig.mjs`, "echo '--- Config scripts finished. ---\n'" ]), sync: true }, generateNodeIndex: { command: chainCommands([ "echo '\n--- Regenerating node index ---'", `node ${nodeFlags} src/node/config/scripts/generateNodeIndex.mjs`, "echo '--- Node index generated. ---\n'" ]), sync: true }, browserTests: { command: "./node_modules/.bin/nightwatch --env prod" }, setupNodeConsumers: { command: chainCommands([ "echo '\n--- Testing node consumers ---'", "npm link", `mkdir ${nodeConsumerTestPath}`, `cp tests/node/consumers/* ${nodeConsumerTestPath}`, `cd ${nodeConsumerTestPath}`, "npm link cyberchef" ]), sync: true }, teardownNodeConsumers: { command: chainCommands([ `rm -rf ${nodeConsumerTestPath}`, "echo '\n--- Node consumer tests complete ---'" ]), }, testCJSNodeConsumer: { command: chainCommands([ `cd ${nodeConsumerTestPath}`, `node ${nodeFlags} cjs-consumer.js`, ]), stdout: false, }, testESMNodeConsumer: { command: chainCommands([ `cd ${nodeConsumerTestPath}`, `node ${nodeFlags} esm-consumer.mjs`, ]), stdout: false, }, fixCryptoApiImports: { command: function () { switch (process.platform) { case "darwin": 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'`; default: 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'`; } }, stdout: false }, fixSnackbarMarkup: { command: function () { switch (process.platform) { case "darwin": return `sed -i '' 's/
/
/g' ./node_modules/snackbarjs/src/snackbar.js`; default: return `sed -i 's/
/
/g' ./node_modules/snackbarjs/src/snackbar.js`; } }, stdout: false }, }, }); }; ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # CyberChef [![](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) [![npm](https://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef) [![](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/gchq/CyberChef/blob/master/LICENSE) [![Gitter](https://badges.gitter.im/gchq/CyberChef.svg)](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) #### *The Cyber Swiss Army Knife* CyberChef 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. The 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. ## Live demo CyberChef 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! Cryptographic operations in CyberChef should not be relied upon to provide security in any situation. No guarantee is offered for their correctness. [A live demo can be found here][1] - have fun! ## Running Locally with Docker **Prerequisites** - [Docker](https://www.docker.com/products/docker-desktop/) - Docker Desktop must be open and running on your machine #### Option 1: Build the Docker Image Yourself 1. Build the docker image ```bash docker build --tag cyberchef --ulimit nofile=10000 . ``` 2. Run the docker container ```bash docker run -it -p 8080:80 cyberchef ``` 3. Navigate to `http://localhost:8080` in your browser #### Option 2: Use the pre-built Docker Image If you prefer to skip the build process, you can use the pre-built image ```bash docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest ``` Just like before, navigate to `http://localhost:8080` in your browser. This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml) ## How it works There are four main areas in CyberChef: 1. The **input** box in the top right, where you can paste, type or drag the text or file you want to operate on. 2. The **output** box in the bottom right, where the outcome of your processing will be displayed. 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. 4. The **recipe** area in the middle, where you can drag the operations that you want to use and specify arguments and options. You can use as many operations as you like in simple or complex ways. Some examples are as follows: - [Decode a Base64-encoded string][2] - [Convert a date and time to a different time zone][3] - [Parse a Teredo IPv6 address][4] - [Convert data from a hexdump, then decompress][5] - [Decrypt and disassemble shellcode][6] - [Display multiple timestamps as full dates][7] - [Carry out different operations on data of different types][8] - [Use parts of the input as arguments to operations][9] - [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10] - [Automagically detect several layers of nested encoding][12] ## Features - Drag and drop - Operations can be dragged in and out of the recipe list, or reorganised. - Files up to 2GB can be dragged over the input box to load them directly into the browser. - Auto Bake - Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately. - This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance). - Automated encoding detection - 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. - Breakpoints - You can set breakpoints on any operation in your recipe to pause execution before running it. - You can also step through the recipe one operation at a time to see what the data looks like at each stage. - Save and load recipes - 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. - You can also copy the URL, which includes your recipe and input, to easily share it with others. - Search - 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. - Highlighting - 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]). - Save to file and load from file - 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. - CyberChef is entirely client-side - 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. - 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. ## Deep linking By manipulating CyberChef's URL hash, you can change the initial settings with which the page opens. The format is `https://gchq.github.io/CyberChef/#recipe=Operation()&input=...` Supported arguments are `recipe`, `input` (encoded in Base64), and `theme`. ## Browser support CyberChef is built to support - Google Chrome 50+ - Mozilla Firefox 38+ ## Node.js support CyberChef 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) ## Contributing Contributing 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. An 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). - Push your changes to your fork. - 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. ## Licencing CyberChef 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/). [1]: https://gchq.github.io/CyberChef [2]: https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1 [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 [4]: https://gchq.github.io/CyberChef/#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy [5]: https://gchq.github.io/CyberChef/#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw [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 [7]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA [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 [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 [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 [11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4 [12]: https://gchq.github.io/CyberChef/#recipe=Magic(3,false,false)&input=V1VhZ3dzaWFlNm1QOGdOdENDTFVGcENwQ0IyNlJtQkRvREQ4UGFjZEFtekF6QlZqa0syUXN0RlhhS2hwQzZpVVM3UkhxWHJKdEZpc29SU2dvSjR3aGptMWFybTg2NHFhTnE0UmNmVW1MSHJjc0FhWmM1VFhDWWlmTmRnUzgzZ0RlZWpHWDQ2Z2FpTXl1QlY2RXNrSHQxc2NnSjg4eDJ0TlNvdFFEd2JHWTFtbUNvYjJBUkdGdkNLWU5xaU45aXBNcTFaVTFtZ2tkYk51R2NiNzZhUnRZV2hDR1VjOGc5M1VKdWRoYjhodHNoZVpud1RwZ3FoeDgzU1ZKU1pYTVhVakpUMnptcEM3dVhXdHVtcW9rYmRTaTg4WXRrV0RBYzFUb291aDJvSDRENGRkbU5LSldVRHBNd21uZ1VtSzE0eHdtb21jY1BRRTloTTE3MkFQblNxd3hkS1ExNzJSa2NBc3lzbm1qNWdHdFJtVk5OaDJzMzU5d3I2bVMyUVJQ ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions CyberChef is supported on a best endeavours basis. Patches will be applied to the latest version rather than retroactively to older versions. To ensure you are using the most secure version of CyberChef, please make sure you have the [latest release](https://github.com/gchq/CyberChef/releases/latest). The official [live demo](https://gchq.github.io/CyberChef/) is always up to date. ## Reporting a Vulnerability In most scenarios, the most appropriate way to report a vulnerability is to [raise a new issue](https://github.com/gchq/CyberChef/issues/new/choose) describing the problem in as much detail as possible, ideally with examples. This will obviously be public. If you feel that the vulnerability is significant enough to warrant a private disclosure, please email [oss@gchq.gov.uk](mailto:oss@gchq.gov.uk) and [n1474335@gmail.com](mailto:n1474335@gmail.com). Disclosures of vulnerabilities in CyberChef are always welcomed. Whilst we aim to write clean and secure code free from bugs, we recognise that this is an open source project written by analysts in their spare time, relying on dozens of open source libraries that are modified and updated on a regular basis. We hope that the community will continue to support us as we endeavour to maintain and develop this tool together. ================================================ FILE: babel.config.js ================================================ module.exports = function(api) { api.cache.forever(); return { "presets": [ ["@babel/preset-env", { "modules": false, "useBuiltIns": "entry", "corejs": 3 }] ], "plugins": [ "@babel/plugin-syntax-import-assertions", [ "@babel/plugin-transform-runtime", { "regenerator": true } ] ] }; }; ================================================ FILE: eslint.config.mjs ================================================ import babelParser from "@babel/eslint-parser"; import jsdoc from "eslint-plugin-jsdoc"; import js from "@eslint/js"; import globals from "globals"; export default [ js.configs.recommended, { languageOptions: { ecmaVersion: 2022, parser: babelParser, parserOptions: { ecmaVersion: 2022, ecmaFeatures: { impliedStrict: true }, sourceType: "module", allowImportExportEverywhere: true }, globals: { ...globals.browser, ...globals.node, ...globals.es6, "$": false, "jQuery": false, "log": false, "app": false, "COMPILE_TIME": false, "COMPILE_MSG": false, "PKG_VERSION": false }, }, ignores: ["src/core/vendor/**"], plugins: { jsdoc }, rules: { // enable additional rules "no-eval": "error", "no-implied-eval": "error", "dot-notation": "error", "eqeqeq": ["error", "smart"], "no-caller": "error", "no-extra-bind": "error", "no-unused-expressions": "error", "no-useless-call": "error", "no-useless-return": "error", "radix": "warn", // modify rules from base configurations "no-unused-vars": ["error", { "args": "none", "vars": "all", "caughtErrors": "none" }], "no-empty": ["error", { "allowEmptyCatch": true }], // disable rules from base configurations "no-control-regex": "off", "require-atomic-updates": "off", "no-async-promise-executor": "off", // stylistic conventions "brace-style": ["error", "1tbs"], "space-before-blocks": ["error", "always"], "block-spacing": "error", "array-bracket-spacing": "error", "comma-spacing": "error", "spaced-comment": ["error", "always", { "exceptions": ["/"] }], "comma-style": "error", "computed-property-spacing": "error", "no-trailing-spaces": "warn", "eol-last": "error", "func-call-spacing": "error", "key-spacing": ["warn", { "mode": "minimum" }], "indent": ["error", 4, { "ignoreComments": true, "ArrayExpression": "first", "SwitchCase": 1 }], "linebreak-style": ["error", "unix"], "quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }], "camelcase": ["error", { "properties": "always" }], "semi": ["error", "always"], "unicode-bom": "error", "jsdoc/require-jsdoc": ["error", { "require": { "FunctionDeclaration": true, "MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": false } }], "keyword-spacing": ["error", { "before": true, "after": true }], "no-multiple-empty-lines": ["warn", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], "no-whitespace-before-property": "error", "operator-linebreak": ["error", "after"], "space-in-parens": "error", "no-var": "error", "prefer-const": "error", "no-console": "error" }, }, // File-pattern specific overrides { files: ["tests/**/*"], rules: { "no-unused-expressions": "off", "no-console": "off" } }, ]; ================================================ FILE: nightwatch.json ================================================ { "src_folders": ["tests/browser"], "exclude": ["tests/browser/browserUtils.js"], "output_folder": "tests/browser/output", "test_settings": { "default": { "launch_url": "http://localhost:8080", "webdriver": { "start_process": true, "server_path": "./node_modules/.bin/chromedriver", "port": 9515, "log_path": "tests/browser/output" }, "desiredCapabilities": { "browserName": "chrome" }, "enable_fail_fast": true }, "dev": { "launch_url": "http://localhost:8080" }, "prod": { "launch_url": "http://localhost:8000/index.html" } } } ================================================ FILE: package.json ================================================ { "name": "cyberchef", "version": "10.22.1", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", "copyright": "Crown copyright 2016", "license": "Apache-2.0", "keywords": [ "cipher", "cypher", "encode", "decode", "encrypt", "decrypt", "base64", "xor", "charset", "hex", "encoding", "format", "cybersecurity", "data manipulation", "data analysis" ], "repository": { "type": "git", "url": "https://github.com/gchq/CyberChef/" }, "main": "src/node/wrapper.js", "exports": { "import": "./src/node/index.mjs", "require": "./src/node/wrapper.js" }, "bugs": "https://github.com/gchq/CyberChef/issues", "browserslist": [ "Chrome >= 50", "Firefox >= 38", "node >= 16" ], "devDependencies": { "@babel/eslint-parser": "^7.28.6", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-transform-runtime": "^7.29.0", "@babel/preset-env": "^7.29.2", "@babel/runtime": "^7.28.6", "@codemirror/commands": "^6.10.3", "@codemirror/language": "^6.12.2", "@codemirror/search": "^6.6.0", "@codemirror/state": "^6.5.4", "@codemirror/view": "^6.39.17", "autoprefixer": "^10.4.27", "babel-loader": "^10.1.1", "base64-loader": "^1.0.0", "chromedriver": "^130.0.4", "cli-progress": "^3.12.0", "colors": "^1.4.0", "compression-webpack-plugin": "^11.1.0", "copy-webpack-plugin": "^13.0.1", "core-js": "^3.48.0", "cspell": "^8.19.4", "css-loader": "7.1.4", "eslint": "^9.39.4", "eslint-plugin-jsdoc": "^50.8.0", "globals": "^15.15.0", "grunt": "^1.6.1", "grunt-chmod": "~1.1.1", "grunt-concurrent": "^3.0.0", "grunt-contrib-clean": "~2.0.1", "grunt-contrib-connect": "^5.0.1", "grunt-contrib-copy": "~1.0.0", "grunt-contrib-watch": "^1.1.0", "grunt-eslint": "^25.0.0", "grunt-exec": "~3.0.0", "grunt-webpack": "^6.0.0", "grunt-zip": "^1.0.0", "html-webpack-plugin": "^5.6.6", "imports-loader": "^5.0.0", "mini-css-extract-plugin": "2.10.1", "modify-source-webpack-plugin": "^4.1.0", "nightwatch": "^3.15.0", "postcss": "^8.5.8", "postcss-css-variables": "^0.19.0", "postcss-import": "^16.1.1", "postcss-loader": "^8.2.1", "prompt": "^1.3.0", "sitemap": "^8.0.3", "terser": "^5.46.1", "webpack": "^5.105.4", "webpack-bundle-analyzer": "^4.10.2", "webpack-dev-server": "5.0.4", "webpack-node-externals": "^3.0.0", "worker-loader": "^3.0.8" }, "dependencies": { "@alexaltea/capstone-js": "^3.0.5", "@astronautlabs/amf": "^0.0.6", "@blu3r4y/lzma": "^2.3.3", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "@xmldom/xmldom": "^0.8.11", "argon2-browser": "^1.18.0", "arrive": "^2.5.2", "assert": "^2.1.0", "avsc": "^5.7.9", "bcryptjs": "^2.4.3", "bignumber.js": "^9.3.1", "blakejs": "^1.2.1", "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", "bootstrap-material-design": "^4.1.3", "browserify-zlib": "^0.2.0", "bson": "^4.7.2", "buffer": "^6.0.3", "cbor": "9.0.2", "chi-squared": "^1.1.0", "codepage": "^1.15.0", "crypto-api": "^0.8.5", "crypto-browserify": "^3.12.1", "crypto-js": "^4.2.0", "ctph.js": "0.0.5", "d3": "7.9.0", "d3-hexbin": "^0.2.2", "diff": "^5.2.2", "dompurify": "^3.3.3", "es6-promisify": "^7.0.0", "escodegen": "^2.1.0", "esprima": "^4.0.1", "events": "^3.3.0", "exif-parser": "^0.1.12", "fernet": "^0.3.3", "file-saver": "^2.0.5", "flat": "^6.0.1", "geodesy": "1.1.3", "handlebars": "^4.7.8", "hash-wasm": "^4.12.0", "highlight.js": "^11.11.1", "ieee754": "^1.2.1", "jimp": "^1.6.0", "jq-web": "^0.5.1", "jquery": "3.7.1", "js-sha3": "^0.9.3", "jsesc": "^3.1.0", "json5": "^2.2.3", "jsonata": "^2.1.0", "jsonpath-plus": "^10.4.0", "jsonwebtoken": "9.0.3", "jsqr": "^1.4.0", "jsrsasign": "^11.1.1", "kbpgp": "^2.1.17", "libbzip2-wasm": "0.0.4", "libyara-wasm": "^1.2.1", "lodash": "^4.17.23", "loglevel": "^1.9.2", "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.5.0", "lz4js": "^0.2.0", "markdown-it": "^14.1.1", "moment": "^2.30.1", "moment-timezone": "^0.6.1", "ngeohash": "^0.6.3", "node-forge": "^1.3.3", "node-md6": "^0.1.0", "nodom": "^2.4.0", "notepack.io": "^3.0.1", "ntlm": "^0.1.3", "nwmatcher": "^1.4.4", "otpauth": "9.3.6", "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", "protobufjs": "^7.5.4", "qr-image": "^3.2.0", "reflect-metadata": "^0.2.2", "rison": "^0.1.1", "scryptsy": "^2.1.0", "snackbarjs": "^1.1.0", "sortablejs": "^1.15.7", "split.js": "^1.6.5", "sql-formatter": "^15.6.12", "ssdeep.js": "0.0.3", "stream-browserify": "^3.0.0", "tesseract.js": "^6.0.1", "ua-parser-js": "^1.0.41", "unorm": "^1.6.0", "url": "^0.11.4", "utf8": "^3.0.0", "uuid": "^13.0.0", "vkbeautify": "^0.99.3", "xpath": "0.0.34", "xregexp": "^5.1.2", "zlibjs": "^0.3.1" }, "scripts": { "start": "npx grunt dev", "build": "npx grunt prod", "node": "npx grunt node", "repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs", "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", "testnodeconsumer": "npx grunt testnodeconsumer", "testui": "npx grunt testui", "testuidev": "npx nightwatch --env=dev", "lint": "npx grunt lint", "lint:grammar": "cspell ./src", "postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup", "newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs", "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'\"", "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\"", "getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'", "setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048" } } ================================================ FILE: postcss.config.js ================================================ module.exports = { plugins: [ require("postcss-import"), require("autoprefixer"), require("postcss-css-variables")({ preserve: true }), ] }; ================================================ FILE: src/core/Chef.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Dish from "./Dish.mjs"; import Recipe from "./Recipe.mjs"; import log from "loglevel"; import { isWorkerEnvironment } from "./Utils.mjs"; /** * The main controller for CyberChef. */ class Chef { /** * Chef constructor */ constructor() { this.dish = new Dish(); } /** * Runs the recipe over the input. * * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer * @param {Object[]} recipeConfig - The recipe configuration object * @param {Object} [options={}] - The options object storing various user choices * @param {string} [options.returnType] - What type to return the result as * * @returns {Object} response * @returns {string} response.result - The output of the recipe * @returns {string} response.type - The data type of the result * @returns {number} response.progress - The position that we have got to in the recipe * @returns {number} response.duration - The number of ms it took to execute the recipe * @returns {number} response.error - The error object thrown by a failed operation (false if no error) */ async bake(input, recipeConfig, options={}) { log.debug("Chef baking"); const startTime = Date.now(), recipe = new Recipe(recipeConfig), containsFc = recipe.containsFlowControl(); let error = false, progress = 0; if (containsFc && isWorkerEnvironment()) self.setOption("attemptHighlight", false); // Load data const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING; this.dish.set(input, type); try { progress = await recipe.execute(this.dish, progress); } catch (err) { log.error(err); error = { displayStr: err.displayStr, }; progress = err.progress; } // Create a raw version of the dish, unpresented const rawDish = this.dish.clone(); // Present the raw result await recipe.present(this.dish); const returnType = this.dish.type === Dish.HTML ? Dish.HTML : options?.returnType ? options.returnType : Dish.ARRAY_BUFFER; return { dish: rawDish, result: await this.dish.get(returnType), type: Dish.enumLookup(this.dish.type), progress: progress, duration: Date.now() - startTime, error: error }; } /** * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs, * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a * minute, we run a silent bake which will force the browser to load and cache all the relevant * JavaScript code needed to do a real bake. * * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a * long time and the browser has swapped out all its memory. * * The output will not be modified (hence "silent" bake). * * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load * the recipe, ingredients and dish. * * @param {Object[]} recipeConfig - The recipe configuration object * @returns {number} The time it took to run the silent bake in milliseconds. */ silentBake(recipeConfig) { log.debug("Running silent bake"); const startTime = Date.now(), recipe = new Recipe(recipeConfig), dish = new Dish(); try { recipe.execute(dish); } catch (err) { // Suppress all errors } return Date.now() - startTime; } /** * Calculates highlight offsets if possible. * * @param {Object[]} recipeConfig * @param {string} direction * @param {Object} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. * @returns {Object} */ async calculateHighlights(recipeConfig, direction, pos) { const recipe = new Recipe(recipeConfig); const highlights = await recipe.generateHighlightList(); if (!highlights) return false; for (let i = 0; i < highlights.length; i++) { // Remove multiple highlights before processing again pos = [pos[0]]; const func = direction === "forward" ? highlights[i].f : highlights[i].b; if (typeof func == "function") { try { pos = func(pos, highlights[i].args); } catch (err) { // Throw away highlighting errors pos = []; } } } return { pos: pos, direction: direction }; } /** * Translates the dish to a specified type and returns it. * * @param {Dish} dish * @param {string} type * @returns {Dish} */ async getDishAs(dish, type) { const newDish = new Dish(dish); return await newDish.get(type); } /** * Gets the title of a dish and returns it * * @param {Dish} dish * @param {number} [maxLength=100] * @returns {string} */ async getDishTitle(dish, maxLength=100) { const newDish = new Dish(dish); return await newDish.getTitle(maxLength); } } export default Chef; ================================================ FILE: src/core/ChefWorker.js ================================================ /** * Web Worker to handle communications between the front-end and the core. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Chef from "./Chef.mjs"; import OperationConfig from "./config/OperationConfig.json" assert {type: "json"}; import OpModules from "./config/modules/OpModules.mjs"; import loglevelMessagePrefix from "loglevel-message-prefix"; // Set up Chef instance self.chef = new Chef(); self.OpModules = OpModules; self.OperationConfig = OperationConfig; self.inputNum = -1; // Tell the app that the worker has loaded and is ready to operate self.postMessage({ action: "workerLoaded", data: {} }); /** * Respond to message from parent thread. * * inputNum is optional and only used for baking multiple inputs. * Defaults to -1 when one isn't sent with the bake message. * * Messages should have the following format: * { * action: "bake" | "silentBake", * data: { * input: {string}, * recipeConfig: {[Object]}, * options: {Object}, * progress: {number}, * step: {boolean}, * [inputNum=-1]: {number} * } * } */ self.addEventListener("message", function(e) { // Handle message const r = e.data; log.debug(`Receiving command '${r.action}'`); switch (r.action) { case "bake": bake(r.data); break; case "silentBake": silentBake(r.data); break; case "getDishAs": getDishAs(r.data); break; case "getDishTitle": getDishTitle(r.data); break; case "docURL": // Used to set the URL of the current document so that scripts can be // imported into an inline worker. self.docURL = r.data; break; case "highlight": calculateHighlights( r.data.recipeConfig, r.data.direction, r.data.pos ); break; case "setLogLevel": log.setLevel(r.data, false); break; case "setLogPrefix": loglevelMessagePrefix(log, { prefixes: [], staticPrefixes: [r.data] }); break; default: break; } }); /** * Baking handler * * @param {Object} data */ async function bake(data) { // Ensure the relevant modules are loaded self.loadRequiredModules(data.recipeConfig); try { self.inputNum = data.inputNum === undefined ? -1 : data.inputNum; const response = await self.chef.bake( data.input, // The user's input data.recipeConfig, // The configuration of the recipe data.options // Options set by the user ); const transferable = (response.dish.value instanceof ArrayBuffer) ? [response.dish.value] : undefined; self.postMessage({ action: "bakeComplete", data: Object.assign(response, { id: data.id, inputNum: data.inputNum, bakeId: data.bakeId }) }, transferable); } catch (err) { self.postMessage({ action: "bakeError", data: { error: err.message || err, id: data.id, inputNum: data.inputNum } }); } self.inputNum = -1; } /** * Silent baking handler */ function silentBake(data) { const duration = self.chef.silentBake(data.recipeConfig); self.postMessage({ action: "silentBakeComplete", data: duration }); } /** * Translates the dish to a given type. */ async function getDishAs(data) { const value = await self.chef.getDishAs(data.dish, data.type); const transferable = (data.type === "ArrayBuffer") ? [value] : undefined; self.postMessage({ action: "dishReturned", data: { value: value, id: data.id } }, transferable); } /** * Gets the dish title * * @param {object} data * @param {Dish} data.dish * @param {number} data.maxLength * @param {number} data.id */ async function getDishTitle(data) { const title = await self.chef.getDishTitle(data.dish, data.maxLength); self.postMessage({ action: "dishReturned", data: { value: title, id: data.id } }); } /** * Calculates highlight offsets if possible. * * @param {Object[]} recipeConfig * @param {string} direction * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. */ async function calculateHighlights(recipeConfig, direction, pos) { pos = await self.chef.calculateHighlights(recipeConfig, direction, pos); self.postMessage({ action: "highlightsCalculated", data: pos }); } /** * Checks that all required modules are loaded and loads them if not. * * @param {Object} recipeConfig */ self.loadRequiredModules = function(recipeConfig) { recipeConfig.forEach(op => { const module = self.OperationConfig[op.op].module; if (!(module in OpModules)) { log.info(`Loading ${module} module`); self.sendStatusMessage(`Loading ${module} module`); self.importScripts(`${self.docURL}/modules/${module}.js`); // lgtm [js/client-side-unvalidated-url-redirection] self.sendStatusMessage(""); } }); }; /** * Send status update to the app. * * @param {string} msg */ self.sendStatusMessage = function(msg) { self.postMessage({ action: "statusMessage", data: { message: msg, inputNum: self.inputNum } }); }; /** * Send progress update to the app. * * @param {number} progress * @param {number} total */ self.sendProgressMessage = function(progress, total) { self.postMessage({ action: "progressMessage", data: { progress: progress, total: total, inputNum: self.inputNum } }); }; /** * Send an option value update to the app. * * @param {string} option * @param {*} value */ self.setOption = function(option, value) { self.postMessage({ action: "optionUpdate", data: { option: option, value: value } }); }; /** * Send register values back to the app. * * @param {number} opIndex * @param {number} numPrevRegisters * @param {string[]} registers */ self.setRegisters = function(opIndex, numPrevRegisters, registers) { self.postMessage({ action: "setRegisters", data: { opIndex: opIndex, numPrevRegisters: numPrevRegisters, registers: registers } }); }; ================================================ FILE: src/core/Dish.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Utils, { isNodeEnvironment } from "./Utils.mjs"; import DishError from "./errors/DishError.mjs"; import BigNumber from "bignumber.js"; import { detectFileType } from "./lib/FileType.mjs"; import log from "loglevel"; import DishByteArray from "./dishTypes/DishByteArray.mjs"; import DishBigNumber from "./dishTypes/DishBigNumber.mjs"; import DishFile from "./dishTypes/DishFile.mjs"; import DishHTML from "./dishTypes/DishHTML.mjs"; import DishJSON from "./dishTypes/DishJSON.mjs"; import DishListFile from "./dishTypes/DishListFile.mjs"; import DishNumber from "./dishTypes/DishNumber.mjs"; import DishString from "./dishTypes/DishString.mjs"; /** * The data being operated on by each operation. */ class Dish { /** * Dish constructor * * @param {Dish || *} [dishOrInput=null] - A dish to clone OR an object * literal to make into a dish * @param {Enum} [type=null] (optional) - A type to accompany object * literal input */ constructor(dishOrInput=null, type = null) { this.value = new ArrayBuffer(0); this.type = Dish.ARRAY_BUFFER; // Case: dishOrInput is dish object if (dishOrInput && Object.prototype.hasOwnProperty.call(dishOrInput, "value") && Object.prototype.hasOwnProperty.call(dishOrInput, "type")) { this.set(dishOrInput.value, dishOrInput.type); // input and type defined separately } else if (dishOrInput && type !== null) { this.set(dishOrInput, type); // No type declared, so infer it. } else if (dishOrInput) { const inferredType = Dish.typeEnum(dishOrInput.constructor.name); this.set(dishOrInput, inferredType); } } /** * Returns the data type enum for the given type string. * * @param {string} typeStr - The name of the data type. * @returns {number} The data type enum value. */ static typeEnum(typeStr) { switch (typeStr.toLowerCase()) { case "bytearray": case "byte array": return Dish.BYTE_ARRAY; case "string": return Dish.STRING; case "number": return Dish.NUMBER; case "html": return Dish.HTML; case "arraybuffer": case "array buffer": return Dish.ARRAY_BUFFER; case "bignumber": case "big number": return Dish.BIG_NUMBER; case "json": case "object": // object constructor name. To allow JSON input in node. return Dish.JSON; case "file": return Dish.FILE; case "list": return Dish.LIST_FILE; default: throw new DishError("Invalid data type string. No matching enum."); } } /** * Returns the data type string for the given type enum. * * @param {number} typeEnum - The enum value of the data type. * @returns {string} The data type as a string. */ static enumLookup(typeEnum) { switch (typeEnum) { case Dish.BYTE_ARRAY: return "byteArray"; case Dish.STRING: return "string"; case Dish.NUMBER: return "number"; case Dish.HTML: return "html"; case Dish.ARRAY_BUFFER: return "ArrayBuffer"; case Dish.BIG_NUMBER: return "BigNumber"; case Dish.JSON: return "JSON"; case Dish.FILE: return "File"; case Dish.LIST_FILE: return "List"; default: throw new DishError("Invalid data type enum. No matching type."); } } /** * Returns the value of the data in the type format specified. * * If running in a browser, get is asynchronous. * * @param {number} type - The data type of value, see Dish enums. * @returns {* | Promise} - (Browser) A promise | (Node) value of dish in given type */ get(type) { if (typeof type === "string") { type = Dish.typeEnum(type); } if (this.type !== type) { // Node environment => _translate is sync if (isNodeEnvironment()) { this._translate(type); return this.value; // Browser environment => _translate is async } else { return new Promise((resolve, reject) => { this._translate(type) .then(() => { resolve(this.value); }) .catch(reject); }); } } return this.value; } /** * Sets the data value and type and then validates them. * * @param {*} value * - The value of the input data. * @param {number} type * - The data type of value, see Dish enums. */ set(value, type) { if (typeof type === "string") { type = Dish.typeEnum(type); } log.debug("Dish type: " + Dish.enumLookup(type)); this.value = value; this.type = type; if (!this.valid()) { const sample = Utils.truncate(JSON.stringify(this.value), 25); throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`); } } /** * Returns the Dish as the given type, without mutating the original dish. * * If running in a browser, get is asynchronous. * * @Node * * @param {number} type - The data type of value, see Dish enums. * @returns {Dish | Promise} - (Browser) A promise | (Node) value of dish in given type */ presentAs(type) { const clone = this.clone(); return clone.get(type); } /** * Detects the MIME type of the current dish * @returns {string} */ detectDishType() { const data = new Uint8Array(this.value.slice(0, 2048)), types = detectFileType(data); if (!types.length || !types[0].mime || !(types[0].mime === "text/plain")) { return null; } else { return types[0].mime; } } /** * Returns the title of the data up to the specified length * * @param {number} maxLength - The maximum title length * @returns {string} */ async getTitle(maxLength) { let title = ""; let cloned; switch (this.type) { case Dish.FILE: title = this.value.name; break; case Dish.LIST_FILE: title = `${this.value.length} file(s)`; break; case Dish.JSON: title = "application/json"; break; case Dish.NUMBER: case Dish.BIG_NUMBER: title = this.value.toString(); break; case Dish.ARRAY_BUFFER: case Dish.BYTE_ARRAY: title = this.detectDishType(); if (title !== null) break; // fall through if no mime type was detected default: try { cloned = this.clone(); cloned.value = cloned.value.slice(0, 256); title = await cloned.get(Dish.STRING); } catch (err) { log.error(`${Dish.enumLookup(this.type)} cannot be sliced. ${err}`); } } return title.slice(0, maxLength); } /** * Validates that the value is the type that has been specified. * May have to disable parts of BYTE_ARRAY validation if it effects performance. * * @returns {boolean} Whether the data is valid or not. */ valid() { switch (this.type) { case Dish.BYTE_ARRAY: if (!(this.value instanceof Uint8Array) && !(this.value instanceof Array)) { return false; } // Check that every value is a number between 0 - 255 for (let i = 0; i < this.value.length; i++) { if (typeof this.value[i] !== "number" || this.value[i] < 0 || this.value[i] > 255) { return false; } } return true; case Dish.STRING: case Dish.HTML: return typeof this.value === "string"; case Dish.NUMBER: return typeof this.value === "number"; case Dish.ARRAY_BUFFER: return this.value instanceof ArrayBuffer; case Dish.BIG_NUMBER: if (BigNumber.isBigNumber(this.value)) return true; /* If a BigNumber is passed between WebWorkers it is serialised as a JSON object with a coefficient (c), exponent (e) and sign (s). We detect this and reinitialise it as a BigNumber object. */ if (Object.keys(this.value).sort().equals(["c", "e", "s"])) { const temp = new BigNumber(); temp.c = this.value.c; temp.e = this.value.e; temp.s = this.value.s; this.value = temp; return true; } return false; case Dish.JSON: // All values can be serialised in some manner, so we return true in all cases return true; case Dish.FILE: return this.value instanceof File; case Dish.LIST_FILE: return this.value instanceof Array && this.value.reduce((acc, curr) => acc && curr instanceof File, true); default: return false; } } /** * Determines how much space the Dish takes up. * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish, * we measure how many bytes are taken up when the number is written as a string. * * @returns {number} */ get size() { switch (this.type) { case Dish.BYTE_ARRAY: case Dish.STRING: case Dish.HTML: return this.value.length; case Dish.NUMBER: case Dish.BIG_NUMBER: return this.value.toString().length; case Dish.ARRAY_BUFFER: return this.value.byteLength; case Dish.JSON: return JSON.stringify(this.value).length; case Dish.FILE: return this.value.size; case Dish.LIST_FILE: return this.value.reduce((acc, curr) => acc + curr.size, 0); default: return -1; } } /** * Returns a deep clone of the current Dish. * * @returns {Dish} */ clone() { const newDish = new Dish(); switch (this.type) { case Dish.STRING: case Dish.HTML: case Dish.NUMBER: case Dish.BIG_NUMBER: // These data types are immutable so it is acceptable to copy them by reference newDish.set( this.value, this.type ); break; case Dish.BYTE_ARRAY: case Dish.JSON: // These data types are mutable so they need to be copied by value newDish.set( JSON.parse(JSON.stringify(this.value)), this.type ); break; case Dish.ARRAY_BUFFER: // Slicing an ArrayBuffer returns a new ArrayBuffer with a copy its contents newDish.set( this.value.slice(0), this.type ); break; case Dish.FILE: // A new file can be created by copying over all the values from the original newDish.set( new File([this.value], this.value.name, { "type": this.value.type, "lastModified": this.value.lastModified }), this.type ); break; case Dish.LIST_FILE: newDish.set( this.value.map(f => new File([f], f.name, { "type": f.type, "lastModified": f.lastModified }) ), this.type ); break; default: throw new DishError("Cannot clone Dish, unknown type"); } return newDish; } /** * Translates the data to the given type format. * * If running in the browser, _translate is asynchronous. * * @param {number} toType - The data type of value, see Dish enums. * @returns {Promise || undefined} */ _translate(toType) { log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); // Node environment => translate is sync if (isNodeEnvironment()) { this._toArrayBuffer(); this.type = Dish.ARRAY_BUFFER; this._fromArrayBuffer(toType); // Browser environment => translate is async } else { return new Promise((resolve, reject) => { this._toArrayBuffer() .then(() => this.type = Dish.ARRAY_BUFFER) .then(() => { this._fromArrayBuffer(toType); resolve(); }) .catch(reject); }); } } /** * Convert this.value to an ArrayBuffer * * If running in a browser, _toByteArray is asynchronous. * * @returns {Promise || undefined} */ _toArrayBuffer() { // Using 'bind' here to allow this.value to be mutated within translation functions const toByteArrayFuncs = { browser: { [Dish.STRING]: () => Promise.resolve(DishString.toArrayBuffer.bind(this)()), [Dish.NUMBER]: () => Promise.resolve(DishNumber.toArrayBuffer.bind(this)()), [Dish.HTML]: () => Promise.resolve(DishHTML.toArrayBuffer.bind(this)()), [Dish.ARRAY_BUFFER]: () => Promise.resolve(), [Dish.BIG_NUMBER]: () => Promise.resolve(DishBigNumber.toArrayBuffer.bind(this)()), [Dish.JSON]: () => Promise.resolve(DishJSON.toArrayBuffer.bind(this)()), [Dish.FILE]: () => DishFile.toArrayBuffer.bind(this)(), [Dish.LIST_FILE]: () => Promise.resolve(DishListFile.toArrayBuffer.bind(this)()), [Dish.BYTE_ARRAY]: () => Promise.resolve(DishByteArray.toArrayBuffer.bind(this)()), }, node: { [Dish.STRING]: () => DishString.toArrayBuffer.bind(this)(), [Dish.NUMBER]: () => DishNumber.toArrayBuffer.bind(this)(), [Dish.HTML]: () => DishHTML.toArrayBuffer.bind(this)(), [Dish.ARRAY_BUFFER]: () => {}, [Dish.BIG_NUMBER]: () => DishBigNumber.toArrayBuffer.bind(this)(), [Dish.JSON]: () => DishJSON.toArrayBuffer.bind(this)(), [Dish.FILE]: () => DishFile.toArrayBuffer.bind(this)(), [Dish.LIST_FILE]: () => DishListFile.toArrayBuffer.bind(this)(), [Dish.BYTE_ARRAY]: () => DishByteArray.toArrayBuffer.bind(this)(), } }; try { return toByteArrayFuncs[isNodeEnvironment() && "node" || "browser"][this.type](); } catch (err) { throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to ArrayBuffer: ${err}`); } } /** * Convert this.value to the given type from ArrayBuffer * * @param {number} toType - the Dish enum to convert to */ _fromArrayBuffer(toType) { // Using 'bind' here to allow this.value to be mutated within translation functions const toTypeFunctions = { [Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(), [Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(), [Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(), [Dish.ARRAY_BUFFER]: () => {}, [Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(), [Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(), [Dish.FILE]: () => DishFile.fromArrayBuffer.bind(this)(), [Dish.LIST_FILE]: () => DishListFile.fromArrayBuffer.bind(this)(), [Dish.BYTE_ARRAY]: () => DishByteArray.fromArrayBuffer.bind(this)(), }; try { toTypeFunctions[toType](); this.type = toType; } catch (err) { throw new DishError(`Error translating from ArrayBuffer to ${Dish.enumLookup(toType)}: ${err}`); } } } /** * Dish data type enum for byte arrays. * @readonly * @enum */ Dish.BYTE_ARRAY = 0; /** * Dish data type enum for strings. * @readonly * @enum */ Dish.STRING = 1; /** * Dish data type enum for numbers. * @readonly * @enum */ Dish.NUMBER = 2; /** * Dish data type enum for HTML. * @readonly * @enum */ Dish.HTML = 3; /** * Dish data type enum for ArrayBuffers. * @readonly * @enum */ Dish.ARRAY_BUFFER = 4; /** * Dish data type enum for BigNumbers. * @readonly * @enum */ Dish.BIG_NUMBER = 5; /** * Dish data type enum for JSON. * @readonly * @enum */ Dish.JSON = 6; /** * Dish data type enum for lists of files. * @readonly * @enum */ Dish.FILE = 7; /** * Dish data type enum for lists of files. * @readonly * @enum */ Dish.LIST_FILE = 8; export default Dish; ================================================ FILE: src/core/Ingredient.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Utils from "./Utils.mjs"; import {fromHex} from "./lib/Hex.mjs"; /** * The arguments to operations. */ class Ingredient { /** * Ingredient constructor * * @param {Object} ingredientConfig */ constructor(ingredientConfig) { this.name = ""; this.type = ""; this._value = null; this.disabled = false; this.hint = ""; this.rows = 0; this.toggleValues = []; this.target = null; this.defaultIndex = 0; this.maxLength = null; this.min = null; this.max = null; this.step = 1; if (ingredientConfig) { this._parseConfig(ingredientConfig); } } /** * Reads and parses the given config. * * @private * @param {Object} ingredientConfig */ _parseConfig(ingredientConfig) { this.name = ingredientConfig.name; this.type = ingredientConfig.type; this.defaultValue = ingredientConfig.value; this.disabled = !!ingredientConfig.disabled; this.hint = ingredientConfig.hint || false; this.rows = ingredientConfig.rows || false; this.toggleValues = ingredientConfig.toggleValues; this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null; this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0; this.maxLength = ingredientConfig.maxLength || null; this.min = ingredientConfig.min; this.max = ingredientConfig.max; this.step = ingredientConfig.step; } /** * Returns the value of the Ingredient as it should be displayed in a recipe config. * * @returns {*} */ get config() { return this._value; } /** * Sets the value of the Ingredient. * * @param {*} value */ set value(value) { this._value = Ingredient.prepare(value, this.type); } /** * Gets the value of the Ingredient. * * @returns {*} */ get value() { return this._value; } /** * Most values will be strings when they are entered. This function converts them to the correct * type. * * @param {*} data * @param {string} type - The name of the data type. */ static prepare(data, type) { let number; switch (type) { case "binaryString": case "binaryShortString": case "editableOption": case "editableOptionShort": return Utils.parseEscapedChars(data); case "byteArray": if (typeof data == "string") { data = data.replace(/\s+/g, ""); return fromHex(data); } else { return data; } case "number": if (data === null) return data; number = parseFloat(data); if (isNaN(number)) { const sample = Utils.truncate(data.toString(), 10); throw "Invalid ingredient value. Not a number: " + sample; } return number; default: return data; } } } export default Ingredient; ================================================ FILE: src/core/Operation.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Dish from "./Dish.mjs"; import Ingredient from "./Ingredient.mjs"; /** * The Operation specified by the user to be run. */ class Operation { /** * Operation constructor */ constructor() { // Private fields this._inputType = -1; this._outputType = -1; this._presentType = -1; this._breakpoint = false; this._disabled = false; this._flowControl = false; this._manualBake = false; this._ingList = []; // Public fields this.name = ""; this.module = ""; this.description = ""; this.infoURL = null; } /** * Interface for operation runner * * @param {*} input * @param {Object[]} args * @returns {*} */ run(input, args) { return input; } /** * Interface for forward highlighter * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return false; } /** * Interface for reverse highlighter * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return false; } /** * Method to be called when displaying the result of an operation in a human-readable * format. This allows operations to return usable data from their run() method and * only format them when this method is called. * * The default action is to return the data unchanged, but child classes can override * this behaviour. * * @param {*} data - The result of the run() function * @param {Object[]} args - The operation's arguments * @returns {*} - A human-readable version of the data */ present(data, args) { return data; } /** * Sets the input type as a Dish enum. * * @param {string} typeStr */ set inputType(typeStr) { this._inputType = Dish.typeEnum(typeStr); } /** * Gets the input type as a readable string. * * @returns {string} */ get inputType() { return Dish.enumLookup(this._inputType); } /** * Sets the output type as a Dish enum. * * @param {string} typeStr */ set outputType(typeStr) { this._outputType = Dish.typeEnum(typeStr); if (this._presentType < 0) this._presentType = this._outputType; } /** * Gets the output type as a readable string. * * @returns {string} */ get outputType() { return Dish.enumLookup(this._outputType); } /** * Sets the presentation type as a Dish enum. * * @param {string} typeStr */ set presentType(typeStr) { this._presentType = Dish.typeEnum(typeStr); } /** * Gets the presentation type as a readable string. * * @returns {string} */ get presentType() { return Dish.enumLookup(this._presentType); } /** * Sets the args for the current operation. * * @param {Object[]} conf */ set args(conf) { conf.forEach(arg => { const ingredient = new Ingredient(arg); this.addIngredient(ingredient); }); } /** * Gets the args for the current operation. * * @param {Object[]} conf */ get args() { return this._ingList.map(ing => { const conf = { name: ing.name, type: ing.type, value: ing.defaultValue }; if (ing.toggleValues) conf.toggleValues = ing.toggleValues; if (ing.hint) conf.hint = ing.hint; if (ing.rows) conf.rows = ing.rows; if (ing.disabled) conf.disabled = ing.disabled; if (ing.target) conf.target = ing.target; if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex; if (ing.maxLength) conf.maxLength = ing.maxLength; if (typeof ing.min === "number") conf.min = ing.min; if (typeof ing.max === "number") conf.max = ing.max; if (ing.step) conf.step = ing.step; return conf; }); } /** * Returns the value of the Operation as it should be displayed in a recipe config. * * @returns {Object} */ get config() { return { "op": this.name, "args": this._ingList.map(ing => ing.config) }; } /** * Adds a new Ingredient to this Operation. * * @param {Ingredient} ingredient */ addIngredient(ingredient) { this._ingList.push(ingredient); } /** * Set the Ingredient values for this Operation. * * @param {Object[]} ingValues */ set ingValues(ingValues) { ingValues.forEach((val, i) => { this._ingList[i].value = val; }); } /** * Get the Ingredient values for this Operation. * * @returns {Object[]} */ get ingValues() { return this._ingList.map(ing => ing.value); } /** * Set whether this Operation has a breakpoint. * * @param {boolean} value */ set breakpoint(value) { this._breakpoint = !!value; } /** * Returns true if this Operation has a breakpoint set. * * @returns {boolean} */ get breakpoint() { return this._breakpoint; } /** * Set whether this Operation is disabled. * * @param {boolean} value */ set disabled(value) { this._disabled = !!value; } /** * Returns true if this Operation is disabled. * * @returns {boolean} */ get disabled() { return this._disabled; } /** * Returns true if this Operation is a flow control. * * @returns {boolean} */ get flowControl() { return this._flowControl; } /** * Set whether this Operation is a flowcontrol op. * * @param {boolean} value */ set flowControl(value) { this._flowControl = !!value; } /** * Returns true if this Operation should not trigger AutoBake. * * @returns {boolean} */ get manualBake() { return this._manualBake; } /** * Set whether this Operation should trigger AutoBake. * * @param {boolean} value */ set manualBake(value) { this._manualBake = !!value; } } export default Operation; ================================================ FILE: src/core/Recipe.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import OperationConfig from "./config/OperationConfig.json" assert {type: "json"}; import OperationError from "./errors/OperationError.mjs"; import Operation from "./Operation.mjs"; import DishError from "./errors/DishError.mjs"; import log from "loglevel"; import { isWorkerEnvironment } from "./Utils.mjs"; // Cache container for modules let modules = null; /** * The Recipe controls a list of Operations and the Dish they operate on. */ class Recipe { /** * Recipe constructor * * @param {Object} recipeConfig */ constructor(recipeConfig) { this.opList = []; if (recipeConfig) { this._parseConfig(recipeConfig); } } /** * Reads and parses the given config. * * @private * @param {Object} recipeConfig */ _parseConfig(recipeConfig) { recipeConfig.forEach(c => { this.opList.push({ name: c.op, module: OperationConfig[c.op].module, ingValues: c.args, breakpoint: c.breakpoint, disabled: c.disabled || c.op === "Comment", }); }); } /** * Populate elements of opList with operation instances. * Dynamic import here removes top-level cyclic dependency issue. * * @private */ async _hydrateOpList() { if (!modules) { // Using Webpack Magic Comments to force the dynamic import to be included in the main chunk // https://webpack.js.org/api/module-methods/ modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules.mjs"); modules = modules.default; } this.opList = this.opList.map(o => { if (o instanceof Operation) { return o; } else { const op = new modules[o.module][o.name](); op.ingValues = o.ingValues; op.breakpoint = o.breakpoint; op.disabled = o.disabled; return op; } }); } /** * Returns the value of the Recipe as it should be displayed in a recipe config. * * @returns {Object[]} */ get config() { return this.opList.map(op => ({ op: op.name, args: op.ingValues, })); } /** * Adds a new Operation to this Recipe. * * @param {Operation} operation */ addOperation(operation) { this.opList.push(operation); } /** * Adds a list of Operations to this Recipe. * * @param {Operation[]} operations */ addOperations(operations) { operations.forEach(o => { if (o instanceof Operation) { this.opList.push(o); } else { this.opList.push({ name: o.name, module: o.module, ingValues: o.args, breakpoint: o.breakpoint, disabled: o.disabled, }); } }); } /** * Set a breakpoint on a specified Operation. * * @param {number} position - The index of the Operation * @param {boolean} value */ setBreakpoint(position, value) { try { this.opList[position].breakpoint = value; } catch (err) { // Ignore index error } } /** * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow * Control Fork operation. * * @param {number} pos */ removeBreaksUpTo(pos) { for (let i = 0; i < pos; i++) { this.opList[i].breakpoint = false; } } /** * Returns true if there is a Flow Control Operation in this Recipe. * * @returns {boolean} */ containsFlowControl() { return this.opList.reduce((acc, curr) => { return acc || curr.flowControl; }, false); } /** * Executes each operation in the recipe over the given Dish. * * @param {Dish} dish * @param {number} [startFrom=0] * - The index of the Operation to start executing from * @param {number} [forkState={}] * - If this is a forked recipe, the state of the recipe up to this point * @returns {number} * - The final progress through the recipe */ async execute(dish, startFrom=0, forkState={}) { let op, input, output, numJumps = 0, numRegisters = forkState.numRegisters || 0; if (startFrom === 0) this.lastRunOp = null; await this._hydrateOpList(); log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`); for (let i = startFrom; i < this.opList.length; i++) { op = this.opList[i]; log.debug(`[${i}] ${op.name} ${JSON.stringify(op.ingValues)}`); if (op.disabled) { log.debug("Operation is disabled, skipping"); continue; } if (op.breakpoint) { log.debug("Pausing at breakpoint"); return i; } try { input = await dish.get(op.inputType); log.debug(`Executing operation '${op.name}'`); if (isWorkerEnvironment()) { self.sendStatusMessage(`Baking... (${i+1}/${this.opList.length})`); self.sendProgressMessage(i + 1, this.opList.length); } if (op.flowControl) { // Package up the current state let state = { "progress": i, "dish": dish, "opList": this.opList, "numJumps": numJumps, "numRegisters": numRegisters, "forkOffset": forkState.forkOffset || 0 }; state = await op.run(state); i = state.progress; numJumps = state.numJumps; numRegisters = state.numRegisters; } else { output = await op.run(input, op.ingValues); dish.set(output, op.outputType); } this.lastRunOp = op; } catch (err) { log.error(err); // Return expected errors as output if (err instanceof OperationError || err?.type === "OperationError") { // Cannot rely on `err instanceof OperationError` here as extending // native types is not fully supported yet. dish.set(err.message, "string"); return i; } else if (err instanceof DishError || err?.type === "DishError") { dish.set(err.message, "string"); return i; } else { const e = typeof err == "string" ? { message: err } : err; e.progress = i; if (e.fileName) { e.displayStr = `${op.name} - ${e.name} in ${e.fileName} on line ` + `${e.lineNumber}.

Message: ${e.displayStr || e.message}`; } else { e.displayStr = `${op.name} - ${e.displayStr || e.message}`; } throw e; } } } log.debug("Recipe complete"); return this.opList.length; } /** * Present the results of the final operation. * * @param {Dish} dish */ async present(dish) { if (!this.lastRunOp) return; const output = await this.lastRunOp.present( await dish.get(this.lastRunOp.outputType), this.lastRunOp.ingValues ); dish.set(output, this.lastRunOp.presentType); } /** * Returns the recipe configuration in string format. * * @returns {string} */ toString() { return JSON.stringify(this.config); } /** * Creates a Recipe from a given configuration string. * * @param {string} recipeStr */ fromString(recipeStr) { const recipeConfig = JSON.parse(recipeStr); this._parseConfig(recipeConfig); } /** * Generates a list of all the highlight functions assigned to operations in the recipe, if the * entire recipe supports highlighting. * * @returns {Object[]} highlights * @returns {function} highlights[].f * @returns {function} highlights[].b * @returns {Object[]} highlights[].args */ async generateHighlightList() { await this._hydrateOpList(); const highlights = []; for (let i = 0; i < this.opList.length; i++) { const op = this.opList[i]; if (op.disabled) continue; // If any breakpoints are set, do not attempt to highlight if (op.breakpoint) return false; // If any of the operations do not support highlighting, fail immediately. if (op.highlight === false || op.highlight === undefined) return false; highlights.push({ f: op.highlight, b: op.highlightReverse, args: op.ingValues }); } return highlights; } /** * Determines whether the previous operation has a different presentation type to its normal output. * * @param {number} progress * @returns {boolean} */ lastOpPresented(progress) { if (progress < 1) return false; return this.opList[progress-1].presentType !== this.opList[progress-1].outputType; } } export default Recipe; ================================================ FILE: src/core/Utils.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ // loglevel import required for Node API import log from "loglevel"; import utf8 from "utf8"; import {fromBase64, toBase64} from "./lib/Base64.mjs"; import {fromHex} from "./lib/Hex.mjs"; import {fromDecimal} from "./lib/Decimal.mjs"; import {fromBinary} from "./lib/Binary.mjs"; /** * Utility functions for use in operations, the core framework and the stage. */ class Utils { /** * Translates an ordinal into a character. * * @param {number} o * @returns {char} * * @example * // returns 'a' * Utils.chr(97); */ static chr(o) { // Detect astral symbols // Thanks to @mathiasbynens for this solution // https://mathiasbynens.be/notes/javascript-unicode if (o > 0xffff) { o -= 0x10000; const high = String.fromCharCode(o >>> 10 & 0x3ff | 0xd800); o = 0xdc00 | o & 0x3ff; return high + String.fromCharCode(o); } return String.fromCharCode(o); } /** * Translates a character into an ordinal. * * @param {char} c * @returns {number} * * @example * // returns 97 * Utils.ord('a'); */ static ord(c) { // Detect astral symbols // Thanks to @mathiasbynens for this solution // https://mathiasbynens.be/notes/javascript-unicode if (c.length === 2) { const high = c.charCodeAt(0); const low = c.charCodeAt(1); if (high >= 0xd800 && high < 0xdc00 && low >= 0xdc00 && low < 0xe000) { return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000; } } return c.charCodeAt(0); } /** * Adds trailing bytes to a byteArray. * * @author tlwr [toby@toby.codes] * * @param {byteArray} arr - byteArray to add trailing bytes to. * @param {number} numBytes - Maximum width of the array. * @param {Integer} [padByte=0] - The byte to pad with. * @returns {byteArray} * * @example * // returns ["a", 0, 0, 0] * Utils.padBytesRight("a", 4); * * // returns ["a", 1, 1, 1] * Utils.padBytesRight("a", 4, 1); * * // returns ["t", "e", "s", "t", 0, 0, 0, 0] * Utils.padBytesRight("test", 8); * * // returns ["t", "e", "s", "t", 1, 1, 1, 1] * Utils.padBytesRight("test", 8, 1); */ static padBytesRight(arr, numBytes, padByte=0) { const paddedBytes = new Array(numBytes); paddedBytes.fill(padByte); [...arr].forEach((b, i) => { paddedBytes[i] = b; }); return paddedBytes; } /** * Truncates a long string to max length and adds suffix. * * @param {string} str - String to truncate * @param {number} max - Maximum length of the final string * @param {string} [suffix='...'] - The string to add to the end of the final string * @returns {string} * * @example * // returns "A long..." * Utils.truncate("A long string", 9); * * // returns "A long s-" * Utils.truncate("A long string", 9, "-"); */ static truncate(str, max, suffix="...") { if (str.length > max) { str = str.slice(0, max - suffix.length) + suffix; } return str; } /** * Converts a character or number to its hex representation. * * @param {char|number} c * @param {number} [length=2] - The width of the resulting hex number. * @returns {string} * * @example * // returns "6e" * Utils.hex("n"); * * // returns "6e" * Utils.hex(110); */ static hex(c, length=2) { c = typeof c == "string" ? Utils.ord(c) : c; return c.toString(16).padStart(length, "0"); } /** * Converts a character or number to its binary representation. * * @param {char|number} c * @param {number} [length=8] - The width of the resulting binary number. * @returns {string} * * @example * // returns "01101110" * Utils.bin("n"); * * // returns "01101110" * Utils.bin(110); */ static bin(c, length=8) { c = typeof c == "string" ? Utils.ord(c) : c; return c.toString(2).padStart(length, "0"); } /** * Returns a string with all non-printable chars as dots, optionally preserving whitespace. * * @param {string} str - The input string to display. * @param {boolean} [preserveWs=false] - Whether or not to print whitespace. * @param {boolean} [onlyAscii=false] - Whether or not to replace non ASCII characters. * @returns {string} */ static printable(str, preserveWs=false, onlyAscii=false) { if (onlyAscii) { return str.replace(/[^\x20-\x7e]/g, "."); } // eslint-disable-next-line no-misleading-character-class 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; const wsRe = /[\x09-\x10\u2028\u2029]/g; str = str.replace(re, "."); if (!preserveWs) str = str.replace(wsRe, "."); return str; } /** * Returns a string with whitespace represented as special characters from the * Unicode Private Use Area, which CyberChef will display as control characters. * Private Use Area characters are in the range U+E000..U+F8FF. * https://en.wikipedia.org/wiki/Private_Use_Areas * @param {string} str * @returns {string} */ static escapeWhitespace(str) { return str.replace(/[\x09-\x10]/g, function(c) { return String.fromCharCode(0xe000 + c.charCodeAt(0)); }); } /** * Parse a string entered by a user and replace escaped chars with the bytes they represent. * * @param {string} str * @returns {string} * * @example * // returns "\x00" * Utils.parseEscapedChars("\\x00"); * * // returns "\n" * Utils.parseEscapedChars("\\n"); */ static parseEscapedChars(str) { 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) { switch (a[0]) { case "\\": return "\\"; case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": return String.fromCharCode(parseInt(a, 8)); case "a": return String.fromCharCode(7); case "b": return "\b"; case "t": return "\t"; case "n": return "\n"; case "v": return "\v"; case "f": return "\f"; case "r": return "\r"; case '"': return '"'; case "'": return "'"; case "x": return String.fromCharCode(parseInt(a.substr(1), 16)); case "u": if (a[1] === "{") return String.fromCodePoint(parseInt(a.slice(2, -1), 16)); else return String.fromCharCode(parseInt(a.substr(1), 16)); } }); } /** * Escape a string containing regex control characters so that it can be safely * used in a regex without causing unintended behaviours. * * @param {string} str * @returns {string} * * @example * // returns "\[example\]" * Utils.escapeRegex("[example]"); */ static escapeRegex(str) { return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1"); } /** * Expand an alphabet range string into a list of the characters in that range. * * @param {string} alphStr * @returns {char[]} * * @example * // returns ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] * Utils.expandAlphRange("0-9"); * * // returns ["a", "b", "c", "d", "0", "1", "2", "3", "+", "/"] * Utils.expandAlphRange("a-d0-3+/"); * * // returns ["a", "b", "c", "d", "0", "-", "3"] * Utils.expandAlphRange("a-d0\\-3") */ static expandAlphRange(alphStr) { const alphArr = []; for (let i = 0; i < alphStr.length; i++) { if (i < alphStr.length - 2 && alphStr[i+1] === "-" && alphStr[i] !== "\\") { const start = Utils.ord(alphStr[i]), end = Utils.ord(alphStr[i+2]); for (let j = start; j <= end; j++) { alphArr.push(Utils.chr(j)); } i += 2; } else if (i < alphStr.length - 2 && alphStr[i] === "\\" && alphStr[i+1] === "-") { alphArr.push("-"); i++; } else { alphArr.push(alphStr[i]); } } return alphArr; } /** * Coverts data of varying types to a byteArray. * Accepts hex, Base64, UTF8 and Latin1 strings. * * @param {string} str * @param {string} type - One of "Binary", "Hex", "Decimal", "Base64", "UTF8" or "Latin1" * @returns {byteArray} * * @example * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] * Utils.convertToByteArray("Привет", "utf8"); * * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] * Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex"); * * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] * Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); */ static convertToByteArray(str, type) { switch (type.toLowerCase()) { case "binary": return fromBinary(str); case "hex": return fromHex(str); case "decimal": return fromDecimal(str); case "base64": return fromBase64(str, null, "byteArray"); case "utf8": return Utils.strToUtf8ByteArray(str); case "latin1": default: return Utils.strToByteArray(str); } } /** * Coverts data of varying types to a byte string. * Accepts hex, Base64, UTF8 and Latin1 strings. * * @param {string} str * @param {string} type - One of "Binary", "Hex", "Decimal", "Base64", "UTF8" or "Latin1" * @returns {string} * * @example * // returns "Привет" * Utils.convertToByteString("Привет", "utf8"); * * // returns "Здравствуйте" * Utils.convertToByteString("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex"); * * // returns "Здравствуйте" * Utils.convertToByteString("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); */ static convertToByteString(str, type) { switch (type.toLowerCase()) { case "binary": return Utils.byteArrayToChars(fromBinary(str)); case "hex": return Utils.byteArrayToChars(fromHex(str)); case "decimal": return Utils.byteArrayToChars(fromDecimal(str)); case "base64": return Utils.byteArrayToChars(fromBase64(str, null, "byteArray")); case "utf8": return utf8.encode(str); case "latin1": default: return str; } } /** * Converts a byte array to an integer. * * @param {byteArray} byteArray * @param {string} byteorder - "little" or "big" * @returns {integer} * * @example * // returns 67305985 * Utils.byteArrayToInt([1, 2, 3, 4], "little"); * * // returns 16909060 * Utils.byteArrayToInt([1, 2, 3, 4], "big"); */ static byteArrayToInt(byteArray, byteorder) { let value = 0; if (byteorder === "big") { for (let i = 0; i < byteArray.length; i++) { value = (value * 256) + byteArray[i]; } } else { for (let i = byteArray.length - 1; i >= 0; i--) { value = (value * 256) + byteArray[i]; } } return value; } /** * Converts an integer to a byte array of {length} bytes. * * @param {integer} value * @param {integer} length * @param {string} byteorder - "little" or "big" * @returns {byteArray} * * @example * // returns [5, 255, 109, 1] * Utils.intToByteArray(23985925, 4, "little"); * * // returns [1, 109, 255, 5] * Utils.intToByteArray(23985925, 4, "big"); * * // returns [0, 0, 0, 0, 1, 109, 255, 5] * Utils.intToByteArray(23985925, 8, "big"); */ static intToByteArray(value, length, byteorder) { const arr = new Array(length); if (byteorder === "little") { for (let i = 0; i < length; i++) { arr[i] = value & 0xFF; value = value >>> 8; } } else { for (let i = length - 1; i >= 0; i--) { arr[i] = value & 0xFF; value = value >>> 8; } } return arr; } /** * Converts a string to an ArrayBuffer. * Treats the string as UTF-8 if any values are over 255. * * @param {string} str * @returns {ArrayBuffer} * * @example * // returns [72,101,108,108,111] * Utils.strToArrayBuffer("Hello"); * * // returns [228,189,160,229,165,189] * Utils.strToArrayBuffer("你好"); */ static strToArrayBuffer(str) { log.debug(`Converting string[${str?.length}] to array buffer`); if (!str) return new ArrayBuffer; const arr = new Uint8Array(str.length); let i = str.length, b; while (i--) { b = str.charCodeAt(i); arr[i] = b; // If any of the bytes are over 255, read as UTF-8 if (b > 255) return Utils.strToUtf8ArrayBuffer(str); } return arr.buffer; } /** * Converts a string to a UTF-8 ArrayBuffer. * * @param {string} str * @returns {ArrayBuffer} * * @example * // returns [72,101,108,108,111] * Utils.strToUtf8ArrayBuffer("Hello"); * * // returns [228,189,160,229,165,189] * Utils.strToUtf8ArrayBuffer("你好"); */ static strToUtf8ArrayBuffer(str) { log.debug(`Converting string[${str?.length}] to UTF8 array buffer`); if (!str) return new ArrayBuffer; const buffer = new TextEncoder("utf-8").encode(str); if (str.length !== buffer.length) { if (isWorkerEnvironment() && self && typeof self.setOption === "function") { self.setOption("attemptHighlight", false); } else if (isWebEnvironment()) { window.app.options.attemptHighlight = false; } } return buffer.buffer; } /** * Converts a string to a byte array. * Treats the string as UTF-8 if any values are over 255. * * @param {string} str * @returns {byteArray} * * @example * // returns [72,101,108,108,111] * Utils.strToByteArray("Hello"); * * // returns [228,189,160,229,165,189] * Utils.strToByteArray("你好"); */ static strToByteArray(str) { log.debug(`Converting string[${str?.length}] to byte array`); if (!str) return []; const byteArray = new Array(str.length); let i = str.length, b; while (i--) { b = str.charCodeAt(i); byteArray[i] = b; // If any of the bytes are over 255, read as UTF-8 if (b > 255) return Utils.strToUtf8ByteArray(str); } return byteArray; } /** * Converts a string to a UTF-8 byte array. * * @param {string} str * @returns {byteArray} * * @example * // returns [72,101,108,108,111] * Utils.strToUtf8ByteArray("Hello"); * * // returns [228,189,160,229,165,189] * Utils.strToUtf8ByteArray("你好"); */ static strToUtf8ByteArray(str) { log.debug(`Converting string[${str?.length}] to UTF8 byte array`); if (!str) return []; const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { if (isWorkerEnvironment()) { self.setOption("attemptHighlight", false); } else if (isWebEnvironment()) { window.app.options.attemptHighlight = false; } } return Utils.strToByteArray(utf8Str); } /** * Converts a string to a unicode charcode array * * @param {string} str * @returns {byteArray} * * @example * // returns [72,101,108,108,111] * Utils.strToCharcode("Hello"); * * // returns [20320,22909] * Utils.strToCharcode("你好"); */ static strToCharcode(str) { log.debug(`Converting string[${str?.length}] to charcode`); if (!str) return []; const charcode = []; for (let i = 0; i < str.length; i++) { let ord = str.charCodeAt(i); // Detect and merge astral symbols if (i < str.length - 1 && ord >= 0xd800 && ord < 0xdc00) { const low = str[i + 1].charCodeAt(0); if (low >= 0xdc00 && low < 0xe000) { ord = Utils.ord(str[i] + str[++i]); } } charcode.push(ord); } return charcode; } /** * Attempts to convert a byte array to a UTF-8 string. * * @param {byteArray|Uint8Array} byteArray * @returns {string} * * @example * // returns "Hello" * Utils.byteArrayToUtf8([72,101,108,108,111]); * * // returns "你好" * Utils.byteArrayToUtf8([228,189,160,229,165,189]); */ static byteArrayToUtf8(byteArray) { log.debug(`Converting byte array[${byteArray?.length}] to UTF8`); if (!byteArray || !byteArray.length) return ""; if (!(byteArray instanceof Uint8Array)) byteArray = new Uint8Array(byteArray); try { const str = new TextDecoder("utf-8", {fatal: true}).decode(byteArray); if (str.length !== byteArray.length) { if (isWorkerEnvironment()) { self.setOption("attemptHighlight", false); } else if (isWebEnvironment()) { window.app.options.attemptHighlight = false; } } return str; } catch (err) { // If it fails, treat it as ANSI return Utils.byteArrayToChars(byteArray); } } /** * Converts a charcode array to a string. * * @param {byteArray|Uint8Array} byteArray * @returns {string} * * @example * // returns "Hello" * Utils.byteArrayToChars([72,101,108,108,111]); * * // returns "你好" * Utils.byteArrayToChars([20320,22909]); */ static byteArrayToChars(byteArray) { log.debug(`Converting byte array[${byteArray?.length}] to chars`); if (!byteArray || !byteArray.length) return ""; let str = ""; // Maxiumum arg length for fromCharCode is 65535, but the stack may already be fairly deep, // so don't get too near it. for (let i = 0; i < byteArray.length; i += 20000) { str += String.fromCharCode(...(byteArray.slice(i, i+20000))); } return str; } /** * Converts an ArrayBuffer to a string. * * @param {ArrayBuffer} arrayBuffer * @param {boolean} [utf8=true] - Whether to attempt to decode the buffer as UTF-8 * @returns {string} * * @example * // returns "hello" * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); */ static arrayBufferToStr(arrayBuffer, utf8=true) { log.debug(`Converting array buffer[${arrayBuffer?.byteLength}] to str`); if (!arrayBuffer || !arrayBuffer.byteLength) return ""; const arr = new Uint8Array(arrayBuffer); return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr); } /** * Calculates the Shannon entropy for a given set of data. * * @param {Uint8Array|ArrayBuffer} input * @returns {number} */ static calculateShannonEntropy(data) { if (data instanceof ArrayBuffer) { data = new Uint8Array(data); } const prob = [], occurrences = new Array(256).fill(0); // Count occurrences of each byte in the input let i; for (i = 0; i < data.length; i++) { occurrences[data[i]]++; } // Store probability list for (i = 0; i < occurrences.length; i++) { if (occurrences[i] > 0) { prob.push(occurrences[i] / data.length); } } // Calculate Shannon entropy let entropy = 0, p; for (i = 0; i < prob.length; i++) { p = prob[i]; entropy += p * Math.log(p) / Math.log(2); } return -entropy; } /** * Parses CSV data and returns it as a two dimensional array or strings. * * @param {string} data * @param {string[]} [cellDelims=[","]] * @param {string[]} [lineDelims=["\n", "\r"]] * @returns {string[][]} * * @example * // returns [["head1", "head2"], ["data1", "data2"]] * Utils.parseCSV("head1,head2\ndata1,data2"); */ static parseCSV(data, cellDelims=[","], lineDelims=["\n", "\r"]) { let b, next, renderNext = false, inString = false, cell = "", line = []; const lines = []; // Remove BOM, often present in Excel CSV files if (data.length && data[0] === "\uFEFF") data = data.substr(1); for (let i = 0; i < data.length; i++) { b = data[i]; next = data[i+1] || ""; if (renderNext) { cell += b; renderNext = false; } else if (b === "\"" && !inString) { inString = true; } else if (b === "\"" && inString) { if (next === "\"") renderNext = true; else inString = false; } else if (!inString && cellDelims.indexOf(b) >= 0) { line.push(cell); cell = ""; } else if (!inString && lineDelims.indexOf(b) >= 0) { line.push(cell); cell = ""; lines.push(line); line = []; // Skip next byte if it is also a line delim (e.g. \r\n) if (lineDelims.indexOf(next) >= 0 && next !== b) { i++; } } else { cell += b; } } if (line.length) { line.push(cell); lines.push(line); } return lines; } /** * Removes all HTML (or XML) tags from the input string. * * @param {string} htmlStr * @param {boolean} [removeScriptAndStyle=false] * - Flag to specify whether to remove entire script or style blocks * @returns {string} * * @example * // returns "Test" * Utils.stripHtmlTags("
Test
"); */ static stripHtmlTags(htmlStr, removeScriptAndStyle=false) { /** * Recursively remove a pattern from a string until there are no more matches. * Avoids incomplete sanitization e.g. "aabcbc".replace(/abc/g, "") === "abc" * * @param {RegExp} pattern * @param {string} str * @returns {string} */ function recursiveRemove(pattern, str) { const newStr = str.replace(pattern, ""); return newStr.length === str.length ? newStr : recursiveRemove(pattern, newStr); } if (removeScriptAndStyle) { htmlStr = recursiveRemove(/]*>(\s|\S)*?<\/script[^>]*>/gi, htmlStr); htmlStr = recursiveRemove(/]*>(\s|\S)*?<\/style[^>]*>/gi, htmlStr); } return recursiveRemove(/<[^>]+>/g, htmlStr); } /** * Escapes HTML tags in a string to stop them being rendered. * https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet * * Null bytes are a special case and are converted to a character from the Unicode * Private Use Area, which CyberChef will display as a control character picture. * This is done due to null bytes not being rendered or stored correctly in HTML * DOM building. * * @param {string} str * @returns string * * @example * // return "A <script> tag" * Utils.escapeHtml("A `; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {json} */ run(input, args) { const visualizationType = args[0]; input = new Uint8Array(input); switch (visualizationType) { case "Histogram (Bar)": case "Histogram (Line)": return this.calculateByteFrequency(input); case "Curve": case "Image": return this.calculateScanningEntropy(input).entropyData; case "Shannon scale": default: return this.calculateShannonEntropy(input); } } /** * Displays the entropy in a visualisation for web apps. * * @param {json} entropyData * @param {Object[]} args * @returns {html} */ present(entropyData, args) { const visualizationType = args[0]; switch (visualizationType) { case "Histogram (Bar)": return this.createByteFrequencyBarHistogram(entropyData); case "Histogram (Line)": return this.createByteFrequencyLineHistogram(entropyData); case "Curve": return this.createEntropyCurve(entropyData); case "Image": return this.createEntropyImage(entropyData); case "Shannon scale": default: return this.createShannonEntropyVisualization(entropyData); } } } export default Entropy; ================================================ FILE: src/core/operations/EscapeString.mjs ================================================ /** * @author Vel0x [dalemy@microsoft.com] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import jsesc from "jsesc"; /** * Escape string operation */ class EscapeString extends Operation { /** * EscapeString constructor */ constructor() { super(); this.name = "Escape string"; this.module = "Default"; this.description = "Escapes special characters in a string so that they do not cause conflicts. For example, Don't stop me now becomes Don\\'t stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
  • \\unnnn (Unicode character)
  • \\u{nnnnnn} (Unicode code point)
"; this.infoURL = "https://wikipedia.org/wiki/Escape_sequence"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Escape level", "type": "option", "value": ["Special chars", "Everything", "Minimal"] }, { "name": "Escape quote", "type": "option", "value": ["Single", "Double", "Backtick"] }, { "name": "JSON compatible", "type": "boolean", "value": false }, { "name": "ES6 compatible", "type": "boolean", "value": true }, { "name": "Uppercase hex", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} * * @example * EscapeString.run("Don't do that", []) * > "Don\'t do that" * EscapeString.run(`Hello * World`, []) * > "Hello\nWorld" */ run(input, args) { const level = args[0], quotes = args[1], jsonCompat = args[2], es6Compat = args[3], lowercaseHex = !args[4]; return jsesc(input, { quotes: quotes.toLowerCase(), es6: es6Compat, escapeEverything: level === "Everything", minimal: level === "Minimal", json: jsonCompat, lowercaseHex: lowercaseHex, }); } } export default EscapeString; ================================================ FILE: src/core/operations/EscapeUnicodeCharacters.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Escape Unicode Characters operation */ class EscapeUnicodeCharacters extends Operation { /** * EscapeUnicodeCharacters constructor */ constructor() { super(); this.name = "Escape Unicode Characters"; this.module = "Default"; this.description = "Converts characters to their unicode-escaped notations.

Supports the prefixes:
  • \\u
  • %u
  • U+
e.g. σου becomes \\u03C3\\u03BF\\u03C5"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Prefix", "type": "option", "value": ["\\u", "%u", "U+"] }, { "name": "Encode all chars", "type": "boolean", "value": false }, { "name": "Padding", "type": "number", "value": 4 }, { "name": "Uppercase hex", "type": "boolean", "value": true } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const regexWhitelist = /[ -~]/i, [prefix, encodeAll, padding, uppercaseHex] = args; let output = "", character = ""; for (let i = 0; i < input.length; i++) { character = input[i]; if (!encodeAll && regexWhitelist.test(character)) { // It’s a printable ASCII character so don’t escape it. output += character; continue; } let cp = character.codePointAt(0).toString(16); if (uppercaseHex) cp = cp.toUpperCase(); output += prefix + cp.padStart(padding, "0"); } return output; } } export default EscapeUnicodeCharacters; ================================================ FILE: src/core/operations/ExpandAlphabetRange.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Expand alphabet range operation */ class ExpandAlphabetRange extends Operation { /** * ExpandAlphabetRange constructor */ constructor() { super(); this.name = "Expand alphabet range"; this.module = "Default"; this.description = "Expand an alphabet range string into a list of the characters in that range.

e.g. a-z becomes abcdefghijklmnopqrstuvwxyz."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "binaryString", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return Utils.expandAlphRange(input).join(args[0]); } } export default ExpandAlphabetRange; ================================================ FILE: src/core/operations/ExtractAudioMetadata.mjs ================================================ /** * @author d0s1nt [d0s1nt@cyberchefaudio] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { makeEmptyReport, sniffContainer } from "../lib/AudioMetaSchema.mjs"; import { parseMp3, parseRiffWave, parseFlac, parseOgg, parseMp4BestEffort, parseAiffBestEffort, parseAacAdts, parseAc3, parseWmaAsf, } from "../lib/AudioParsers.mjs"; /** * Extract Audio Metadata operation. */ class ExtractAudioMetadata extends Operation { /** Creates the Extract Audio Metadata operation. */ constructor() { super(); this.name = "Extract Audio Metadata"; this.module = "Default"; this.description = "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."; this.infoURL = "https://wikipedia.org/wiki/Audio_file_format"; this.inputType = "ArrayBuffer"; this.outputType = "JSON"; this.presentType = "html"; this.args = [ { name: "Filename (optional)", type: "string", value: "" }, { name: "Max embedded text bytes (iXML/axml/etc)", type: "number", value: 1024 * 512 }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {Object} */ run(input, args) { const filename = (args?.[0] || "").trim() || null; const maxTextBytes = Number.isFinite(args?.[1]) ? Math.max(1024, args[1]) : 1024 * 512; if (!(input instanceof ArrayBuffer) || input.byteLength === 0) throw new OperationError("No input data. Load an audio file (drag/drop or use the open file button)."); const bytes = new Uint8Array(input); const container = sniffContainer(bytes); const report = makeEmptyReport(filename, bytes.length, container); try { const parsers = { mp3: () => parseMp3(bytes, report), wav: () => parseRiffWave(bytes, report, maxTextBytes), bw64: () => parseRiffWave(bytes, report, maxTextBytes), flac: () => parseFlac(bytes, report, maxTextBytes), ogg: () => parseOgg(bytes, report), opus: () => parseOgg(bytes, report), mp4: () => parseMp4BestEffort(bytes, report), m4a: () => parseMp4BestEffort(bytes, report), aiff: () => parseAiffBestEffort(bytes, report, maxTextBytes), aac: () => parseAacAdts(bytes, report), ac3: () => parseAc3(bytes, report), wma: () => parseWmaAsf(bytes, report), }; if (parsers[container.type]) { parsers[container.type](); } else { report.errors.push({ stage: "sniff", message: "Unknown/unsupported container (best-effort scan not implemented)." }); } } catch (e) { report.errors.push({ stage: "parse", message: String(e?.message || e) }); } return report; } /** Renders the extracted metadata as an HTML table. */ present(data) { if (!data || typeof data !== "object") return JSON.stringify(data, null, 4); const esc = Utils.escapeHtml; const row = (k, v) => `${esc(String(k))}${esc(String(v ?? ""))}\n`; const section = (title) => `${esc(title)}\n`; const objRows = (obj, filter = (v) => v !== null) => { for (const [k, v] of Object.entries(obj)) { if (filter(v)) html += row(k, v); } }; const objSection = (obj, title, filter) => { if (!obj) return; html += section(title); objRows(obj, filter); }; const listSection = (arr, title, fmt) => { if (!arr?.length) return; html += section(title); for (const item of arr) html += fmt(item); }; let html = `\n`; html += section("Artifact"); html += row("Filename", data.artifact?.filename || "(none)"); html += row("Size", `${(data.artifact?.byte_length ?? 0).toLocaleString()} bytes`); html += row("Container", data.artifact?.container?.type); html += row("MIME", data.artifact?.container?.mime); if (data.artifact?.container?.brand) html += row("Brand", data.artifact.container.brand); html += section("Detections"); html += row("Metadata systems", (data.detections?.metadata_systems || []).join(", ") || "None"); html += row("Provenance systems", (data.detections?.provenance_systems || []).join(", ") || "None"); const common = data.tags?.common || {}; html += section("Common Tags"); if (Object.values(common).some((v) => v !== null)) { for (const [key, val] of Object.entries(common)) { if (val !== null) html += row(key.charAt(0).toUpperCase() + key.slice(1), val); } } else { html += row("(none)", "No common tags found"); } listSection(data.tags?.raw?.id3v2?.frames, "ID3v2 Frames", (f) => { const val = typeof f.decoded === "object" ? JSON.stringify(f.decoded) : (f.decoded ?? `(${f.size} bytes)`); return row(f.id + (f.description ? ` \u2014 ${f.description}` : ""), val); }); objSection(data.tags?.raw?.id3v1, "ID3v1", (v) => !!v); listSection(data.tags?.raw?.apev2?.items, "APEv2 Tags", (i) => row(i.key, i.value)); if (data.tags?.raw?.vorbis_comments?.comments?.length) { html += section("Vorbis Comments"); html += row("Vendor", data.tags.raw.vorbis_comments.vendor); for (const c of data.tags.raw.vorbis_comments.comments) html += row(c.key, c.value); } objSection(data.tags?.raw?.riff?.info, "RIFF INFO", () => true); objSection(data.tags?.raw?.riff?.bext, "BWF bext"); listSection(data.tags?.raw?.riff?.chunks, "RIFF Chunks", (c) => row(c.id, `${c.size} bytes @ offset ${c.offset}`)); listSection(data.tags?.raw?.flac?.blocks, "FLAC Metadata Blocks", (b) => row(b.type, `${b.length} bytes`)); if (data.tags?.raw?.mp4?.top_level_atoms?.length) { html += section("MP4 Top-Level Atoms"); const atoms = data.tags.raw.mp4.top_level_atoms; for (const a of atoms.slice(0, 50)) html += row(a.type, `${a.size} bytes @ offset ${a.offset}`); if (atoms.length > 50) html += row("...", `${atoms.length - 50} more atoms`); } listSection(data.tags?.raw?.aiff?.chunks, "AIFF Chunks", (c) => row(c.id, c.value)); objSection(data.tags?.raw?.aac, "AAC ADTS"); objSection(data.tags?.raw?.ac3, "AC3 (Dolby Digital)"); objSection(data.tags?.raw?.asf?.content_description, "ASF Content Description", (v) => !!v); listSection(data.tags?.raw?.asf?.extended_content, "ASF Extended Content", (d) => row(d.name, d.value)); listSection(data.embedded, "Embedded Objects", (e) => row(e.id, `${e.content_type || "unknown"} \u2014 ${(e.byte_length ?? 0).toLocaleString()} bytes`)); if (data.provenance?.c2pa?.present) { html += section("C2PA Provenance"); html += row("Present", "Yes"); for (const emb of (data.provenance.c2pa.embedding || [])) html += row("Carrier", `${emb.carrier} \u2014 ${(emb.byte_length ?? 0).toLocaleString()} bytes`); } listSection(data.errors, "Errors", (e) => row(e.stage, e.message)); html += "
"; return html; } } export default ExtractAudioMetadata; ================================================ FILE: src/core/operations/ExtractDates.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { search } from "../lib/Extract.mjs"; /** * Extract dates operation */ class ExtractDates extends Operation { /** * ExtractDates constructor */ constructor() { super(); this.name = "Extract dates"; this.module = "Regex"; this.description = "Extracts dates in the following formats
  • yyyy-mm-dd
  • dd/mm/yyyy
  • mm/dd/yyyy
Dividers can be any of /, -, . or space"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Display total", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const displayTotal = args[0], date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig"); const results = search(input, regex); if (displayTotal) { return `Total found: ${results.length}\n\n${results.join("\n")}`; } else { return results.join("\n"); } } } export default ExtractDates; ================================================ FILE: src/core/operations/ExtractDomains.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { search, DOMAIN_REGEX, DMARC_DOMAIN_REGEX } from "../lib/Extract.mjs"; import { caseInsensitiveSort } from "../lib/Sort.mjs"; /** * Extract domains operation */ class ExtractDomains extends Operation { /** * ExtractDomains constructor */ constructor() { super(); this.name = "Extract domains"; this.module = "Regex"; this.description = "Extracts fully qualified domain names.
Note that this will not include paths. Use Extract URLs to find entire URLs."; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Display total", type: "boolean", value: false }, { name: "Sort", type: "boolean", value: false }, { name: "Unique", type: "boolean", value: false }, { name: "Underscore (DMARC, DKIM, etc)", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [displayTotal, sort, unique, dmarc] = args; const results = search( input, dmarc ? DMARC_DOMAIN_REGEX : DOMAIN_REGEX, null, sort ? caseInsensitiveSort : null, unique ); if (displayTotal) { return `Total found: ${results.length}\n\n${results.join("\n")}`; } else { return results.join("\n"); } } } export default ExtractDomains; ================================================ FILE: src/core/operations/ExtractEXIF.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import ExifParser from "exif-parser"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Extract EXIF operation */ class ExtractEXIF extends Operation { /** * ExtractEXIF constructor */ constructor() { super(); this.name = "Extract EXIF"; this.module = "Image"; this.description = [ "Extracts EXIF data from an image.", "

", "EXIF data is metadata embedded in images (JPEG, JPG, TIFF) and audio files.", "

", "EXIF data from photos usually contains information about the image file itself as well as the device used to create it.", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Exif"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { try { const parser = ExifParser.create(input); const result = parser.parse(); const lines = []; for (const tagName in result.tags) { const value = result.tags[tagName]; lines.push(`${tagName}: ${value}`); } const numTags = lines.length; lines.unshift(`Found ${numTags} tags.\n`); return lines.join("\n"); } catch (err) { throw new OperationError(`Could not extract EXIF data from image: ${err}`); } } } export default ExtractEXIF; ================================================ FILE: src/core/operations/ExtractEmailAddresses.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { search } from "../lib/Extract.mjs"; import { caseInsensitiveSort } from "../lib/Sort.mjs"; /** * Extract email addresses operation */ class ExtractEmailAddresses extends Operation { /** * ExtractEmailAddresses constructor */ constructor() { super(); this.name = "Extract email addresses"; this.module = "Regex"; this.description = "Extracts all email addresses from the input."; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Display total", type: "boolean", value: false }, { name: "Sort", type: "boolean", value: false }, { name: "Unique", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [displayTotal, sort, unique] = args, // email regex from: https://www.regextester.com/98066 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; const results = search( input, regex, null, sort ? caseInsensitiveSort : null, unique ); if (displayTotal) { return `Total found: ${results.length}\n\n${results.join("\n")}`; } else { return results.join("\n"); } } } export default ExtractEmailAddresses; ================================================ FILE: src/core/operations/ExtractFilePaths.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { search } from "../lib/Extract.mjs"; import { caseInsensitiveSort } from "../lib/Sort.mjs"; /** * Extract file paths operation */ class ExtractFilePaths extends Operation { /** * ExtractFilePaths constructor */ constructor() { super(); this.name = "Extract file paths"; this.module = "Regex"; this.description = "Extracts anything that looks like a Windows or UNIX file path.

Note that if UNIX is selected, there will likely be a lot of false positives."; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Windows", type: "boolean", value: true }, { name: "UNIX", type: "boolean", value: true }, { name: "Display total", type: "boolean", value: false }, { name: "Sort", type: "boolean", value: false }, { name: "Unique", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [includeWinPath, includeUnixPath, displayTotal, sort, unique] = args, winDrive = "[A-Z]:\\\\", winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}", winExt = "[A-Z\\d]{1,6}", winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName + "(?:\\." + winExt + ")?", unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+"; let filePaths = ""; if (includeWinPath && includeUnixPath) { filePaths = winPath + "|" + unixPath; } else if (includeWinPath) { filePaths = winPath; } else if (includeUnixPath) { filePaths = unixPath; } if (!filePaths) { return ""; } const regex = new RegExp(filePaths, "ig"); const results = search( input, regex, null, sort ? caseInsensitiveSort : null, unique ); if (displayTotal) { return `Total found: ${results.length}\n\n${results.join("\n")}`; } else { return results.join("\n"); } } } export default ExtractFilePaths; ================================================ FILE: src/core/operations/ExtractFiles.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import {scanForFileTypes, extractFile} from "../lib/FileType.mjs"; import {FILE_SIGNATURES} from "../lib/FileSignatures.mjs"; /** * Extract Files operation */ class ExtractFiles extends Operation { /** * ExtractFiles constructor */ constructor() { super(); // Get the first extension for each signature that can be extracted let supportedExts = Object.keys(FILE_SIGNATURES).map(cat => { return FILE_SIGNATURES[cat] .filter(sig => sig.extractor) .map(sig => sig.extension.toUpperCase()); }); // Flatten categories and remove duplicates supportedExts = [].concat(...supportedExts).unique(); this.name = "Extract Files"; this.module = "Default"; this.description = `Performs file carving to attempt to extract files from the input.

This operation is currently capable of carving out the following formats:
  • ${supportedExts.join("
  • ")}
Minimum File Size can be used to prune small false positives.`; this.infoURL = "https://forensics.wiki/file_carving"; this.inputType = "ArrayBuffer"; this.outputType = "List"; this.presentType = "html"; this.args = Object.keys(FILE_SIGNATURES).map(cat => { return { name: cat, type: "boolean", value: cat === "Miscellaneous" ? false : true }; }).concat([ { name: "Ignore failed extractions", type: "boolean", value: true }, { name: "Minimum File Size", type: "number", value: 100 } ]); } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {List} */ run(input, args) { const bytes = new Uint8Array(input), categories = [], minSize = args.pop(1), ignoreFailedExtractions = args.pop(1); args.forEach((cat, i) => { if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]); }); // Scan for embedded files const detectedFiles = scanForFileTypes(bytes, categories); // Extract each file that we support const files = []; const errors = []; detectedFiles.forEach(detectedFile => { try { const file = extractFile(bytes, detectedFile.fileDetails, detectedFile.offset); if (file.size >= minSize) files.push(file); } catch (err) { if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) { errors.push( `Error while attempting to extract ${detectedFile.fileDetails.name} ` + `at offset ${detectedFile.offset}:\n` + `${err.message}` ); } } }); if (errors.length) { throw new OperationError(errors.join("\n\n")); } return files; } /** * Displays the files in HTML for web apps. * * @param {File[]} files * @returns {html} */ async present(files) { return await Utils.displayFilesAsHTML(files); } } export default ExtractFiles; ================================================ FILE: src/core/operations/ExtractHashes.mjs ================================================ /** * @author mshwed [m@ttshwed.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { search } from "../lib/Extract.mjs"; /** * Extract Hash Values operation */ class ExtractHashes extends Operation { /** * ExtractHashValues constructor */ constructor() { super(); this.name = "Extract hashes"; this.module = "Regex"; this.description = "Extracts potential hashes based on hash character length"; this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Hash character length", type: "number", value: 40 }, { name: "All hashes", type: "boolean", value: false }, { name: "Display Total", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const results = []; let hashCount = 0; const [hashLength, searchAllHashes, showDisplayTotal] = args; // Convert character length to bit length let hashBitLengths = [(hashLength / 2) * 8]; if (searchAllHashes) hashBitLengths = [4, 8, 16, 32, 64, 128, 160, 192, 224, 256, 320, 384, 512, 1024]; for (const hashBitLength of hashBitLengths) { // Convert bit length to character length const hashCharacterLength = (hashBitLength / 8) * 2; const regex = new RegExp(`(\\b|^)[a-f0-9]{${hashCharacterLength}}(\\b|$)`, "g"); const searchResults = search(input, regex, null, false); hashCount += searchResults.length; results.push(...searchResults); } let output = ""; if (showDisplayTotal) { output = `Total Results: ${hashCount}\n\n`; } output = output + results.join("\n"); return output; } } export default ExtractHashes; ================================================ FILE: src/core/operations/ExtractID3.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; /** * Extract ID3 operation */ class ExtractID3 extends Operation { /** * ExtractID3 constructor */ constructor() { super(); this.name = "Extract ID3"; this.module = "Default"; this.description = "This operation extracts ID3 metadata from an MP3 file.

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."; this.infoURL = "https://wikipedia.org/wiki/ID3"; this.inputType = "ArrayBuffer"; this.outputType = "JSON"; this.presentType = "html"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {JSON} */ run(input, args) { input = new Uint8Array(input); /** * Extracts the ID3 header fields. */ function extractHeader() { if (!Array.from(input.slice(0, 3)).equals([0x49, 0x44, 0x33])) throw new OperationError("No valid ID3 header."); const header = { "Type": "ID3", // Tag version "Version": input[3].toString() + "." + input[4].toString(), // Header version "Flags": input[5].toString() }; input = input.slice(6); return header; } /** * Converts the size fields to a single integer. * * @param {number} num * @returns {string} */ function readSize(num) { let result = 0; // The sizes are 7 bit numbers stored in 8 bit locations for (let i = (num) * 7; i; i -= 7) { result = (result << i) | input[0]; input = input.slice(1); } return result; } /** * Reads frame header based on ID. * * @param {string} id * @returns {number} */ function readFrame(id) { const frame = {}; // Size of frame const size = readSize(4); frame.Size = size.toString(); frame.Description = FRAME_DESCRIPTIONS[id]; input = input.slice(2); // Read data from frame let data = ""; for (let i = 1; i < size; i++) data += String.fromCharCode(input[i]); frame.Data = data; // Move to next Frame input = input.slice(size); return [frame, size]; } const result = extractHeader(); const headerTagSize = readSize(4); result.Size = headerTagSize.toString(); const tags = {}; let pos = 10; // While the current element is in the header while (pos < headerTagSize) { // Frame Identifier of frame let id = String.fromCharCode(input[0]) + String.fromCharCode(input[1]) + String.fromCharCode(input[2]); input = input.slice(3); // If the next character is non-zero it is an identifier if (input[0] !== 0) { id += String.fromCharCode(input[0]); } input = input.slice(1); if (id in FRAME_DESCRIPTIONS) { const [frame, size] = readFrame(id); tags[id] = frame; pos += 10 + size; } else if (id === "\x00\x00\x00") { // end of header break; } else { throw new OperationError("Unknown Frame Identifier: " + id); } } result.Tags = tags; return result; } /** * Displays the extracted data in a more accessible format for web apps. * @param {JSON} data * @returns {html} */ present(data) { if (!data || !Object.prototype.hasOwnProperty.call(data, "Tags")) return JSON.stringify(data, null, 4); let output = ``; for (const tagID in data.Tags) { const description = data.Tags[tagID].Description, contents = data.Tags[tagID].Data; output += ``; } output += "
TagDescriptionData
${tagID}${Utils.escapeHtml(description)}${Utils.escapeHtml(contents)}
"; return output; } } // Borrowed from https://github.com/aadsm/jsmediatags const FRAME_DESCRIPTIONS = { // v2.2 "BUF": "Recommended buffer size", "CNT": "Play counter", "COM": "Comments", "CRA": "Audio encryption", "CRM": "Encrypted meta frame", "ETC": "Event timing codes", "EQU": "Equalization", "GEO": "General encapsulated object", "IPL": "Involved people list", "LNK": "Linked information", "MCI": "Music CD Identifier", "MLL": "MPEG location lookup table", "PIC": "Attached picture", "POP": "Popularimeter", "REV": "Reverb", "RVA": "Relative volume adjustment", "SLT": "Synchronized lyric/text", "STC": "Synced tempo codes", "TAL": "Album/Movie/Show title", "TBP": "BPM (Beats Per Minute)", "TCM": "Composer", "TCO": "Content type", "TCR": "Copyright message", "TDA": "Date", "TDY": "Playlist delay", "TEN": "Encoded by", "TFT": "File type", "TIM": "Time", "TKE": "Initial key", "TLA": "Language(s)", "TLE": "Length", "TMT": "Media type", "TOA": "Original artist(s)/performer(s)", "TOF": "Original filename", "TOL": "Original Lyricist(s)/text writer(s)", "TOR": "Original release year", "TOT": "Original album/Movie/Show title", "TP1": "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group", "TP2": "Band/Orchestra/Accompaniment", "TP3": "Conductor/Performer refinement", "TP4": "Interpreted, remixed, or otherwise modified by", "TPA": "Part of a set", "TPB": "Publisher", "TRC": "ISRC (International Standard Recording Code)", "TRD": "Recording dates", "TRK": "Track number/Position in set", "TSI": "Size", "TSS": "Software/hardware and settings used for encoding", "TT1": "Content group description", "TT2": "Title/Songname/Content description", "TT3": "Subtitle/Description refinement", "TXT": "Lyricist/text writer", "TXX": "User defined text information frame", "TYE": "Year", "UFI": "Unique file identifier", "ULT": "Unsychronized lyric/text transcription", "WAF": "Official audio file webpage", "WAR": "Official artist/performer webpage", "WAS": "Official audio source webpage", "WCM": "Commercial information", "WCP": "Copyright/Legal information", "WPB": "Publishers official webpage", "WXX": "User defined URL link frame", // v2.3 "AENC": "Audio encryption", "APIC": "Attached picture", "ASPI": "Audio seek point index", "CHAP": "Chapter", "CTOC": "Table of contents", "COMM": "Comments", "COMR": "Commercial frame", "ENCR": "Encryption method registration", "EQU2": "Equalisation (2)", "EQUA": "Equalization", "ETCO": "Event timing codes", "GEOB": "General encapsulated object", "GRID": "Group identification registration", "IPLS": "Involved people list", "LINK": "Linked information", "MCDI": "Music CD identifier", "MLLT": "MPEG location lookup table", "OWNE": "Ownership frame", "PRIV": "Private frame", "PCNT": "Play counter", "POPM": "Popularimeter", "POSS": "Position synchronisation frame", "RBUF": "Recommended buffer size", "RVA2": "Relative volume adjustment (2)", "RVAD": "Relative volume adjustment", "RVRB": "Reverb", "SEEK": "Seek frame", "SYLT": "Synchronized lyric/text", "SYTC": "Synchronized tempo codes", "TALB": "Album/Movie/Show title", "TBPM": "BPM (beats per minute)", "TCOM": "Composer", "TCON": "Content type", "TCOP": "Copyright message", "TDAT": "Date", "TDLY": "Playlist delay", "TDRC": "Recording time", "TDRL": "Release time", "TDTG": "Tagging time", "TENC": "Encoded by", "TEXT": "Lyricist/Text writer", "TFLT": "File type", "TIME": "Time", "TIPL": "Involved people list", "TIT1": "Content group description", "TIT2": "Title/songname/content description", "TIT3": "Subtitle/Description refinement", "TKEY": "Initial key", "TLAN": "Language(s)", "TLEN": "Length", "TMCL": "Musician credits list", "TMED": "Media type", "TMOO": "Mood", "TOAL": "Original album/movie/show title", "TOFN": "Original filename", "TOLY": "Original lyricist(s)/text writer(s)", "TOPE": "Original artist(s)/performer(s)", "TORY": "Original release year", "TOWN": "File owner/licensee", "TPE1": "Lead performer(s)/Soloist(s)", "TPE2": "Band/orchestra/accompaniment", "TPE3": "Conductor/performer refinement", "TPE4": "Interpreted, remixed, or otherwise modified by", "TPOS": "Part of a set", "TPRO": "Produced notice", "TPUB": "Publisher", "TRCK": "Track number/Position in set", "TRDA": "Recording dates", "TRSN": "Internet radio station name", "TRSO": "Internet radio station owner", "TSOA": "Album sort order", "TSOP": "Performer sort order", "TSOT": "Title sort order", "TSIZ": "Size", "TSRC": "ISRC (international standard recording code)", "TSSE": "Software/Hardware and settings used for encoding", "TSST": "Set subtitle", "TYER": "Year", "TXXX": "User defined text information frame", "UFID": "Unique file identifier", "USER": "Terms of use", "USLT": "Unsychronized lyric/text transcription", "WCOM": "Commercial information", "WCOP": "Copyright/Legal information", "WOAF": "Official audio file webpage", "WOAR": "Official artist/performer webpage", "WOAS": "Official audio source webpage", "WORS": "Official internet radio station homepage", "WPAY": "Payment", "WPUB": "Publishers official webpage", "WXXX": "User defined URL link frame" }; export default ExtractID3; ================================================ FILE: src/core/operations/ExtractIPAddresses.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { search } from "../lib/Extract.mjs"; import { ipSort } from "../lib/Sort.mjs"; /** * Extract IP addresses operation */ class ExtractIPAddresses extends Operation { /** * ExtractIPAddresses constructor */ constructor() { super(); this.name = "Extract IP addresses"; this.module = "Regex"; this.description = "Extracts all IPv4 and IPv6 addresses.

Warning: Given a string 1.2.3.4.5.6.7.8, this will match 1.2.3.4 and 5.6.7.8 so always check the original input!"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "IPv4", type: "boolean", value: true }, { name: "IPv6", type: "boolean", value: false }, { name: "Remove local IPv4 addresses", type: "boolean", value: false }, { name: "Display total", type: "boolean", value: false }, { name: "Sort", type: "boolean", value: false }, { name: "Unique", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args, // IPv4 decimal groups can have values 0 to 255. To construct a regex the following sub-regex is reused: ipv4DecimalByte = "(?:25[0-5]|2[0-4]\\d|1?[0-9]\\d|\\d)", ipv4OctalByte = "(?:0[1-3]?[0-7]{1,2})", // Look behind and ahead will be used to exclude matches with additional decimal digits left and right of IP address lookBehind = "(? option !== "") .map((option) => COLOUR_OPTIONS.indexOf(option)), parsedImage = await Jimp.read(input), width = parsedImage.bitmap.width, height = parsedImage.bitmap.height, rgba = parsedImage.bitmap.data; if (bit < 0 || bit > 7) { throw new OperationError( "Error: Bit argument must be between 0 and 7", ); } let i, combinedBinary = ""; if (pixelOrder === "Row") { for (i = 0; i < rgba.length; i += 4) { for (const colour of colours) { combinedBinary += Utils.bin(rgba[i + colour])[bit]; } } } else { let rowWidth; const pixelWidth = width * 4; for (let col = 0; col < width; col++) { for (let row = 0; row < height; row++) { rowWidth = row * pixelWidth; for (const colour of colours) { i = rowWidth + (col + colour * 4); combinedBinary += Utils.bin(rgba[i])[bit]; } } } } return fromBinary(combinedBinary); } } const COLOUR_OPTIONS = ["R", "G", "B", "A"]; export default ExtractLSB; ================================================ FILE: src/core/operations/ExtractMACAddresses.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { search } from "../lib/Extract.mjs"; import { hexadecimalSort } from "../lib/Sort.mjs"; /** * Extract MAC addresses operation */ class ExtractMACAddresses extends Operation { /** * ExtractMACAddresses constructor */ constructor() { super(); this.name = "Extract MAC addresses"; this.module = "Regex"; this.description = "Extracts all Media Access Control (MAC) addresses from the input."; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Display total", type: "boolean", value: false }, { name: "Sort", type: "boolean", value: false }, { name: "Unique", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [displayTotal, sort, unique] = args, regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig, results = search( input, regex, null, sort ? hexadecimalSort : null, unique ); if (displayTotal) { return `Total found: ${results.length}\n\n${results.join("\n")}`; } else { return results.join("\n"); } } } export default ExtractMACAddresses; ================================================ FILE: src/core/operations/ExtractRGBA.mjs ================================================ /** * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { Jimp } from "jimp"; import { RGBA_DELIM_OPTIONS } from "../lib/Delim.mjs"; /** * Extract RGBA operation */ class ExtractRGBA extends Operation { /** * ExtractRGBA constructor */ constructor() { super(); this.name = "Extract RGBA"; this.module = "Image"; this.description = "Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data."; this.infoURL = "https://wikipedia.org/wiki/RGBA_color_space"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Delimiter", type: "editableOption", value: RGBA_DELIM_OPTIONS, }, { name: "Include Alpha", type: "boolean", value: true, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ async run(input, args) { if (!isImage(input)) throw new OperationError("Please enter a valid image file."); const delimiter = args[0], includeAlpha = args[1], parsedImage = await Jimp.read(input); let bitmap = parsedImage.bitmap.data; bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3); return bitmap.join(delimiter); } } export default ExtractRGBA; ================================================ FILE: src/core/operations/ExtractURLs.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { search, URL_REGEX } from "../lib/Extract.mjs"; import { caseInsensitiveSort } from "../lib/Sort.mjs"; /** * Extract URLs operation */ class ExtractURLs extends Operation { /** * ExtractURLs constructor */ constructor() { super(); this.name = "Extract URLs"; this.module = "Regex"; 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."; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Display total", type: "boolean", value: false }, { name: "Sort", type: "boolean", value: false }, { name: "Unique", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [displayTotal, sort, unique] = args; const results = search( input, URL_REGEX, null, sort ? caseInsensitiveSort : null, unique ); if (displayTotal) { return `Total found: ${results.length}\n\n${results.join("\n")}`; } else { return results.join("\n"); } } } export default ExtractURLs; ================================================ FILE: src/core/operations/FangURL.mjs ================================================ /** * @author arnydo [github@arnydo.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * FangURL operation */ class FangURL extends Operation { /** * FangURL constructor */ constructor() { super(); this.name = "Fang URL"; this.module = "Default"; 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."; this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Restore [.]", type: "boolean", value: true }, { name: "Restore hxxp", type: "boolean", value: true }, { name: "Restore ://", type: "boolean", value: true } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [dots, http, slashes] = args; input = fangURL(input, dots, http, slashes); return input; } } /** * Defangs a given URL * * @param {string} url * @param {boolean} dots * @param {boolean} http * @param {boolean} slashes * @returns {string} */ function fangURL(url, dots, http, slashes) { if (dots) url = url.replace(/\[\.\]/g, "."); if (http) url = url.replace(/hxxp/g, "http"); if (slashes) url = url.replace(/\[:\/\/\]/g, "://"); return url; } export default FangURL; ================================================ FILE: src/core/operations/FernetDecrypt.mjs ================================================ /** * @author Karsten Silkenbäumer [github.com/kassi] * @copyright Karsten Silkenbäumer 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import fernet from "fernet"; /** * FernetDecrypt operation */ class FernetDecrypt extends Operation { /** * FernetDecrypt constructor */ constructor() { super(); this.name = "Fernet Decrypt"; this.module = "Default"; 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().

Key: The key must be 32 bytes (256 bits) encoded with Base64."; this.infoURL = "https://asecuritysite.com/encryption/fer"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "string", "value": "" }, ]; this.patterns = [ { match: "^[A-Z\\d\\-_=]{20,}$", flags: "i", args: [] }, ]; } /** * @param {String} input * @param {Object[]} args * @returns {String} */ run(input, args) { const [secretInput] = args; try { const secret = new fernet.Secret(secretInput); const token = new fernet.Token({ secret: secret, token: input, ttl: 0 }); return token.decode(); } catch (err) { throw new OperationError(err); } } } export default FernetDecrypt; ================================================ FILE: src/core/operations/FernetEncrypt.mjs ================================================ /** * @author Karsten Silkenbäumer [github.com/kassi] * @copyright Karsten Silkenbäumer 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import fernet from "fernet"; /** * FernetEncrypt operation */ class FernetEncrypt extends Operation { /** * FernetEncrypt constructor */ constructor() { super(); this.name = "Fernet Encrypt"; this.module = "Default"; 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().

Key: The key must be 32 bytes (256 bits) encoded with Base64."; this.infoURL = "https://asecuritysite.com/encryption/fer"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "string", "value": "" }, ]; } /** * @param {String} input * @param {Object[]} args * @returns {String} */ run(input, args) { const [secretInput] = args; try { const secret = new fernet.Secret(secretInput); const token = new fernet.Token({ secret: secret, }); return token.encode(input); } catch (err) { throw new OperationError(err); } } } export default FernetEncrypt; ================================================ FILE: src/core/operations/FileTree.mjs ================================================ /** * @author sw5678 * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * Unique operation */ class FileTree extends Operation { /** * Unique constructor */ constructor() { super(); this.name = "File Tree"; this.module = "Default"; this.description = "Creates a file tree from a list of file paths (similar to the tree command in Linux)"; this.infoURL = "https://wikipedia.org/wiki/Tree_(command)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "File Path Delimiter", type: "binaryString", value: "/" }, { name: "Delimiter", type: "option", value: INPUT_DELIM_OPTIONS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { // Set up arrow and pipe for nice output display const ARROW = "|---"; const PIPE = "| "; // Get args from input const fileDelim = args[0]; const entryDelim = Utils.charRep(args[1]); // Store path to print const completedList = []; const printList = []; // Loop through all entries const filePaths = input.split(entryDelim).unique().sort(); for (let i = 0; i < filePaths.length; i++) { // Split by file delimiter let path = filePaths[i].split(fileDelim); if (path[0] === "") { path = path.slice(1, path.length); } for (let j = 0; j < path.length; j++) { let printLine; let key; if (j === 0) { printLine = path[j]; key = path[j]; } else { printLine = PIPE.repeat(j-1) + ARROW + path[j]; key = path.slice(0, j+1).join("/"); } // Check to see we have already added that path if (!completedList.includes(key)) { completedList.push(key); printList.push(printLine); } } } return printList.join("\n"); } } export default FileTree; ================================================ FILE: src/core/operations/Filter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; import OperationError from "../errors/OperationError.mjs"; import XRegExp from "xregexp"; /** * Filter operation */ class Filter extends Operation { /** * Filter constructor */ constructor() { super(); this.name = "Filter"; this.module = "Regex"; this.description = "Splits up the input using the specified delimiter and then filters each branch based on a regular expression."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": INPUT_DELIM_OPTIONS }, { "name": "Regex", "type": "string", "value": "" }, { "name": "Invert condition", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = Utils.charRep(args[0]), reverse = args[2]; let regex; try { regex = new XRegExp(args[1]); } catch (err) { throw new OperationError(`Invalid regex. Details: ${err.message}`); } const regexFilter = function(value) { return reverse ^ regex.test(value); }; return input.split(delim).filter(regexFilter).join(delim); } } export default Filter; ================================================ FILE: src/core/operations/FindReplace.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import XRegExp from "xregexp"; /** * Find / Replace operation */ class FindReplace extends Operation { /** * FindReplace constructor */ constructor() { super(); this.name = "Find / Replace"; this.module = "Regex"; this.description = "Replaces all occurrences of the first string with the second.

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)."; this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Find", "type": "toggleString", "value": "", "toggleValues": ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"] }, { "name": "Replace", "type": "binaryString", "value": "" }, { "name": "Global match", "type": "boolean", "value": true }, { "name": "Case insensitive", "type": "boolean", "value": false }, { "name": "Multiline matching", "type": "boolean", "value": true }, { "name": "Dot matches all", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [{option: type}, replace, g, i, m, s] = args; let find = args[0].string, modifiers = ""; if (g) modifiers += "g"; if (i) modifiers += "i"; if (m) modifiers += "m"; if (s) modifiers += "s"; if (type === "Regex") { find = new XRegExp(find, modifiers); return input.replace(find, replace); } if (type.indexOf("Extended") === 0) { find = Utils.parseEscapedChars(find); } find = new XRegExp(Utils.escapeRegex(find), modifiers); return input.replace(find, replace); } } export default FindReplace; ================================================ FILE: src/core/operations/FlaskSessionDecode.mjs ================================================ /** * @author ThePlayer372-FR [] * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { fromBase64 } from "../lib/Base64.mjs"; /** * Flask Session Decode operation */ class FlaskSessionDecode extends Operation { /** * FlaskSessionDecode constructor */ constructor() { super(); this.name = "Flask Session Decode"; this.module = "Crypto"; this.description = "Decodes the payload of a Flask session cookie (itsdangerous) into JSON."; this.inputType = "string"; this.outputType = "JSON"; this.args = [ { name: "View TimeStamp", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {Object[]} */ run(input, args) { input = input.trim(); const parts = input.split("."); if (parts.length !== 3) { throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature"); } const payloadB64 = parts[0]; const time = parts[1]; const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/"); const binary = fromBase64(timeB64); const bytes = new Uint8Array(4); for (let i = 0; i < 4; i++) { bytes[i] = binary.charCodeAt(i); } const view = new DataView(bytes.buffer); const timestamp = view.getInt32(0, false); const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/"); const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "="); let payloadJson; try { payloadJson = fromBase64(padded); } catch (e) { throw new OperationError("Invalid Base64 payload"); } try { let data = JSON.parse(payloadJson); if (args[0]) { data = {payload: data, timestamp: timestamp}; } return data; } catch (e) { throw new OperationError("Unable to decode JSON payload: " + e.message); } } } export default FlaskSessionDecode; ================================================ FILE: src/core/operations/FlaskSessionSign.mjs ================================================ /** * @author ThePlayer372-FR [] * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import CryptoApi from "crypto-api/src/crypto-api.mjs"; import Utils from "../Utils.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Flask Session Sign operation */ class FlaskSessionSign extends Operation { /** * FlaskSessionSign constructor */ constructor() { super(); this.name = "Flask Session Sign"; this.module = "Crypto"; this.description = "Signs a JSON payload to produce a Flask session cookie (itsdangerous HMAC)."; this.inputType = "JSON"; this.outputType = "string"; this.args = [ { name: "Key", type: "toggleString", value: "", toggleValues: ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"] }, { name: "Salt", type: "toggleString", value: "cookie-session", toggleValues: ["UTF8", "Hex", "Decimal", "Binary", "Base64", "Latin1"] }, { name: "Algorithm", type: "option", value: ["sha1", "sha256"], } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!args[0].string) { throw new OperationError("Secret key required"); } const key = Utils.convertToByteString(args[0].string, args[0].option); const salt = Utils.convertToByteString(args[1].string || "cookie-session", args[1].option); const algorithm = args[2] || "sha1"; const payloadB64 = toBase64(Utils.strToByteArray(JSON.stringify(input))); const payload = payloadB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm)); derivedKey.update(salt); const currentTimeStamp = Math.ceil(Date.now() / 1000); const buffer = new ArrayBuffer(4); const view = new DataView(buffer); view.setInt32(0, currentTimeStamp, false); const bytes = new Uint8Array(buffer); let binary = ""; bytes.forEach(b => binary += String.fromCharCode(b)); const timeB64 = toBase64(Utils.strToByteArray(binary)); const time = timeB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); const data = Utils.convertToByteString(payload + "." + time, "utf8"); const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm)); sign.update(data); const signB64 = toBase64(sign.finalize()); const sign64 = signB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); return payload + "." + time + "." + sign64; } } export default FlaskSessionSign; ================================================ FILE: src/core/operations/FlaskSessionVerify.mjs ================================================ /** * @author ThePlayer372-FR [] * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import CryptoApi from "crypto-api/src/crypto-api.mjs"; import Utils from "../Utils.mjs"; import { toBase64, fromBase64 } from "../lib/Base64.mjs"; /** * Flask Session Verify operation */ class FlaskSessionVerify extends Operation { /** * FlaskSessionVerify constructor */ constructor() { super(); this.name = "Flask Session Verify"; this.module = "Crypto"; this.description = "Verifies the HMAC signature of a Flask session cookie (itsdangerous) generated."; this.inputType = "string"; this.outputType = "JSON"; this.args = [ { name: "Key", type: "toggleString", value: "", toggleValues: ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"] }, { name: "Salt", type: "toggleString", value: "cookie-session", toggleValues: ["UTF8", "Hex", "Decimal", "Binary", "Base64", "Latin1"] }, { name: "Algorithm", type: "option", value: ["sha1", "sha256"], }, { name: "View TimeStamp", type: "boolean", value: true } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!args[0].string) { throw new OperationError("Secret key required"); } const key = Utils.convertToByteString(args[0].string, args[0].option); const salt = Utils.convertToByteString(args[1].string || "cookie-session", args[1].option); const algorithm = args[2] || "sha1"; input = input.trim(); const parts = input.split("."); if (parts.length !== 3) { throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature"); } const data = Utils.convertToByteString(parts[0] + "." + parts[1], "utf8"); const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm)); derivedKey.update(salt); const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm)); sign.update(data); const payloadB64 = parts[0]; const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/"); const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "="); const time = parts[1]; const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/"); const binary = fromBase64(timeB64); const bytes = new Uint8Array(4); for (let i = 0; i < 4; i++) { bytes[i] = binary.charCodeAt(i); } const view = new DataView(bytes.buffer); const timestamp = view.getInt32(0, false); let payloadJson; try { payloadJson = fromBase64(padded); } catch (e) { throw new OperationError("Invalid Base64 payload"); } const signB64 = toBase64(sign.finalize()); const sign64 = signB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); if (sign64 !== parts[2]) { throw new OperationError("Invalid signature!"); } try { const decoded = JSON.parse(payloadJson); if (!args[3]) { return { valid: true, payload: decoded, }; } else { return { valid: true, payload: decoded, timestamp: timestamp }; } } catch (e) { throw new OperationError("Unable to decode JSON payload: " + e.message); } } } export default FlaskSessionVerify; ================================================ FILE: src/core/operations/Fletcher16Checksum.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Fletcher-16 Checksum operation */ class Fletcher16Checksum extends Operation { /** * Fletcher16Checksum constructor */ constructor() { super(); this.name = "Fletcher-16 Checksum"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-16"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { let a = 0, b = 0; input = new Uint8Array(input); for (let i = 0; i < input.length; i++) { a = (a + input[i]) % 0xff; b = (b + a) % 0xff; } return Utils.hex(((b << 8) | a) >>> 0, 4); } } export default Fletcher16Checksum; ================================================ FILE: src/core/operations/Fletcher32Checksum.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Fletcher-32 Checksum operation */ class Fletcher32Checksum extends Operation { /** * Fletcher32Checksum constructor */ constructor() { super(); this.name = "Fletcher-32 Checksum"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-32"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { let a = 0, b = 0; if (ArrayBuffer.isView(input)) { input = new DataView(input.buffer, input.byteOffset, input.byteLength); } else { input = new DataView(input); } for (let i = 0; i < input.byteLength - 1; i += 2) { a = (a + input.getUint16(i, true)) % 0xffff; b = (b + a) % 0xffff; } if (input.byteLength % 2 !== 0) { a = (a + input.getUint8(input.byteLength - 1)) % 0xffff; b = (b + a) % 0xffff; } return Utils.hex(((b << 16) | a) >>> 0, 8); } } export default Fletcher32Checksum; ================================================ FILE: src/core/operations/Fletcher64Checksum.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Fletcher-64 Checksum operation */ class Fletcher64Checksum extends Operation { /** * Fletcher64Checksum constructor */ constructor() { super(); this.name = "Fletcher-64 Checksum"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-64"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { let a = 0, b = 0; if (ArrayBuffer.isView(input)) { input = new DataView(input.buffer, input.byteOffset, input.byteLength); } else { input = new DataView(input); } for (let i = 0; i < input.byteLength - 3; i += 4) { a = (a + input.getUint32(i, true)) % 0xffffffff; b = (b + a) % 0xffffffff; } if (input.byteLength % 4 !== 0) { let lastValue = 0; for (let i = 0; i < input.byteLength % 4; i++) { lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i); } a = (a + lastValue) % 0xffffffff; b = (b + a) % 0xffffffff; } return Utils.hex(b >>> 0, 8) + Utils.hex(a >>> 0, 8); } } export default Fletcher64Checksum; ================================================ FILE: src/core/operations/Fletcher8Checksum.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Fletcher-8 Checksum operation */ class Fletcher8Checksum extends Operation { /** * Fletcher8Checksum constructor */ constructor() { super(); this.name = "Fletcher-8 Checksum"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { let a = 0, b = 0; input = new Uint8Array(input); for (let i = 0; i < input.length; i++) { a = (a + input[i]) % 0xf; b = (b + a) % 0xf; } return Utils.hex(((b << 4) | a) >>> 0, 2); } } export default Fletcher8Checksum; ================================================ FILE: src/core/operations/FlipImage.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Flip Image operation */ class FlipImage extends Operation { /** * FlipImage constructor */ constructor() { super(); this.name = "Flip Image"; this.module = "Image"; this.description = "Flips an image along its X or Y axis."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Axis", type: "option", value: ["Horizontal", "Vertical"], }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { const [flipAxis] = args; if (!isImage(input)) { throw new OperationError("Invalid input file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (isWorkerEnvironment()) self.sendStatusMessage("Flipping image..."); switch (flipAxis) { case "Horizontal": image.flip({ horizontal: true, vertical: false, }); break; case "Vertical": image.flip({ horizontal: false, vertical: true, }); break; } let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error flipping image. (${err})`); } } /** * Displays the flipped image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default FlipImage; ================================================ FILE: src/core/operations/Fork.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Recipe from "../Recipe.mjs"; import Dish from "../Dish.mjs"; /** * Fork operation */ class Fork extends Operation { /** * Fork constructor */ constructor() { super(); this.name = "Fork"; this.flowControl = true; this.module = "Default"; this.description = "Split the input data up based on the specified delimiter and run all subsequent operations on each branch separately.

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."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Split delimiter", "type": "binaryShortString", "value": "\\n" }, { "name": "Merge delimiter", "type": "binaryShortString", "value": "\\n" }, { "name": "Ignore errors", "type": "boolean", "value": false } ]; } /** * @param {Object} state - The current state of the recipe. * @param {number} state.progress - The current position in the recipe. * @param {Dish} state.dish - The Dish being operated on. * @param {Operation[]} state.opList - The list of operations in the recipe. * @returns {Object} The updated state of the recipe. */ async run(state) { const opList = state.opList, inputType = opList[state.progress].inputType, outputType = opList[state.progress].outputType, input = await state.dish.get(inputType), ings = opList[state.progress].ingValues, [splitDelim, mergeDelim, ignoreErrors] = ings, subOpList = []; let inputs = [], i; if (input) inputs = input.split(splitDelim); // Set to 1 as if we are here, then there is one, the current one. let numOp = 1; // Create subOpList for each tranche to operate on // all remaining operations unless we encounter a Merge for (i = state.progress + 1; i < opList.length; i++) { if (opList[i].name === "Merge" && !opList[i].disabled) { numOp--; if (numOp === 0 || opList[i].ingValues[0]) break; else // Not this Fork's Merge. subOpList.push(opList[i]); } else { if (opList[i].name === "Fork" || opList[i].name === "Subsection") numOp++; subOpList.push(opList[i]); } } const recipe = new Recipe(); const outputs = []; let progress = 0; state.forkOffset += state.progress + 1; recipe.addOperations(subOpList); // Take a deep(ish) copy of the ingredient values const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues))); // Run recipe over each tranche for (i = 0; i < inputs.length; i++) { // Baseline ing values for each tranche so that registers are reset recipe.opList.forEach((op, i) => { op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); }); const dish = new Dish(); dish.set(inputs[i], inputType); try { progress = await recipe.execute(dish, 0, state); } catch (err) { if (!ignoreErrors) { throw err; } progress = err.progress + 1; } outputs.push(await dish.get(outputType)); } state.dish.set(outputs.join(mergeDelim), outputType); state.progress += progress; return state; } } export default Fork; ================================================ FILE: src/core/operations/FormatMACAddresses.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Format MAC addresses operation */ class FormatMACAddresses extends Operation { /** * FormatMACAddresses constructor */ constructor() { super(); this.name = "Format MAC addresses"; this.module = "Default"; this.description = "Displays given MAC addresses in multiple different formats.

Expects addresses in a list separated by newlines, spaces or commas.

WARNING: There are no validity checks."; this.infoURL = "https://wikipedia.org/wiki/MAC_address#Notational_conventions"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Output case", "type": "option", "value": ["Both", "Upper only", "Lower only"] }, { "name": "No delimiter", "type": "boolean", "value": true }, { "name": "Dash delimiter", "type": "boolean", "value": true }, { "name": "Colon delimiter", "type": "boolean", "value": true }, { "name": "Cisco style", "type": "boolean", "value": false }, { "name": "IPv6 interface ID", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!input) return ""; const [ outputCase, noDelim, dashDelim, colonDelim, ciscoStyle, ipv6IntID ] = args, outputList = [], macs = input.toLowerCase().split(/[,\s\r\n]+/); macs.forEach(function(mac) { const cleanMac = mac.replace(/[:.-]+/g, ""), macHyphen = cleanMac.replace(/(.{2}(?=.))/g, "$1-"), macColon = cleanMac.replace(/(.{2}(?=.))/g, "$1:"), macCisco = cleanMac.replace(/(.{4}(?=.))/g, "$1."); let macIPv6 = cleanMac.slice(0, 6) + "fffe" + cleanMac.slice(6); macIPv6 = macIPv6.replace(/(.{4}(?=.))/g, "$1:"); let bite = parseInt(macIPv6.slice(0, 2), 16) ^ 2; bite = bite.toString(16).padStart(2, "0"); macIPv6 = bite + macIPv6.slice(2); if (outputCase === "Lower only") { if (noDelim) outputList.push(cleanMac); if (dashDelim) outputList.push(macHyphen); if (colonDelim) outputList.push(macColon); if (ciscoStyle) outputList.push(macCisco); if (ipv6IntID) outputList.push(macIPv6); } else if (outputCase === "Upper only") { if (noDelim) outputList.push(cleanMac.toUpperCase()); if (dashDelim) outputList.push(macHyphen.toUpperCase()); if (colonDelim) outputList.push(macColon.toUpperCase()); if (ciscoStyle) outputList.push(macCisco.toUpperCase()); if (ipv6IntID) outputList.push(macIPv6.toUpperCase()); } else { if (noDelim) outputList.push(cleanMac, cleanMac.toUpperCase()); if (dashDelim) outputList.push(macHyphen, macHyphen.toUpperCase()); if (colonDelim) outputList.push(macColon, macColon.toUpperCase()); if (ciscoStyle) outputList.push(macCisco, macCisco.toUpperCase()); if (ipv6IntID) outputList.push(macIPv6, macIPv6.toUpperCase()); } outputList.push( "" // Empty line to delimit groups ); }); // Return the data as a string return outputList.join("\n"); } } export default FormatMACAddresses; ================================================ FILE: src/core/operations/FrequencyDistribution.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Frequency distribution operation */ class FrequencyDistribution extends Operation { /** * FrequencyDistribution constructor */ constructor() { super(); this.name = "Frequency distribution"; this.module = "Default"; this.description = "Displays the distribution of bytes in the data as a graph."; this.infoURL = "https://wikipedia.org/wiki/Frequency_distribution"; this.inputType = "ArrayBuffer"; this.outputType = "json"; this.presentType = "html"; this.args = [ { "name": "Show 0%s", "type": "boolean", "value": true }, { "name": "Show ASCII", "type": "boolean", "value": true } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {json} */ run(input, args) { const data = new Uint8Array(input); if (!data.length) throw new OperationError("No data"); const distrib = new Array(256).fill(0), percentages = new Array(256), len = data.length; let i; // Count bytes for (i = 0; i < len; i++) { distrib[data[i]]++; } // Calculate percentages let repr = 0; for (i = 0; i < 256; i++) { if (distrib[i] > 0) repr++; percentages[i] = distrib[i] / len * 100; } return { "dataLength": len, "percentages": percentages, "distribution": distrib, "bytesRepresented": repr }; } /** * Displays the frequency distribution as a bar chart for web apps. * * @param {json} freq * @returns {html} */ present(freq, args) { const [showZeroes, showAscii] = args; // Print let output = `
Total data length: ${freq.dataLength} Number of bytes represented: ${freq.bytesRepresented} Number of bytes not represented: ${256 - freq.bytesRepresented} ${showAscii ? "" : ""}`; for (let i = 0; i < 256; i++) { if (freq.distribution[i] || showZeroes) { let c = ""; if (showAscii) { if (i <= 32) { c = String.fromCharCode(0x2400 + i); } else if (i === 127) { c = String.fromCharCode(0x2421); } else { c = String.fromCharCode(i); } } const bite = ``, ascii = showAscii ? `` : "", percentage = ``, bars = ``; output += `${bite}${ascii}${percentage}${bars}`; } } output += "
ByteASCIIPercentage
${Utils.hex(i, 2)}${c}${(freq.percentages[i].toFixed(2).replace(".00", "") + "%").padEnd(8, " ")}${Array(Math.ceil(freq.percentages[i])+1).join("|")}
"; return output; } } export default FrequencyDistribution; ================================================ FILE: src/core/operations/FromBCD.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD.mjs"; import BigNumber from "bignumber.js"; /** * From BCD operation */ class FromBCD extends Operation { /** * FromBCD constructor */ constructor() { super(); this.name = "From BCD"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Binary-coded_decimal"; this.inputType = "string"; this.outputType = "BigNumber"; this.args = [ { "name": "Scheme", "type": "option", "value": ENCODING_SCHEME }, { "name": "Packed", "type": "boolean", "value": true }, { "name": "Signed", "type": "boolean", "value": false }, { "name": "Input format", "type": "option", "value": FORMAT } ]; this.checks = [ { pattern: "^(?:\\d{4} ){3,}\\d{4}$", flags: "", args: ["8 4 2 1", true, false, "Nibbles"] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {BigNumber} */ run(input, args) { const encoding = ENCODING_LOOKUP[args[0]], packed = args[1], signed = args[2], inputFormat = args[3], nibbles = []; let output = "", byteArray; // Normalise the input switch (inputFormat) { case "Nibbles": case "Bytes": input = input.replace(/\s/g, ""); for (let i = 0; i < input.length; i += 4) { nibbles.push(parseInt(input.substr(i, 4), 2)); } break; case "Raw": default: byteArray = new Uint8Array(Utils.strToArrayBuffer(input)); byteArray.forEach(b => { nibbles.push(b >>> 4); nibbles.push(b & 15); }); break; } if (!packed) { // Discard each high nibble for (let i = 0; i < nibbles.length; i++) { nibbles.splice(i, 1); // lgtm [js/loop-iteration-skipped-due-to-shifting] } } if (signed) { const sign = nibbles.pop(); if (sign === 13 || sign === 11) { // Negative output += "-"; } } nibbles.forEach(n => { if (isNaN(n)) throw new OperationError("Invalid input"); const val = encoding.indexOf(n); if (val < 0) throw new OperationError(`Value ${Utils.bin(n, 4)} is not in the encoding scheme`); output += val.toString(); }); return new BigNumber(output); } } export default FromBCD; ================================================ FILE: src/core/operations/FromBase.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import BigNumber from "bignumber.js"; import OperationError from "../errors/OperationError.mjs"; /** * From Base operation */ class FromBase extends Operation { /** * FromBase constructor */ constructor() { super(); this.name = "From Base"; this.module = "Default"; this.description = "Converts a number to decimal from a given numerical base."; this.infoURL = "https://wikipedia.org/wiki/Radix"; this.inputType = "string"; this.outputType = "BigNumber"; this.args = [ { "name": "Radix", "type": "number", "value": 36 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {BigNumber} */ run(input, args) { const radix = args[0]; if (radix < 2 || radix > 36) { throw new OperationError("Error: Radix argument must be between 2 and 36"); } const number = input.replace(/\s/g, "").split("."); let result = new BigNumber(number[0], radix); if (number.length === 1) return result; // Fractional part for (let i = 0; i < number[1].length; i++) { const digit = new BigNumber(number[1][i], radix); result += digit.div(Math.pow(radix, i+1)); } return result; } } export default FromBase; ================================================ FILE: src/core/operations/FromBase32.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {ALPHABET_OPTIONS} from "../lib/Base32.mjs"; /** * From Base32 operation */ class FromBase32 extends Operation { /** * FromBase32 constructor */ constructor() { super(); this.name = "From Base32"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Base32"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { name: "Alphabet", type: "editableOption", value: ALPHABET_OPTIONS }, { name: "Remove non-alphabet chars", type: "boolean", value: true } ]; this.checks = [ { 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})?$", flags: "", args: ["A-Z2-7=", false] }, { 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})?$", flags: "", args: ["0-9A-V=", false] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { if (!input) return []; const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=", removeNonAlphChars = args[1], output = []; let chr1, chr2, chr3, chr4, chr5, enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, i = 0; if (removeNonAlphChars) { const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g"); input = input.replace(re, ""); } while (i < input.length) { enc1 = alphabet.indexOf(input.charAt(i++)); enc2 = alphabet.indexOf(input.charAt(i++) || "="); enc3 = alphabet.indexOf(input.charAt(i++) || "="); enc4 = alphabet.indexOf(input.charAt(i++) || "="); enc5 = alphabet.indexOf(input.charAt(i++) || "="); enc6 = alphabet.indexOf(input.charAt(i++) || "="); enc7 = alphabet.indexOf(input.charAt(i++) || "="); enc8 = alphabet.indexOf(input.charAt(i++) || "="); chr1 = (enc1 << 3) | (enc2 >> 2); chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4); chr3 = ((enc4 & 15) << 4) | (enc5 >> 1); chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3); chr5 = ((enc7 & 7) << 5) | enc8; output.push(chr1); if ((enc2 & 3) !== 0 || enc3 !== 32) output.push(chr2); if ((enc4 & 15) !== 0 || enc5 !== 32) output.push(chr3); if ((enc5 & 1) !== 0 || enc6 !== 32) output.push(chr4); if ((enc7 & 7) !== 0 || enc8 !== 32) output.push(chr5); } return output; } } export default FromBase32; ================================================ FILE: src/core/operations/FromBase45.mjs ================================================ /** * @author Thomas Weißschuh [thomas@t-8ch.de] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import {ALPHABET, highlightToBase45, highlightFromBase45} from "../lib/Base45.mjs"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; /** * From Base45 operation */ class FromBase45 extends Operation { /** * FromBase45 constructor */ constructor() { super(); this.name = "From Base45"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { name: "Alphabet", type: "string", value: ALPHABET }, { name: "Remove non-alphabet chars", type: "boolean", value: true }, ]; this.highlight = highlightFromBase45; this.highlightReverse = highlightToBase45; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { if (!input) return []; const alphabet = Utils.expandAlphRange(args[0]).join(""); const removeNonAlphChars = args[1]; const res = []; // Remove non-alphabet characters if (removeNonAlphChars) { const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); input = input.replace(re, ""); } for (const triple of Utils.chunked(input, 3)) { triple.reverse(); let b = 0; for (const c of triple) { const idx = alphabet.indexOf(c); if (idx === -1) { throw new OperationError(`Character not in alphabet: '${c}'`); } b *= 45; b += idx; } if (b > 65535) { throw new OperationError(`Triplet too large: '${triple.join("")}'`); } if (triple.length > 2) { /** * The last triple may only have 2 bytes so we push the MSB when we got 3 bytes * Pushing MSB */ res.push(b >> 8); } /** * Pushing LSB */ res.push(b & 0xff); } return res; } } export default FromBase45; ================================================ FILE: src/core/operations/FromBase58.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import {ALPHABET_OPTIONS} from "../lib/Base58.mjs"; /** * From Base58 operation */ class FromBase58 extends Operation { /** * FromBase58 constructor */ constructor() { super(); this.name = "From Base58"; this.module = "Default"; 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.

This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included) back into its raw form.

e.g. StV1DL6CwTryKyV becomes hello world

Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc)."; this.infoURL = "https://wikipedia.org/wiki/Base58"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { "name": "Alphabet", "type": "editableOption", "value": ALPHABET_OPTIONS }, { "name": "Remove non-alphabet chars", "type": "boolean", "value": true } ]; this.checks = [ { pattern: "^[1-9A-HJ-NP-Za-km-z]{20,}$", flags: "", args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", false] }, { pattern: "^[1-9A-HJ-NP-Za-km-z]{20,}$", flags: "", args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", false] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { let alphabet = args[0] || ALPHABET_OPTIONS[0].value; const removeNonAlphaChars = args[1] === undefined ? true : args[1], result = []; alphabet = Utils.expandAlphRange(alphabet).join(""); if (alphabet.length !== 58 || [].unique.call(alphabet).length !== 58) { throw new OperationError("Alphabet must be of length 58"); } if (input.length === 0) return []; let zeroPrefix = 0; for (let i = 0; i < input.length && input[i] === alphabet[0]; i++) { zeroPrefix++; } [].forEach.call(input, function(c, charIndex) { const index = alphabet.indexOf(c); if (index === -1) { if (removeNonAlphaChars) { return; } else { throw new OperationError(`Char '${c}' at position ${charIndex} not in alphabet`); } } let carry = index; for (let i = 0; i < result.length; i++) { carry += result[i] * 58; result[i] = carry & 0xFF; carry = carry >> 8; } while (carry > 0) { result.push(carry & 0xFF); carry = carry >> 8; } }); while (zeroPrefix--) { result.push(0); } return result.reverse(); } } export default FromBase58; ================================================ FILE: src/core/operations/FromBase62.mjs ================================================ /** * @author tcode2k16 [tcode2k16@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import BigNumber from "bignumber.js"; import Utils from "../Utils.mjs"; /** * From Base62 operation */ class FromBase62 extends Operation { /** * FromBase62 constructor */ constructor() { super(); this.name = "From Base62"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { name: "Alphabet", type: "string", value: "0-9A-Za-z" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { if (input.length < 1) return []; const alphabet = Utils.expandAlphRange(args[0]).join(""); const BN62 = BigNumber.clone({ ALPHABET: alphabet }); const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); input = input.replace(re, ""); // Read number in using Base62 alphabet const number = new BN62(input, 62); // Copy to new BigNumber object that uses the default alphabet const normalized = new BigNumber(number); // Convert to hex and add leading 0 if required let hex = normalized.toString(16); if (hex.length % 2 !== 0) hex = "0" + hex; return Utils.convertToByteArray(hex, "Hex"); } } export default FromBase62; ================================================ FILE: src/core/operations/FromBase64.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {fromBase64, ALPHABET_OPTIONS} from "../lib/Base64.mjs"; /** * From Base64 operation */ class FromBase64 extends Operation { /** * FromBase64 constructor */ constructor() { super(); this.name = "From Base64"; this.module = "Default"; this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.

This operation decodes data from an ASCII Base64 string back into its raw format.

e.g. aGVsbG8= becomes hello"; this.infoURL = "https://wikipedia.org/wiki/Base64"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { name: "Alphabet", type: "editableOption", value: ALPHABET_OPTIONS }, { name: "Remove non-alphabet chars", type: "boolean", value: true }, { name: "Strict mode", type: "boolean", value: false } ]; this.checks = [ { pattern: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", flags: "i", args: ["A-Za-z0-9+/=", true, false] }, { pattern: "^\\s*[A-Z\\d\\-_]{20,}\\s*$", flags: "i", args: ["A-Za-z0-9-_", true, false] }, { pattern: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$", flags: "i", args: ["A-Za-z0-9+\\-=", true, false] }, { pattern: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$", flags: "i", args: ["./0-9A-Za-z=", true, false] }, { pattern: "^\\s*[A-Z\\d_.]{20,}\\s*$", flags: "i", args: ["A-Za-z0-9_.", true, false] }, { pattern: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$", flags: "i", args: ["A-Za-z0-9._-", true, false] }, { pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", flags: "i", args: ["0-9a-zA-Z+/=", true, false] }, { pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", flags: "i", args: ["0-9A-Za-z+/=", true, false] }, { pattern: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$", flags: "", args: [" -_", false, false] }, { pattern: "^\\s*[A-Z\\d+\\-]{20,}\\s*$", flags: "i", args: ["+\\-0-9A-Za-z", true, false] }, { pattern: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$", flags: "", args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true, false] }, { pattern: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$", flags: "i", args: ["N-ZA-Mn-za-m0-9+/=", true, false] }, { pattern: "^\\s*[A-Z\\d./]{20,}\\s*$", flags: "i", args: ["./0-9A-Za-z", true, false] }, { pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}CC|[A-Z=\\d\\+/]{3}C)?\\s*$", flags: "i", args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true, false] }, { pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$", flags: "i", args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true, false] }, { pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}22|[A-Z=\\d\\+/]{3}2)?\\s*$", flags: "i", args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true, false] }, { pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$", flags: "i", args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true, false] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const [alphabet, removeNonAlphChars, strictMode] = args; return fromBase64(input, alphabet, "byteArray", removeNonAlphChars, strictMode); } /** * Highlight to Base64 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { pos[0].start = Math.ceil(pos[0].start / 4 * 3); pos[0].end = Math.floor(pos[0].end / 4 * 3); return pos; } /** * Highlight from Base64 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { pos[0].start = Math.floor(pos[0].start / 3 * 4); pos[0].end = Math.ceil(pos[0].end / 3 * 4); return pos; } } export default FromBase64; ================================================ FILE: src/core/operations/FromBase85.mjs ================================================ /** * @author PenguinGeorge [george@penguingeorge.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import {ALPHABET_OPTIONS} from "../lib/Base85.mjs"; /** * From Base85 operation */ class FromBase85 extends Operation { /** * From Base85 constructor */ constructor() { super(); this.name = "From Base85"; this.module = "Default"; this.description = "Base85 (also called Ascii85) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.

This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included).

e.g. BOu!rD]j7BEbo7 becomes hello world

Base85 is commonly used in Adobe's PostScript and PDF file formats."; this.infoURL = "https://wikipedia.org/wiki/Ascii85"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { name: "Alphabet", type: "editableOption", value: ALPHABET_OPTIONS }, { name: "Remove non-alphabet chars", type: "boolean", value: true }, { name: "All-zero group char", type: "binaryShortString", value: "z", maxLength: 1 } ]; this.checks = [ { pattern: "^\\s*(?:<~)?" + // Optional whitespace and starting marker "[\\s!-uz]*" + // Any amount of base85 characters and whitespace "[!-uz]{15}" + // At least 15 continoues base85 characters without whitespace "[\\s!-uz]*" + // Any amount of base85 characters and whitespace "(?:~>)?\\s*$", // Optional ending marker and whitespace args: ["!-u"], }, { pattern: "^" + "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + "[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{15}" + // At least 15 continoues base85 characters without whitespace "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + "$", args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"], }, { pattern: "^" + "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + "[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{15}" + // At least 15 continoues base85 characters without whitespace "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + "$", args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"], }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const alphabet = Utils.expandAlphRange(args[0]).join(""), removeNonAlphChars = args[1], allZeroGroupChar = typeof args[2] === "string" ? args[2].slice(0, 1) : "", result = []; if (alphabet.length !== 85 || [].unique.call(alphabet).length !== 85) { throw new OperationError("Alphabet must be of length 85"); } if (allZeroGroupChar && alphabet.includes(allZeroGroupChar)) { throw new OperationError("The all-zero group char cannot appear in the alphabet"); } // Remove delimiters if present const matches = input.match(/^<~(.+?)~>$/); if (matches !== null) input = matches[1]; // Remove non-alphabet characters if (removeNonAlphChars) { const re = new RegExp("[^~" + allZeroGroupChar +alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); input = input.replace(re, ""); // Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters) const matches = input.match(/^<~(.+?)~>$/); if (matches !== null) input = matches[1]; } if (input.length === 0) return []; let i = 0; let block, blockBytes; while (i < input.length) { if (input[i] === allZeroGroupChar) { result.push(0, 0, 0, 0); i++; } else { let digits = []; digits = input .substr(i, 5) .split("") .map((chr, idx) => { const digit = alphabet.indexOf(chr); if ((digit < 0 || digit > 84) && chr !== allZeroGroupChar) { throw `Invalid character '${chr}' at index ${i + idx}`; } return digit; }); block = digits[0] * 52200625 + digits[1] * 614125 + (i + 2 < input.length ? digits[2] : 84) * 7225 + (i + 3 < input.length ? digits[3] : 84) * 85 + (i + 4 < input.length ? digits[4] : 84); blockBytes = [ (block >> 24) & 0xff, (block >> 16) & 0xff, (block >> 8) & 0xff, block & 0xff ]; if (input.length < i + 5) { blockBytes.splice(input.length - (i + 5), 5); } result.push.apply(result, blockBytes); i += 5; } } return result; } } export default FromBase85; ================================================ FILE: src/core/operations/FromBase92.mjs ================================================ /** * @author sg5506844 [sg5506844@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import { base92Ord } from "../lib/Base92.mjs"; import Operation from "../Operation.mjs"; /** * From Base92 operation */ class FromBase92 extends Operation { /** * FromBase92 constructor */ constructor() { super(); this.name = "From Base92"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; this.inputType = "string"; this.outputType = "byteArray"; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const res = []; let bitString = ""; for (let i = 0; i < input.length; i += 2) { if (i + 1 !== input.length) { const x = base92Ord(input[i]) * 91 + base92Ord(input[i + 1]); bitString += x.toString(2).padStart(13, "0"); } else { const x = base92Ord(input[i]); bitString += x.toString(2).padStart(6, "0"); } while (bitString.length >= 8) { res.push(parseInt(bitString.slice(0, 8), 2)); bitString = bitString.slice(8); } } return res; } } export default FromBase92; ================================================ FILE: src/core/operations/FromBech32.mjs ================================================ /** * @author Medjedtxm * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { decode } from "../lib/Bech32.mjs"; import { toHex } from "../lib/Hex.mjs"; /** * From Bech32 operation */ class FromBech32 extends Operation { /** * FromBech32 constructor */ constructor() { super(); this.name = "From Bech32"; this.module = "Default"; 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.

Bech32m (BIP-0350) is an updated version used for Bitcoin Taproot addresses.

Auto-detect will attempt Bech32 first, then Bech32m if the checksum fails.

Output format options allow you to see the Human-Readable Part (HRP) along with the decoded data."; this.infoURL = "https://wikipedia.org/wiki/Bech32"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Encoding", "type": "option", "value": ["Auto-detect", "Bech32", "Bech32m"] }, { "name": "Output Format", "type": "option", "value": ["Raw", "Hex", "Bitcoin scriptPubKey", "HRP: Hex", "JSON"] } ]; this.checks = [ { // Bitcoin mainnet SegWit/Taproot addresses pattern: "^bc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", flags: "i", args: ["Auto-detect", "Hex"] }, { // Bitcoin testnet addresses pattern: "^tb1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", flags: "i", args: ["Auto-detect", "Hex"] }, { // AGE public keys pattern: "^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", flags: "i", args: ["Auto-detect", "HRP: Hex"] }, { // AGE secret keys pattern: "^AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{6,87}$", flags: "", args: ["Auto-detect", "HRP: Hex"] }, { // Litecoin mainnet addresses pattern: "^ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", flags: "i", args: ["Auto-detect", "Hex"] }, { // Generic bech32 pattern pattern: "^[a-z]{1,83}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,}$", flags: "i", args: ["Auto-detect", "Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const encoding = args[0]; const outputFormat = args[1]; input = input.trim(); if (input.length === 0) { return ""; } const decoded = decode(input, encoding); // Format output based on selected option switch (outputFormat) { case "Raw": return decoded.data.map(b => String.fromCharCode(b)).join(""); case "Hex": return toHex(decoded.data, ""); case "Bitcoin scriptPubKey": { // Convert to Bitcoin scriptPubKey format as shown in BIP-0173/BIP-0350 // Format: [OP_version][length][witness_program] // OP_0 = 0x00, OP_1-OP_16 = 0x51-0x60 if (decoded.witnessVersion === null || decoded.data.length < 2) { // Not a SegWit address, fall back to hex return toHex(decoded.data, ""); } const witnessVersion = decoded.data[0]; const witnessProgram = decoded.data.slice(1); // Convert witness version to OP code let opCode; if (witnessVersion === 0) { opCode = 0x00; // OP_0 } else if (witnessVersion >= 1 && witnessVersion <= 16) { opCode = 0x50 + witnessVersion; // OP_1 = 0x51, ..., OP_16 = 0x60 } else { // Invalid witness version, fall back to hex return toHex(decoded.data, ""); } // Build scriptPubKey: [OP_version][length][program] const scriptPubKey = [opCode, witnessProgram.length, ...witnessProgram]; return toHex(scriptPubKey, ""); } case "HRP: Hex": return `${decoded.hrp}: ${toHex(decoded.data, "")}`; case "JSON": return JSON.stringify({ hrp: decoded.hrp, encoding: decoded.encoding, data: toHex(decoded.data, "") }, null, 2); default: return toHex(decoded.data, ""); } } } export default FromBech32; ================================================ FILE: src/core/operations/FromBinary.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {BIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; import {fromBinary} from "../lib/Binary.mjs"; /** * From Binary operation */ class FromBinary extends Operation { /** * FromBinary constructor */ constructor() { super(); this.name = "From Binary"; this.module = "Default"; this.description = "Converts a binary string back into its raw form.

e.g. 01001000 01101001 becomes Hi"; this.infoURL = "https://wikipedia.org/wiki/Binary_code"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { "name": "Delimiter", "type": "option", "value": BIN_DELIM_OPTIONS }, { "name": "Byte Length", "type": "number", "value": 8, "min": 1 } ]; this.checks = [ { pattern: "^(?:[01]{8})+$", flags: "", args: ["None"] }, { pattern: "^(?:[01]{8})(?: [01]{8})*$", flags: "", args: ["Space"] }, { pattern: "^(?:[01]{8})(?:,[01]{8})*$", flags: "", args: ["Comma"] }, { pattern: "^(?:[01]{8})(?:;[01]{8})*$", flags: "", args: ["Semi-colon"] }, { pattern: "^(?:[01]{8})(?::[01]{8})*$", flags: "", args: ["Colon"] }, { pattern: "^(?:[01]{8})(?:\\n[01]{8})*$", flags: "", args: ["Line feed"] }, { pattern: "^(?:[01]{8})(?:\\r\\n[01]{8})*$", flags: "", args: ["CRLF"] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const byteLen = args[1] ? args[1] : 8; return fromBinary(input, args[0], byteLen); } /** * Highlight From Binary * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { const delim = Utils.charRep(args[0] || "Space"); pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length)); pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length)); return pos; } /** * Highlight From Binary in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { const delim = Utils.charRep(args[0] || "Space"); pos[0].start = pos[0].start * (8 + delim.length); pos[0].end = pos[0].end * (8 + delim.length) - delim.length; return pos; } } export default FromBinary; ================================================ FILE: src/core/operations/FromBraille.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {BRAILLE_LOOKUP} from "../lib/Braille.mjs"; /** * From Braille operation */ class FromBraille extends Operation { /** * FromBraille constructor */ constructor() { super(); this.name = "From Braille"; this.module = "Default"; this.description = "Converts six-dot braille symbols to text."; this.infoURL = "https://wikipedia.org/wiki/Braille"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return input.split("").map(b => { const idx = BRAILLE_LOOKUP.dot6.indexOf(b); return idx < 0 ? b : BRAILLE_LOOKUP.ascii[idx]; }).join(""); } /** * Highlight From Braille * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight From Braille in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default FromBraille; ================================================ FILE: src/core/operations/FromCaseInsensitiveRegex.mjs ================================================ /** * @author masq [github.cyberchef@masq.cc] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * From Case Insensitive Regex operation */ class FromCaseInsensitiveRegex extends Operation { /** * FromCaseInsensitiveRegex constructor */ constructor() { super(); this.name = "From Case Insensitive Regex"; this.module = "Default"; this.description = "Converts a case-insensitive regex string to a case sensitive regex string (no guarantee on it being the proper original casing) in case the i flag wasn't available at the time but now is, or you need it to be case-sensitive again.

e.g. [mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .* becomes Mozilla/[0-9].[0-9] .*"; this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return input.replace(/\[[a-z]{2}\]/ig, m => m[1].toUpperCase() === m[2].toUpperCase() ? m[1] : m); } } export default FromCaseInsensitiveRegex; ================================================ FILE: src/core/operations/FromCharcode.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { DELIM_OPTIONS } from "../lib/Delim.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * From Charcode operation */ class FromCharcode extends Operation { /** * FromCharcode constructor */ constructor() { super(); this.name = "From Charcode"; this.module = "Default"; this.description = "Converts unicode character codes back into text.

e.g. 0393 03b5 03b9 03ac 20 03c3 03bf 03c5 becomes Γειά σου"; this.infoURL = "https://wikipedia.org/wiki/Plane_(Unicode)"; this.inputType = "string"; this.outputType = "ArrayBuffer"; this.args = [ { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS }, { "name": "Base", "type": "number", "value": 16 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {ArrayBuffer} * * @throws {OperationError} if base out of range */ run(input, args) { const delim = Utils.charRep(args[0] || "Space"), base = args[1]; let bites = input.split(delim), i = 0; if (base < 2 || base > 36) { throw new OperationError("Error: Base argument must be between 2 and 36"); } if (input.length === 0) { return new ArrayBuffer; } if (base !== 16 && isWorkerEnvironment()) self.setOption("attemptHighlight", false); // Split into groups of 2 if the whole string is concatenated and // too long to be a single character if (bites.length === 1 && input.length > 17) { bites = []; for (i = 0; i < input.length; i += 2) { bites.push(input.slice(i, i+2)); } } let latin1 = ""; for (i = 0; i < bites.length; i++) { latin1 += Utils.chr(parseInt(bites[i], base)); } return Utils.strToArrayBuffer(latin1); } } export default FromCharcode; ================================================ FILE: src/core/operations/FromDecimal.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {DELIM_OPTIONS} from "../lib/Delim.mjs"; import {fromDecimal} from "../lib/Decimal.mjs"; /** * From Decimal operation */ class FromDecimal extends Operation { /** * FromDecimal constructor */ constructor() { super(); this.name = "From Decimal"; this.module = "Default"; this.description = "Converts the data from an ordinal integer array back into its raw form.

e.g. 72 101 108 108 111 becomes Hello"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS }, { "name": "Support signed values", "type": "boolean", "value": false } ]; this.checks = [ { 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]))*$", flags: "", args: ["Space", false] }, { 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]))*$", flags: "", args: ["Comma", false] }, { 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]))*$", flags: "", args: ["Semi-colon", false] }, { 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]))*$", flags: "", args: ["Colon", false] }, { 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]))*$", flags: "", args: ["Line feed", false] }, { 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]))*$", flags: "", args: ["CRLF", false] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { let data = fromDecimal(input, args[0]); if (args[1]) { // Convert negatives data = data.map(v => v < 0 ? 0xFF + v + 1 : v); } return data; } } export default FromDecimal; ================================================ FILE: src/core/operations/FromFloat.mjs ================================================ /** * @author tcode2k16 [tcode2k16@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import ieee754 from "ieee754"; import {DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * From Float operation */ class FromFloat extends Operation { /** * FromFloat constructor */ constructor() { super(); this.name = "From Float"; this.module = "Default"; this.description = "Convert from IEEE754 Floating Point Numbers"; this.infoURL = "https://wikipedia.org/wiki/IEEE_754"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { "name": "Endianness", "type": "option", "value": [ "Big Endian", "Little Endian" ] }, { "name": "Size", "type": "option", "value": [ "Float (4 bytes)", "Double (8 bytes)" ] }, { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { if (input.length === 0) return []; const [endianness, size, delimiterName] = args; const delim = Utils.charRep(delimiterName || "Space"); const byteSize = size === "Double (8 bytes)" ? 8 : 4; const isLE = endianness === "Little Endian"; const mLen = byteSize === 4 ? 23 : 52; const floats = input.split(delim); const output = new Array(floats.length*byteSize); for (let i = 0; i < floats.length; i++) { ieee754.write(output, parseFloat(floats[i]), i*byteSize, isLE, mLen, byteSize); } return output; } } export default FromFloat; ================================================ FILE: src/core/operations/FromHTMLEntity.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * From HTML Entity operation */ class FromHTMLEntity extends Operation { /** * FromHTMLEntity constructor */ constructor() { super(); this.name = "From HTML Entity"; this.module = "Encodings"; this.description = "Converts HTML entities back to characters

e.g. &amp; becomes &"; this.infoURL = "https://wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = [ { pattern: "&(?:#\\d{2,3}|#x[\\da-f]{2}|[a-z]{2,6});", flags: "i", args: [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const regex = /&(#?x?[a-zA-Z0-9]{1,20});/g; let output = "", m, i = 0; while ((m = regex.exec(input))) { // Add up to match for (; i < m.index;) output += input[i++]; // Add match const bite = entityToByte[m[1]]; if (bite) { output += Utils.chr(bite); } else if (!bite && m[1][0] === "#" && m[1].length > 1 && /^#\d{1,6}$/.test(m[1])) { // Numeric entity (e.g. ) const num = m[1].slice(1, m[1].length); output += Utils.chr(parseInt(num, 10)); } else if (!bite && m[1][0] === "#" && m[1].length > 3 && /^#x[\dA-F]{2,8}$/i.test(m[1])) { // Hex entity (e.g. :) const hex = m[1].slice(2, m[1].length); output += Utils.chr(parseInt(hex, 16)); } else { // Not a valid entity, print as normal for (; i < regex.lastIndex;) output += input[i++]; } i = regex.lastIndex; } // Add all after final match for (; i < input.length;) output += input[i++]; return output; } } /** * Lookup table to translate HTML entity codes to their byte values. */ const entityToByte = { "Tab": 9, "NewLine": 10, "excl": 33, "quot": 34, "num": 35, "dollar": 36, "percnt": 37, "amp": 38, "apos": 39, "lpar": 40, "rpar": 41, "ast": 42, "plus": 43, "comma": 44, "period": 46, "sol": 47, "colon": 58, "semi": 59, "lt": 60, "equals": 61, "gt": 62, "quest": 63, "commat": 64, "lsqb": 91, "bsol": 92, "rsqb": 93, "Hat": 94, "lowbar": 95, "grave": 96, "lcub": 123, "verbar": 124, "rcub": 125, "nbsp": 160, "iexcl": 161, "cent": 162, "pound": 163, "curren": 164, "yen": 165, "brvbar": 166, "sect": 167, "uml": 168, "copy": 169, "ordf": 170, "laquo": 171, "not": 172, "shy": 173, "reg": 174, "macr": 175, "deg": 176, "plusmn": 177, "sup2": 178, "sup3": 179, "acute": 180, "micro": 181, "para": 182, "middot": 183, "cedil": 184, "sup1": 185, "ordm": 186, "raquo": 187, "frac14": 188, "frac12": 189, "frac34": 190, "iquest": 191, "Agrave": 192, "Aacute": 193, "Acirc": 194, "Atilde": 195, "Auml": 196, "Aring": 197, "AElig": 198, "Ccedil": 199, "Egrave": 200, "Eacute": 201, "Ecirc": 202, "Euml": 203, "Igrave": 204, "Iacute": 205, "Icirc": 206, "Iuml": 207, "ETH": 208, "Ntilde": 209, "Ograve": 210, "Oacute": 211, "Ocirc": 212, "Otilde": 213, "Ouml": 214, "times": 215, "Oslash": 216, "Ugrave": 217, "Uacute": 218, "Ucirc": 219, "Uuml": 220, "Yacute": 221, "THORN": 222, "szlig": 223, "agrave": 224, "aacute": 225, "acirc": 226, "atilde": 227, "auml": 228, "aring": 229, "aelig": 230, "ccedil": 231, "egrave": 232, "eacute": 233, "ecirc": 234, "euml": 235, "igrave": 236, "iacute": 237, "icirc": 238, "iuml": 239, "eth": 240, "ntilde": 241, "ograve": 242, "oacute": 243, "ocirc": 244, "otilde": 245, "ouml": 246, "divide": 247, "oslash": 248, "ugrave": 249, "uacute": 250, "ucirc": 251, "uuml": 252, "yacute": 253, "thorn": 254, "yuml": 255, "Amacr": 256, "amacr": 257, "Abreve": 258, "abreve": 259, "Aogon": 260, "aogon": 261, "Cacute": 262, "cacute": 263, "Ccirc": 264, "ccirc": 265, "Cdot": 266, "cdot": 267, "Ccaron": 268, "ccaron": 269, "Dcaron": 270, "dcaron": 271, "Dstrok": 272, "dstrok": 273, "Emacr": 274, "emacr": 275, "Edot": 278, "edot": 279, "Eogon": 280, "eogon": 281, "Ecaron": 282, "ecaron": 283, "Gcirc": 284, "gcirc": 285, "Gbreve": 286, "gbreve": 287, "Gdot": 288, "gdot": 289, "Gcedil": 290, "Hcirc": 292, "hcirc": 293, "Hstrok": 294, "hstrok": 295, "Itilde": 296, "itilde": 297, "Imacr": 298, "imacr": 299, "Iogon": 302, "iogon": 303, "Idot": 304, "imath": 305, "IJlig": 306, "ijlig": 307, "Jcirc": 308, "jcirc": 309, "Kcedil": 310, "kcedil": 311, "kgreen": 312, "Lacute": 313, "lacute": 314, "Lcedil": 315, "lcedil": 316, "Lcaron": 317, "lcaron": 318, "Lmidot": 319, "lmidot": 320, "Lstrok": 321, "lstrok": 322, "Nacute": 323, "nacute": 324, "Ncedil": 325, "ncedil": 326, "Ncaron": 327, "ncaron": 328, "napos": 329, "ENG": 330, "eng": 331, "Omacr": 332, "omacr": 333, "Odblac": 336, "odblac": 337, "OElig": 338, "oelig": 339, "Racute": 340, "racute": 341, "Rcedil": 342, "rcedil": 343, "Rcaron": 344, "rcaron": 345, "Sacute": 346, "sacute": 347, "Scirc": 348, "scirc": 349, "Scedil": 350, "scedil": 351, "Scaron": 352, "scaron": 353, "Tcedil": 354, "tcedil": 355, "Tcaron": 356, "tcaron": 357, "Tstrok": 358, "tstrok": 359, "Utilde": 360, "utilde": 361, "Umacr": 362, "umacr": 363, "Ubreve": 364, "ubreve": 365, "Uring": 366, "uring": 367, "Udblac": 368, "udblac": 369, "Uogon": 370, "uogon": 371, "Wcirc": 372, "wcirc": 373, "Ycirc": 374, "ycirc": 375, "Yuml": 376, "Zacute": 377, "zacute": 378, "Zdot": 379, "zdot": 380, "Zcaron": 381, "zcaron": 382, "fnof": 402, "imped": 437, "gacute": 501, "jmath": 567, "circ": 710, "caron": 711, "breve": 728, "dot": 729, "ring": 730, "ogon": 731, "tilde": 732, "dblac": 733, "DownBreve": 785, "UnderBar": 818, "Alpha": 913, "Beta": 914, "Gamma": 915, "Delta": 916, "Epsilon": 917, "Zeta": 918, "Eta": 919, "Theta": 920, "Iota": 921, "Kappa": 922, "Lambda": 923, "Mu": 924, "Nu": 925, "Xi": 926, "Omicron": 927, "Pi": 928, "Rho": 929, "Sigma": 931, "Tau": 932, "Upsilon": 933, "Phi": 934, "Chi": 935, "Psi": 936, "Omega": 937, "alpha": 945, "beta": 946, "gamma": 947, "delta": 948, "epsilon": 949, "zeta": 950, "eta": 951, "theta": 952, "iota": 953, "kappa": 954, "lambda": 955, "mu": 956, "nu": 957, "xi": 958, "omicron": 959, "pi": 960, "rho": 961, "sigmaf": 962, "sigma": 963, "tau": 964, "upsilon": 965, "phi": 966, "chi": 967, "psi": 968, "omega": 969, "thetasym": 977, "upsih": 978, "straightphi": 981, "piv": 982, "Gammad": 988, "gammad": 989, "kappav": 1008, "rhov": 1009, "epsi": 1013, "bepsi": 1014, "IOcy": 1025, "DJcy": 1026, "GJcy": 1027, "Jukcy": 1028, "DScy": 1029, "Iukcy": 1030, "YIcy": 1031, "Jsercy": 1032, "LJcy": 1033, "NJcy": 1034, "TSHcy": 1035, "KJcy": 1036, "Ubrcy": 1038, "DZcy": 1039, "Acy": 1040, "Bcy": 1041, "Vcy": 1042, "Gcy": 1043, "Dcy": 1044, "IEcy": 1045, "ZHcy": 1046, "Zcy": 1047, "Icy": 1048, "Jcy": 1049, "Kcy": 1050, "Lcy": 1051, "Mcy": 1052, "Ncy": 1053, "Ocy": 1054, "Pcy": 1055, "Rcy": 1056, "Scy": 1057, "Tcy": 1058, "Ucy": 1059, "Fcy": 1060, "KHcy": 1061, "TScy": 1062, "CHcy": 1063, "SHcy": 1064, "SHCHcy": 1065, "HARDcy": 1066, "Ycy": 1067, "SOFTcy": 1068, "Ecy": 1069, "YUcy": 1070, "YAcy": 1071, "acy": 1072, "bcy": 1073, "vcy": 1074, "gcy": 1075, "dcy": 1076, "iecy": 1077, "zhcy": 1078, "zcy": 1079, "icy": 1080, "jcy": 1081, "kcy": 1082, "lcy": 1083, "mcy": 1084, "ncy": 1085, "ocy": 1086, "pcy": 1087, "rcy": 1088, "scy": 1089, "tcy": 1090, "ucy": 1091, "fcy": 1092, "khcy": 1093, "tscy": 1094, "chcy": 1095, "shcy": 1096, "shchcy": 1097, "hardcy": 1098, "ycy": 1099, "softcy": 1100, "ecy": 1101, "yucy": 1102, "yacy": 1103, "iocy": 1105, "djcy": 1106, "gjcy": 1107, "jukcy": 1108, "dscy": 1109, "iukcy": 1110, "yicy": 1111, "jsercy": 1112, "ljcy": 1113, "njcy": 1114, "tshcy": 1115, "kjcy": 1116, "ubrcy": 1118, "dzcy": 1119, "ensp": 8194, "emsp": 8195, "emsp13": 8196, "emsp14": 8197, "numsp": 8199, "puncsp": 8200, "thinsp": 8201, "hairsp": 8202, "ZeroWidthSpace": 8203, "zwnj": 8204, "zwj": 8205, "lrm": 8206, "rlm": 8207, "hyphen": 8208, "ndash": 8211, "mdash": 8212, "horbar": 8213, "Verbar": 8214, "lsquo": 8216, "rsquo": 8217, "sbquo": 8218, "ldquo": 8220, "rdquo": 8221, "bdquo": 8222, "dagger": 8224, "Dagger": 8225, "bull": 8226, "nldr": 8229, "hellip": 8230, "permil": 8240, "pertenk": 8241, "prime": 8242, "Prime": 8243, "tprime": 8244, "bprime": 8245, "lsaquo": 8249, "rsaquo": 8250, "oline": 8254, "caret": 8257, "hybull": 8259, "frasl": 8260, "bsemi": 8271, "qprime": 8279, "MediumSpace": 8287, "NoBreak": 8288, "ApplyFunction": 8289, "InvisibleTimes": 8290, "InvisibleComma": 8291, "euro": 8364, "tdot": 8411, "TripleDot": 8411, "DotDot": 8412, "Copf": 8450, "incare": 8453, "gscr": 8458, "hamilt": 8459, "Hfr": 8460, "quaternions": 8461, "planckh": 8462, "planck": 8463, "Iscr": 8464, "image": 8465, "Lscr": 8466, "ell": 8467, "Nopf": 8469, "numero": 8470, "copysr": 8471, "weierp": 8472, "Popf": 8473, "rationals": 8474, "Rscr": 8475, "real": 8476, "reals": 8477, "rx": 8478, "trade": 8482, "integers": 8484, "ohm": 8486, "mho": 8487, "Zfr": 8488, "iiota": 8489, "angst": 8491, "bernou": 8492, "Cfr": 8493, "escr": 8495, "Escr": 8496, "Fscr": 8497, "phmmat": 8499, "order": 8500, "alefsym": 8501, "beth": 8502, "gimel": 8503, "daleth": 8504, "CapitalDifferentialD": 8517, "DifferentialD": 8518, "ExponentialE": 8519, "ImaginaryI": 8520, "frac13": 8531, "frac23": 8532, "frac15": 8533, "frac25": 8534, "frac35": 8535, "frac45": 8536, "frac16": 8537, "frac56": 8538, "frac18": 8539, "frac38": 8540, "frac58": 8541, "frac78": 8542, "larr": 8592, "uarr": 8593, "rarr": 8594, "darr": 8595, "harr": 8596, "varr": 8597, "nwarr": 8598, "nearr": 8599, "searr": 8600, "swarr": 8601, "nlarr": 8602, "nrarr": 8603, "rarrw": 8605, "Larr": 8606, "Uarr": 8607, "Rarr": 8608, "Darr": 8609, "larrtl": 8610, "rarrtl": 8611, "LeftTeeArrow": 8612, "UpTeeArrow": 8613, "map": 8614, "DownTeeArrow": 8615, "larrhk": 8617, "rarrhk": 8618, "larrlp": 8619, "rarrlp": 8620, "harrw": 8621, "nharr": 8622, "lsh": 8624, "rsh": 8625, "ldsh": 8626, "rdsh": 8627, "crarr": 8629, "cularr": 8630, "curarr": 8631, "olarr": 8634, "orarr": 8635, "lharu": 8636, "lhard": 8637, "uharr": 8638, "uharl": 8639, "rharu": 8640, "rhard": 8641, "dharr": 8642, "dharl": 8643, "rlarr": 8644, "udarr": 8645, "lrarr": 8646, "llarr": 8647, "uuarr": 8648, "rrarr": 8649, "ddarr": 8650, "lrhar": 8651, "rlhar": 8652, "nlArr": 8653, "nhArr": 8654, "nrArr": 8655, "lArr": 8656, "uArr": 8657, "rArr": 8658, "dArr": 8659, "hArr": 8660, "vArr": 8661, "nwArr": 8662, "neArr": 8663, "seArr": 8664, "swArr": 8665, "lAarr": 8666, "rAarr": 8667, "zigrarr": 8669, "larrb": 8676, "rarrb": 8677, "duarr": 8693, "loarr": 8701, "roarr": 8702, "hoarr": 8703, "forall": 8704, "comp": 8705, "part": 8706, "exist": 8707, "nexist": 8708, "empty": 8709, "nabla": 8711, "isin": 8712, "notin": 8713, "ni": 8715, "notni": 8716, "prod": 8719, "coprod": 8720, "sum": 8721, "minus": 8722, "mnplus": 8723, "plusdo": 8724, "setmn": 8726, "lowast": 8727, "compfn": 8728, "radic": 8730, "prop": 8733, "infin": 8734, "angrt": 8735, "ang": 8736, "angmsd": 8737, "angsph": 8738, "mid": 8739, "nmid": 8740, "par": 8741, "npar": 8742, "and": 8743, "or": 8744, "cap": 8745, "cup": 8746, "int": 8747, "Int": 8748, "tint": 8749, "conint": 8750, "Conint": 8751, "Cconint": 8752, "cwint": 8753, "cwconint": 8754, "awconint": 8755, "there4": 8756, "becaus": 8757, "ratio": 8758, "Colon": 8759, "minusd": 8760, "mDDot": 8762, "homtht": 8763, "sim": 8764, "bsim": 8765, "ac": 8766, "acd": 8767, "wreath": 8768, "nsim": 8769, "esim": 8770, "sime": 8771, "nsime": 8772, "cong": 8773, "simne": 8774, "ncong": 8775, "asymp": 8776, "nap": 8777, "ape": 8778, "apid": 8779, "bcong": 8780, "asympeq": 8781, "bump": 8782, "bumpe": 8783, "esdot": 8784, "eDot": 8785, "efDot": 8786, "erDot": 8787, "colone": 8788, "ecolon": 8789, "ecir": 8790, "cire": 8791, "wedgeq": 8793, "veeeq": 8794, "trie": 8796, "equest": 8799, "ne": 8800, "equiv": 8801, "nequiv": 8802, "le": 8804, "ge": 8805, "lE": 8806, "gE": 8807, "lnE": 8808, "gnE": 8809, "Lt": 8810, "Gt": 8811, "twixt": 8812, "NotCupCap": 8813, "nlt": 8814, "ngt": 8815, "nle": 8816, "nge": 8817, "lsim": 8818, "gsim": 8819, "nlsim": 8820, "ngsim": 8821, "lg": 8822, "gl": 8823, "ntlg": 8824, "ntgl": 8825, "pr": 8826, "sc": 8827, "prcue": 8828, "sccue": 8829, "prsim": 8830, "scsim": 8831, "npr": 8832, "nsc": 8833, "sub": 8834, "sup": 8835, "nsub": 8836, "nsup": 8837, "sube": 8838, "supe": 8839, "nsube": 8840, "nsupe": 8841, "subne": 8842, "supne": 8843, "cupdot": 8845, "uplus": 8846, "sqsub": 8847, "sqsup": 8848, "sqsube": 8849, "sqsupe": 8850, "sqcap": 8851, "sqcup": 8852, "oplus": 8853, "ominus": 8854, "otimes": 8855, "osol": 8856, "odot": 8857, "ocir": 8858, "oast": 8859, "odash": 8861, "plusb": 8862, "minusb": 8863, "timesb": 8864, "sdotb": 8865, "vdash": 8866, "dashv": 8867, "top": 8868, "perp": 8869, "models": 8871, "vDash": 8872, "Vdash": 8873, "Vvdash": 8874, "VDash": 8875, "nvdash": 8876, "nvDash": 8877, "nVdash": 8878, "nVDash": 8879, "prurel": 8880, "vltri": 8882, "vrtri": 8883, "ltrie": 8884, "rtrie": 8885, "origof": 8886, "imof": 8887, "mumap": 8888, "hercon": 8889, "intcal": 8890, "veebar": 8891, "barvee": 8893, "angrtvb": 8894, "lrtri": 8895, "xwedge": 8896, "xvee": 8897, "xcap": 8898, "xcup": 8899, "diam": 8900, "sdot": 8901, "sstarf": 8902, "divonx": 8903, "bowtie": 8904, "ltimes": 8905, "rtimes": 8906, "lthree": 8907, "rthree": 8908, "bsime": 8909, "cuvee": 8910, "cuwed": 8911, "Sub": 8912, "Sup": 8913, "Cap": 8914, "Cup": 8915, "fork": 8916, "epar": 8917, "ltdot": 8918, "gtdot": 8919, "Ll": 8920, "Gg": 8921, "leg": 8922, "gel": 8923, "cuepr": 8926, "cuesc": 8927, "nprcue": 8928, "nsccue": 8929, "nsqsube": 8930, "nsqsupe": 8931, "lnsim": 8934, "gnsim": 8935, "prnsim": 8936, "scnsim": 8937, "nltri": 8938, "nrtri": 8939, "nltrie": 8940, "nrtrie": 8941, "vellip": 8942, "ctdot": 8943, "utdot": 8944, "dtdot": 8945, "disin": 8946, "isinsv": 8947, "isins": 8948, "isindot": 8949, "notinvc": 8950, "notinvb": 8951, "isinE": 8953, "nisd": 8954, "xnis": 8955, "nis": 8956, "notnivc": 8957, "notnivb": 8958, "barwed": 8965, "Barwed": 8966, "lceil": 8968, "rceil": 8969, "lfloor": 8970, "rfloor": 8971, "drcrop": 8972, "dlcrop": 8973, "urcrop": 8974, "ulcrop": 8975, "bnot": 8976, "profline": 8978, "profsurf": 8979, "telrec": 8981, "target": 8982, "ulcorn": 8988, "urcorn": 8989, "dlcorn": 8990, "drcorn": 8991, "frown": 8994, "smile": 8995, "lang": 9001, "rang": 9002, "cylcty": 9005, "profalar": 9006, "topbot": 9014, "ovbar": 9021, "solbar": 9023, "angzarr": 9084, "lmoust": 9136, "rmoust": 9137, "tbrk": 9140, "bbrk": 9141, "bbrktbrk": 9142, "OverParenthesis": 9180, "UnderParenthesis": 9181, "OverBrace": 9182, "UnderBrace": 9183, "trpezium": 9186, "elinters": 9191, "blank": 9251, "oS": 9416, "boxh": 9472, "boxv": 9474, "boxdr": 9484, "boxdl": 9488, "boxur": 9492, "boxul": 9496, "boxvr": 9500, "boxvl": 9508, "boxhd": 9516, "boxhu": 9524, "boxvh": 9532, "boxH": 9552, "boxV": 9553, "boxdR": 9554, "boxDr": 9555, "boxDR": 9556, "boxdL": 9557, "boxDl": 9558, "boxDL": 9559, "boxuR": 9560, "boxUr": 9561, "boxUR": 9562, "boxuL": 9563, "boxUl": 9564, "boxUL": 9565, "boxvR": 9566, "boxVr": 9567, "boxVR": 9568, "boxvL": 9569, "boxVl": 9570, "boxVL": 9571, "boxHd": 9572, "boxhD": 9573, "boxHD": 9574, "boxHu": 9575, "boxhU": 9576, "boxHU": 9577, "boxvH": 9578, "boxVh": 9579, "boxVH": 9580, "uhblk": 9600, "lhblk": 9604, "block": 9608, "blk14": 9617, "blk12": 9618, "blk34": 9619, "squ": 9633, "squf": 9642, "EmptyVerySmallSquare": 9643, "rect": 9645, "marker": 9646, "fltns": 9649, "xutri": 9651, "utrif": 9652, "utri": 9653, "rtrif": 9656, "rtri": 9657, "xdtri": 9661, "dtrif": 9662, "dtri": 9663, "ltrif": 9666, "ltri": 9667, "loz": 9674, "cir": 9675, "tridot": 9708, "xcirc": 9711, "ultri": 9720, "urtri": 9721, "lltri": 9722, "EmptySmallSquare": 9723, "FilledSmallSquare": 9724, "starf": 9733, "bigstar": 9733, "star": 9734, "phone": 9742, "female": 9792, "male": 9794, "spades": 9824, "clubs": 9827, "hearts": 9829, "diams": 9830, "sung": 9834, "flat": 9837, "natur": 9838, "sharp": 9839, "check": 10003, "cross": 10007, "malt": 10016, "sext": 10038, "VerticalSeparator": 10072, "lbbrk": 10098, "rbbrk": 10099, "lobrk": 10214, "robrk": 10215, "Lang": 10218, "Rang": 10219, "loang": 10220, "roang": 10221, "xlarr": 10229, "xrarr": 10230, "xharr": 10231, "xlArr": 10232, "xrArr": 10233, "xhArr": 10234, "xmap": 10236, "dzigrarr": 10239, "nvlArr": 10498, "nvrArr": 10499, "nvHarr": 10500, "Map": 10501, "lbarr": 10508, "rbarr": 10509, "lBarr": 10510, "rBarr": 10511, "RBarr": 10512, "DDotrahd": 10513, "UpArrowBar": 10514, "DownArrowBar": 10515, "Rarrtl": 10518, "latail": 10521, "ratail": 10522, "lAtail": 10523, "rAtail": 10524, "larrfs": 10525, "rarrfs": 10526, "larrbfs": 10527, "rarrbfs": 10528, "nwarhk": 10531, "nearhk": 10532, "searhk": 10533, "swarhk": 10534, "nwnear": 10535, "nesear": 10536, "seswar": 10537, "swnwar": 10538, "rarrc": 10547, "cudarrr": 10549, "ldca": 10550, "rdca": 10551, "cudarrl": 10552, "larrpl": 10553, "curarrm": 10556, "cularrp": 10557, "rarrpl": 10565, "harrcir": 10568, "Uarrocir": 10569, "lurdshar": 10570, "ldrushar": 10571, "LeftRightVector": 10574, "RightUpDownVector": 10575, "DownLeftRightVector": 10576, "LeftUpDownVector": 10577, "LeftVectorBar": 10578, "RightVectorBar": 10579, "RightUpVectorBar": 10580, "RightDownVectorBar": 10581, "DownLeftVectorBar": 10582, "DownRightVectorBar": 10583, "LeftUpVectorBar": 10584, "LeftDownVectorBar": 10585, "LeftTeeVector": 10586, "RightTeeVector": 10587, "RightUpTeeVector": 10588, "RightDownTeeVector": 10589, "DownLeftTeeVector": 10590, "DownRightTeeVector": 10591, "LeftUpTeeVector": 10592, "LeftDownTeeVector": 10593, "lHar": 10594, "uHar": 10595, "rHar": 10596, "dHar": 10597, "luruhar": 10598, "ldrdhar": 10599, "ruluhar": 10600, "rdldhar": 10601, "lharul": 10602, "llhard": 10603, "rharul": 10604, "lrhard": 10605, "udhar": 10606, "duhar": 10607, "RoundImplies": 10608, "erarr": 10609, "simrarr": 10610, "larrsim": 10611, "rarrsim": 10612, "rarrap": 10613, "ltlarr": 10614, "gtrarr": 10616, "subrarr": 10617, "suplarr": 10619, "lfisht": 10620, "rfisht": 10621, "ufisht": 10622, "dfisht": 10623, "lopar": 10629, "ropar": 10630, "lbrke": 10635, "rbrke": 10636, "lbrkslu": 10637, "rbrksld": 10638, "lbrksld": 10639, "rbrkslu": 10640, "langd": 10641, "rangd": 10642, "lparlt": 10643, "rpargt": 10644, "gtlPar": 10645, "ltrPar": 10646, "vzigzag": 10650, "vangrt": 10652, "angrtvbd": 10653, "ange": 10660, "range": 10661, "dwangle": 10662, "uwangle": 10663, "angmsdaa": 10664, "angmsdab": 10665, "angmsdac": 10666, "angmsdad": 10667, "angmsdae": 10668, "angmsdaf": 10669, "angmsdag": 10670, "angmsdah": 10671, "bemptyv": 10672, "demptyv": 10673, "cemptyv": 10674, "raemptyv": 10675, "laemptyv": 10676, "ohbar": 10677, "omid": 10678, "opar": 10679, "operp": 10681, "olcross": 10683, "odsold": 10684, "olcir": 10686, "ofcir": 10687, "olt": 10688, "ogt": 10689, "cirscir": 10690, "cirE": 10691, "solb": 10692, "bsolb": 10693, "boxbox": 10697, "trisb": 10701, "rtriltri": 10702, "LeftTriangleBar": 10703, "RightTriangleBar": 10704, "race": 10714, "iinfin": 10716, "infintie": 10717, "nvinfin": 10718, "eparsl": 10723, "smeparsl": 10724, "eqvparsl": 10725, "lozf": 10731, "RuleDelayed": 10740, "dsol": 10742, "xodot": 10752, "xoplus": 10753, "xotime": 10754, "xuplus": 10756, "xsqcup": 10758, "qint": 10764, "fpartint": 10765, "cirfnint": 10768, "awint": 10769, "rppolint": 10770, "scpolint": 10771, "npolint": 10772, "pointint": 10773, "quatint": 10774, "intlarhk": 10775, "pluscir": 10786, "plusacir": 10787, "simplus": 10788, "plusdu": 10789, "plussim": 10790, "plustwo": 10791, "mcomma": 10793, "minusdu": 10794, "loplus": 10797, "roplus": 10798, "Cross": 10799, "timesd": 10800, "timesbar": 10801, "smashp": 10803, "lotimes": 10804, "rotimes": 10805, "otimesas": 10806, "Otimes": 10807, "odiv": 10808, "triplus": 10809, "triminus": 10810, "tritime": 10811, "iprod": 10812, "amalg": 10815, "capdot": 10816, "ncup": 10818, "ncap": 10819, "capand": 10820, "cupor": 10821, "cupcap": 10822, "capcup": 10823, "cupbrcap": 10824, "capbrcup": 10825, "cupcup": 10826, "capcap": 10827, "ccups": 10828, "ccaps": 10829, "ccupssm": 10832, "And": 10835, "Or": 10836, "andand": 10837, "oror": 10838, "orslope": 10839, "andslope": 10840, "andv": 10842, "orv": 10843, "andd": 10844, "ord": 10845, "wedbar": 10847, "sdote": 10854, "simdot": 10858, "congdot": 10861, "easter": 10862, "apacir": 10863, "apE": 10864, "eplus": 10865, "pluse": 10866, "Esim": 10867, "Colone": 10868, "Equal": 10869, "eDDot": 10871, "equivDD": 10872, "ltcir": 10873, "gtcir": 10874, "ltquest": 10875, "gtquest": 10876, "les": 10877, "ges": 10878, "lesdot": 10879, "gesdot": 10880, "lesdoto": 10881, "gesdoto": 10882, "lesdotor": 10883, "gesdotol": 10884, "lap": 10885, "gap": 10886, "lne": 10887, "gne": 10888, "lnap": 10889, "gnap": 10890, "lEg": 10891, "gEl": 10892, "lsime": 10893, "gsime": 10894, "lsimg": 10895, "gsiml": 10896, "lgE": 10897, "glE": 10898, "lesges": 10899, "gesles": 10900, "els": 10901, "egs": 10902, "elsdot": 10903, "egsdot": 10904, "el": 10905, "eg": 10906, "siml": 10909, "simg": 10910, "simlE": 10911, "simgE": 10912, "LessLess": 10913, "GreaterGreater": 10914, "glj": 10916, "gla": 10917, "ltcc": 10918, "gtcc": 10919, "lescc": 10920, "gescc": 10921, "smt": 10922, "lat": 10923, "smte": 10924, "late": 10925, "bumpE": 10926, "pre": 10927, "sce": 10928, "prE": 10931, "scE": 10932, "prnE": 10933, "scnE": 10934, "prap": 10935, "scap": 10936, "prnap": 10937, "scnap": 10938, "Pr": 10939, "Sc": 10940, "subdot": 10941, "supdot": 10942, "subplus": 10943, "supplus": 10944, "submult": 10945, "supmult": 10946, "subedot": 10947, "supedot": 10948, "subE": 10949, "supE": 10950, "subsim": 10951, "supsim": 10952, "subnE": 10955, "supnE": 10956, "csub": 10959, "csup": 10960, "csube": 10961, "csupe": 10962, "subsup": 10963, "supsub": 10964, "subsub": 10965, "supsup": 10966, "suphsub": 10967, "supdsub": 10968, "forkv": 10969, "topfork": 10970, "mlcp": 10971, "Dashv": 10980, "Vdashl": 10982, "Barv": 10983, "vBar": 10984, "vBarv": 10985, "Vbar": 10987, "Not": 10988, "bNot": 10989, "rnmid": 10990, "cirmid": 10991, "midcir": 10992, "topcir": 10993, "nhpar": 10994, "parsim": 10995, "parsl": 11005, "fflig": 64256, "filig": 64257, "fllig": 64258, "ffilig": 64259, "ffllig": 64260, "Ascr": 119964, "Cscr": 119966, "Dscr": 119967, "Gscr": 119970, "Jscr": 119973, "Kscr": 119974, "Nscr": 119977, "Oscr": 119978, "Pscr": 119979, "Qscr": 119980, "Sscr": 119982, "Tscr": 119983, "Uscr": 119984, "Vscr": 119985, "Wscr": 119986, "Xscr": 119987, "Yscr": 119988, "Zscr": 119989, "ascr": 119990, "bscr": 119991, "cscr": 119992, "dscr": 119993, "fscr": 119995, "hscr": 119997, "iscr": 119998, "jscr": 119999, "kscr": 120000, "lscr": 120001, "mscr": 120002, "nscr": 120003, "pscr": 120005, "qscr": 120006, "rscr": 120007, "sscr": 120008, "tscr": 120009, "uscr": 120010, "vscr": 120011, "wscr": 120012, "xscr": 120013, "yscr": 120014, "zscr": 120015, "Afr": 120068, "Bfr": 120069, "Dfr": 120071, "Efr": 120072, "Ffr": 120073, "Gfr": 120074, "Jfr": 120077, "Kfr": 120078, "Lfr": 120079, "Mfr": 120080, "Nfr": 120081, "Ofr": 120082, "Pfr": 120083, "Qfr": 120084, "Sfr": 120086, "Tfr": 120087, "Ufr": 120088, "Vfr": 120089, "Wfr": 120090, "Xfr": 120091, "Yfr": 120092, "afr": 120094, "bfr": 120095, "cfr": 120096, "dfr": 120097, "efr": 120098, "ffr": 120099, "gfr": 120100, "hfr": 120101, "ifr": 120102, "jfr": 120103, "kfr": 120104, "lfr": 120105, "mfr": 120106, "nfr": 120107, "ofr": 120108, "pfr": 120109, "qfr": 120110, "rfr": 120111, "sfr": 120112, "tfr": 120113, "ufr": 120114, "vfr": 120115, "wfr": 120116, "xfr": 120117, "yfr": 120118, "zfr": 120119, "Aopf": 120120, "Bopf": 120121, "Dopf": 120123, "Eopf": 120124, "Fopf": 120125, "Gopf": 120126, "Iopf": 120128, "Jopf": 120129, "Kopf": 120130, "Lopf": 120131, "Mopf": 120132, "Oopf": 120134, "Sopf": 120138, "Topf": 120139, "Uopf": 120140, "Vopf": 120141, "Wopf": 120142, "Xopf": 120143, "Yopf": 120144, "aopf": 120146, "bopf": 120147, "copf": 120148, "dopf": 120149, "eopf": 120150, "fopf": 120151, "gopf": 120152, "hopf": 120153, "iopf": 120154, "jopf": 120155, "kopf": 120156, "lopf": 120157, "mopf": 120158, "nopf": 120159, "oopf": 120160, "popf": 120161, "qopf": 120162, "ropf": 120163, "sopf": 120164, "topf": 120165, "uopf": 120166, "vopf": 120167, "wopf": 120168, "xopf": 120169, "yopf": 120170, "zopf": 120171 }; export default FromHTMLEntity; ================================================ FILE: src/core/operations/FromHex.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {fromHex, FROM_HEX_DELIM_OPTIONS} from "../lib/Hex.mjs"; import Utils from "../Utils.mjs"; /** * From Hex operation */ class FromHex extends Operation { /** * FromHex constructor */ constructor() { super(); this.name = "From Hex"; this.module = "Default"; this.description = "Converts a hexadecimal byte string back into its raw value.

e.g. ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a becomes the UTF-8 encoded string Γειά σου"; this.infoURL = "https://wikipedia.org/wiki/Hexadecimal"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { name: "Delimiter", type: "option", value: FROM_HEX_DELIM_OPTIONS } ]; this.checks = [ { pattern: "^(?:[\\dA-F]{2})+$", flags: "i", args: ["None"] }, { pattern: "^[\\dA-F]{2}(?: [\\dA-F]{2})*$", flags: "i", args: ["Space"] }, { pattern: "^[\\dA-F]{2}(?:,[\\dA-F]{2})*$", flags: "i", args: ["Comma"] }, { pattern: "^[\\dA-F]{2}(?:;[\\dA-F]{2})*$", flags: "i", args: ["Semi-colon"] }, { pattern: "^[\\dA-F]{2}(?::[\\dA-F]{2})*$", flags: "i", args: ["Colon"] }, { pattern: "^[\\dA-F]{2}(?:\\n[\\dA-F]{2})*$", flags: "i", args: ["Line feed"] }, { pattern: "^[\\dA-F]{2}(?:\\r\\n[\\dA-F]{2})*$", flags: "i", args: ["CRLF"] }, { pattern: "^(?:0x[\\dA-F]{2})+$", flags: "i", args: ["0x"] }, { pattern: "^0x[\\dA-F]{2}(?:,0x[\\dA-F]{2})*$", flags: "i", args: ["0x with comma"] }, { pattern: "^(?:\\\\x[\\dA-F]{2})+$", flags: "i", args: ["\\x"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const delim = args[0] || "Auto"; return fromHex(input, delim, 2); } /** * Highlight to Hex * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { if (args[0] === "Auto") return false; const delim = Utils.charRep(args[0] || "Space"), len = delim === "\r\n" ? 1 : delim.length, width = len + 2; // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly if (delim === "0x" || delim === "\\x") { if (pos[0].start > 1) pos[0].start -= 2; else pos[0].start = 0; if (pos[0].end > 1) pos[0].end -= 2; else pos[0].end = 0; } pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width); pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width); return pos; } /** * Highlight from Hex * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { const delim = Utils.charRep(args[0] || "Space"), len = delim === "\r\n" ? 1 : delim.length; pos[0].start = pos[0].start * (2 + len); pos[0].end = pos[0].end * (2 + len) - len; // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly if (delim === "0x" || delim === "\\x") { pos[0].start += 2; pos[0].end += 2; } return pos; } } export default FromHex; ================================================ FILE: src/core/operations/FromHexContent.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {fromHex} from "../lib/Hex.mjs"; /** * From Hex Content operation */ class FromHexContent extends Operation { /** * FromHexContent constructor */ constructor() { super(); this.name = "From Hex Content"; this.module = "Default"; this.description = "Translates hexadecimal bytes in text back to raw bytes. This format is used by SNORT for representing hex within ASCII text.

e.g. foo|3d|bar becomes foo=bar."; this.infoURL = "http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000"; this.inputType = "string"; this.outputType = "byteArray"; this.args = []; this.checks = [ { pattern: "\\|([\\da-f]{2} ?)+\\|", flags: "i", args: [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const regex = /\|([a-f\d ]{2,})\|/gi, output = []; let m, i = 0; while ((m = regex.exec(input))) { // Add up to match for (; i < m.index;) output.push(Utils.ord(input[i++])); // Add match const bytes = fromHex(m[1]); if (bytes) { for (let a = 0; a < bytes.length;) output.push(bytes[a++]); } else { // Not valid hex, print as normal for (; i < regex.lastIndex;) output.push(Utils.ord(input[i++])); } i = regex.lastIndex; } // Add all after final match for (; i < input.length;) output.push(Utils.ord(input[i++])); return output; } } export default FromHexContent; ================================================ FILE: src/core/operations/FromHexdump.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { fromHex } from "../lib/Hex.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * From Hexdump operation */ class FromHexdump extends Operation { /** * FromHexdump constructor */ constructor() { super(); this.name = "From Hexdump"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Hex_dump"; this.inputType = "string"; this.outputType = "byteArray"; this.args = []; this.checks = [ { 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,}$", flags: "i", args: [] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const output = [], 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; let block, line; while ((block = regex.exec(input))) { line = fromHex(block[1].replace(/-/g, " ")); for (let i = 0; i < line.length; i++) { output.push(line[i]); } } // Is this a CyberChef hexdump or is it from a different tool? const width = input.indexOf("\n"); const w = (width - 13) / 4; // w should be the specified width of the hexdump and therefore a round number if (Math.floor(w) !== w || input.indexOf("\r") !== -1 || output.indexOf(13) !== -1) { if (isWorkerEnvironment()) self.setOption("attemptHighlight", false); } return output; } /** * Highlight From Hexdump * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { const w = args[0] || 16; const width = 14 + (w*4); let line = Math.floor(pos[0].start / width); let offset = pos[0].start % width; if (offset < 10) { // In line number section pos[0].start = line*w; } else if (offset > 10+(w*3)) { // In ASCII section pos[0].start = (line+1)*w; } else { // In byte section pos[0].start = line*w + Math.floor((offset-10)/3); } line = Math.floor(pos[0].end / width); offset = pos[0].end % width; if (offset < 10) { // In line number section pos[0].end = line*w; } else if (offset > 10+(w*3)) { // In ASCII section pos[0].end = (line+1)*w; } else { // In byte section pos[0].end = line*w + Math.ceil((offset-10)/3); } return pos; } /** * Highlight From Hexdump in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { // Calculate overall selection const w = args[0] || 16, width = 14 + (w*4); let line = Math.floor(pos[0].start / w), offset = pos[0].start % w, start = 0, end = 0; pos[0].start = line*width + 10 + offset*3; line = Math.floor(pos[0].end / w); offset = pos[0].end % w; if (offset === 0) { line--; offset = w; } pos[0].end = line*width + 10 + offset*3 - 1; // Set up multiple selections for bytes let startLineNum = Math.floor(pos[0].start / width); const endLineNum = Math.floor(pos[0].end / width); if (startLineNum === endLineNum) { pos.push(pos[0]); } else { start = pos[0].start; end = (startLineNum+1) * width - w - 5; pos.push({ start: start, end: end }); while (end < pos[0].end) { startLineNum++; start = startLineNum * width + 10; end = (startLineNum+1) * width - w - 5; if (end > pos[0].end) end = pos[0].end; pos.push({ start: start, end: end }); } } // Set up multiple selections for ASCII const len = pos.length; let lineNum = 0; start = 0; end = 0; for (let i = 1; i < len; i++) { lineNum = Math.floor(pos[i].start / width); start = (((pos[i].start - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); end = (((pos[i].end + 1 - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); pos.push({ start: start, end: end }); } return pos; } } export default FromHexdump; ================================================ FILE: src/core/operations/FromMessagePack.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import notepack from "notepack.io"; /** * From MessagePack operation */ class FromMessagePack extends Operation { /** * FromMessagePack constructor */ constructor() { super(); this.name = "From MessagePack"; this.module = "Code"; 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."; this.infoURL = "https://wikipedia.org/wiki/MessagePack"; this.inputType = "ArrayBuffer"; this.outputType = "JSON"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {JSON} */ run(input, args) { try { const buf = Buffer.from(new Uint8Array(input)); return notepack.decode(buf); } catch (err) { throw new OperationError(`Could not decode MessagePack to JSON: ${err}`); } } } export default FromMessagePack; ================================================ FILE: src/core/operations/FromModhex.mjs ================================================ /** * @author linuxgemini [ilteris@asenkron.com.tr] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { FROM_MODHEX_DELIM_OPTIONS, fromModhex } from "../lib/Modhex.mjs"; /** * From Modhex operation */ class FromModhex extends Operation { /** * FromModhex constructor */ constructor() { super(); this.name = "From Modhex"; this.module = "Default"; this.description = "Converts a modhex byte string back into its raw value."; this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { name: "Delimiter", type: "option", value: FROM_MODHEX_DELIM_OPTIONS } ]; this.checks = [ { pattern: "^(?:[cbdefghijklnrtuv]{2})+$", flags: "i", args: ["None"] }, { pattern: "^[cbdefghijklnrtuv]{2}(?: [cbdefghijklnrtuv]{2})*$", flags: "i", args: ["Space"] }, { pattern: "^[cbdefghijklnrtuv]{2}(?:,[cbdefghijklnrtuv]{2})*$", flags: "i", args: ["Comma"] }, { pattern: "^[cbdefghijklnrtuv]{2}(?:;[cbdefghijklnrtuv]{2})*$", flags: "i", args: ["Semi-colon"] }, { pattern: "^[cbdefghijklnrtuv]{2}(?::[cbdefghijklnrtuv]{2})*$", flags: "i", args: ["Colon"] }, { pattern: "^[cbdefghijklnrtuv]{2}(?:\\n[cbdefghijklnrtuv]{2})*$", flags: "i", args: ["Line feed"] }, { pattern: "^[cbdefghijklnrtuv]{2}(?:\\r\\n[cbdefghijklnrtuv]{2})*$", flags: "i", args: ["CRLF"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const delim = args[0] || "Auto"; return fromModhex(input, delim, 2); } } export default FromModhex; ================================================ FILE: src/core/operations/FromMorseCode.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * From Morse Code operation */ class FromMorseCode extends Operation { /** * FromMorseCode constructor */ constructor() { super(); this.name = "From Morse Code"; this.module = "Default"; this.description = "Translates Morse Code into (upper case) alphanumeric characters."; this.infoURL = "https://wikipedia.org/wiki/Morse_code"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Letter delimiter", "type": "option", "value": LETTER_DELIM_OPTIONS }, { "name": "Word delimiter", "type": "option", "value": WORD_DELIM_OPTIONS } ]; this.checks = [ { pattern: "(?:^[-. \\n]{5,}$|^[_. \\n]{5,}$|^(?:dash|dot| |\\n){5,}$)", flags: "i", args: ["Space", "Line feed"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!this.reversedTable) { this.reverseTable(); } const letterDelim = Utils.charRep(args[0]); const wordDelim = Utils.charRep(args[1]); input = input.replace(/-|‐|−|_|–|—|dash/ig, ""); // hyphen-minus|hyphen|minus-sign|undersore|en-dash|em-dash input = input.replace(/\.|·|dot/ig, ""); let words = input.split(wordDelim); const self = this; words = Array.prototype.map.call(words, function(word) { const signals = word.split(letterDelim); const letters = signals.map(function(signal) { return self.reversedTable[signal]; }); return letters.join(""); }); words = words.join(" "); return words; } /** * Reverses the Morse Code lookup table */ reverseTable() { this.reversedTable = {}; for (const letter in MORSE_TABLE) { const signal = MORSE_TABLE[letter]; this.reversedTable[signal] = letter; } } } const MORSE_TABLE = { "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": "", "1": "", "2": "", "3": "", "4": "", "5": "", "6": "", "7": "", "8": "", "9": "", "0": "", ".": "", ",": "", ":": "", ";": "", "!": "", "?": "", "'": "", "\"": "", "/": "", "-": "", "+": "", "(": "", ")": "", "@": "", "=": "", "&": "", "_": "", "$": "", " ": "" }; export default FromMorseCode; ================================================ FILE: src/core/operations/FromOctal.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * From Octal operation */ class FromOctal extends Operation { /** * FromOctal constructor */ constructor() { super(); this.name = "From Octal"; this.module = "Default"; this.description = "Converts an octal byte string back into its raw value.

e.g. 316 223 316 265 316 271 316 254 40 317 203 316 277 317 205 becomes the UTF-8 encoded string Γειά σου"; this.infoURL = "https://wikipedia.org/wiki/Octal"; this.inputType = "string"; this.outputType = "byteArray"; this.args = [ { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS } ]; this.checks = [ { pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?: (?:[0-7]{1,2}|[123][0-7]{2}))*$", flags: "", args: ["Space"] }, { pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:,(?:[0-7]{1,2}|[123][0-7]{2}))*$", flags: "", args: ["Comma"] }, { pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:;(?:[0-7]{1,2}|[123][0-7]{2}))*$", flags: "", args: ["Semi-colon"] }, { pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?::(?:[0-7]{1,2}|[123][0-7]{2}))*$", flags: "", args: ["Colon"] }, { pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", flags: "", args: ["Line feed"] }, { pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\r\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", flags: "", args: ["CRLF"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const delim = Utils.charRep(args[0] || "Space"); if (input.length === 0) return []; return input.split(delim).map(val => parseInt(val, 8)); } } export default FromOctal; ================================================ FILE: src/core/operations/FromPunycode.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import punycode from "punycode"; /** * From Punycode operation */ class FromPunycode extends Operation { /** * FromPunycode constructor */ constructor() { super(); this.name = "From Punycode"; this.module = "Encodings"; this.description = "Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.

e.g. mnchen-3ya decodes to m\xfcnchen"; this.infoURL = "https://wikipedia.org/wiki/Punycode"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Internationalised domain name", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const idn = args[0]; if (idn) { return punycode.toUnicode(input); } else { return punycode.decode(input); } } } export default FromPunycode; ================================================ FILE: src/core/operations/FromQuotedPrintable.mjs ================================================ /** * Some parts taken from mimelib (http://github.com/andris9/mimelib) * @author Andris Reinman * @license MIT * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * From Quoted Printable operation */ class FromQuotedPrintable extends Operation { /** * FromQuotedPrintable constructor */ constructor() { super(); this.name = "From Quoted Printable"; this.module = "Default"; this.description = "Converts QP-encoded text back to standard text. This format is a content transfer encoding common in email messages.

e.g. The quoted-printable encoded string hello=20world becomes hello world"; this.infoURL = "https://wikipedia.org/wiki/Quoted-printable"; this.inputType = "string"; this.outputType = "byteArray"; this.args = []; this.checks = [ { pattern: "^[\\x21-\\x3d\\x3f-\\x7e \\t]{0,76}(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$", flags: "i", args: [] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const str = input.replace(/=(?:\r?\n|$)/g, ""); const encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length, bufferLength = str.length - encodedBytesCount * 2, buffer = new Array(bufferLength); let chr, hex, bufferPos = 0; for (let i = 0, len = str.length; i < len; i++) { chr = str.charAt(i); if (chr === "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { buffer[bufferPos++] = parseInt(hex, 16); i += 2; continue; } buffer[bufferPos++] = chr.charCodeAt(0); } return buffer; } } export default FromQuotedPrintable; ================================================ FILE: src/core/operations/FromUNIXTimestamp.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import moment from "moment-timezone"; import {UNITS} from "../lib/DateTime.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * From UNIX Timestamp operation */ class FromUNIXTimestamp extends Operation { /** * FromUNIXTimestamp constructor */ constructor() { super(); this.name = "From UNIX Timestamp"; this.module = "Default"; this.description = "Converts a UNIX timestamp to a datetime string.

e.g. 978346800 becomes Mon 1 January 2001 11:00:00 UTC

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch)."; this.infoURL = "https://wikipedia.org/wiki/Unix_time"; this.inputType = "number"; this.outputType = "string"; this.args = [ { "name": "Units", "type": "option", "value": UNITS } ]; this.checks = [ { pattern: "^1?\\d{9}$", flags: "", args: ["Seconds (s)"] }, { pattern: "^1?\\d{12}$", flags: "", args: ["Milliseconds (ms)"] }, { pattern: "^1?\\d{15}$", flags: "", args: ["Microseconds (μs)"] }, { pattern: "^1?\\d{18}$", flags: "", args: ["Nanoseconds (ns)"] } ]; } /** * @param {number} input * @param {Object[]} args * @returns {string} * * @throws {OperationError} if invalid unit */ run(input, args) { const units = args[0]; let d; input = parseFloat(input); if (units === "Seconds (s)") { d = moment.unix(input); return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss") + " UTC"; } else if (units === "Milliseconds (ms)") { d = moment(input); return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; } else if (units === "Microseconds (μs)") { d = moment(input / 1000); return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; } else if (units === "Nanoseconds (ns)") { d = moment(input / 1000000); return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; } else { throw new OperationError("Unrecognised unit"); } } } export default FromUNIXTimestamp; ================================================ FILE: src/core/operations/FuzzyMatch.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {fuzzyMatch, calcMatchRanges, DEFAULT_WEIGHTS} from "../lib/FuzzyMatch.mjs"; import Utils from "../Utils.mjs"; /** * Fuzzy Match operation */ class FuzzyMatch extends Operation { /** * FuzzyMatch constructor */ constructor() { super(); this.name = "Fuzzy Match"; this.module = "Default"; this.description = "Conducts a fuzzy search to find a pattern within the input based on weighted criteria.

e.g. A search for dpan will match on Don't Panic"; this.infoURL = "https://wikipedia.org/wiki/Fuzzy_matching_(computer-assisted_translation)"; this.inputType = "string"; this.outputType = "html"; this.args = [ { name: "Search", type: "binaryString", value: "" }, { name: "Sequential bonus", type: "number", value: DEFAULT_WEIGHTS.sequentialBonus, hint: "Bonus for adjacent matches" }, { name: "Separator bonus", type: "number", value: DEFAULT_WEIGHTS.separatorBonus, hint: "Bonus if match occurs after a separator" }, { name: "Camel bonus", type: "number", value: DEFAULT_WEIGHTS.camelBonus, hint: "Bonus if match is uppercase and previous is lower" }, { name: "First letter bonus", type: "number", value: DEFAULT_WEIGHTS.firstLetterBonus, hint: "Bonus if the first letter is matched" }, { name: "Leading letter penalty", type: "number", value: DEFAULT_WEIGHTS.leadingLetterPenalty, hint: "Penalty applied for every letter in the input before the first match" }, { name: "Max leading letter penalty", type: "number", value: DEFAULT_WEIGHTS.maxLeadingLetterPenalty, hint: "Maxiumum penalty for leading letters" }, { name: "Unmatched letter penalty", type: "number", value: DEFAULT_WEIGHTS.unmatchedLetterPenalty }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const searchStr = args[0]; const weights = { sequentialBonus: args[1], separatorBonus: args[2], camelBonus: args[3], firstLetterBonus: args[4], leadingLetterPenalty: args[5], maxLeadingLetterPenalty: args[6], unmatchedLetterPenalty: args[7] }; const matches = fuzzyMatch(searchStr, input, true, weights); if (!matches) { return "No matches."; } let result = "", pos = 0, hlClass = "hl1"; matches.forEach(([matches, score, idxs]) => { const matchRanges = calcMatchRanges(idxs); matchRanges.forEach(([start, length], i) => { result += Utils.escapeHtml(input.slice(pos, start)); if (i === 0) result += ``; pos = start + length; result += `${Utils.escapeHtml(input.slice(start, pos))}`; }); result += ""; hlClass = hlClass === "hl1" ? "hl2" : "hl1"; }); result += Utils.escapeHtml(input.slice(pos, input.length)); return result; } } export default FuzzyMatch; ================================================ FILE: src/core/operations/GOSTDecrypt.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { toHexFast, fromHex } from "../lib/Hex.mjs"; import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; /** * GOST Decrypt operation */ class GOSTDecrypt extends Operation { /** * GOSTDecrypt constructor */ constructor() { super(); this.name = "GOST Decrypt"; this.module = "Ciphers"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Key", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "IV", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "Input type", type: "option", value: ["Hex", "Raw"] }, { name: "Output type", type: "option", value: ["Raw", "Hex"] }, { name: "Algorithm", type: "argSelector", value: [ { name: "GOST 28147 (1989)", on: [5] }, { name: "GOST R 34.12 (Magma, 2015)", off: [5] }, { name: "GOST R 34.12 (Kuznyechik, 2015)", off: [5] } ] }, { name: "sBox", type: "option", value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] }, { name: "Block mode", type: "option", value: ["ECB", "CFB", "OFB", "CTR", "CBC"] }, { name: "Key meshing mode", type: "option", value: ["NO", "CP"] }, { name: "Padding", type: "option", value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args; const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); let blockLength, versionNum; switch (version) { case "GOST 28147 (1989)": versionNum = 1989; blockLength = 64; break; case "GOST R 34.12 (Magma, 2015)": versionNum = 2015; blockLength = 64; break; case "GOST R 34.12 (Kuznyechik, 2015)": versionNum = 2015; blockLength = 128; break; default: throw new OperationError(`Unknown algorithm version: ${version}`); } const sBoxVal = versionNum === 1989 ? sBox : null; const algorithm = { version: versionNum, length: blockLength, mode: "ES", sBox: sBoxVal, block: blockMode, keyMeshing: keyMeshing, padding: padding }; try { const Hex = CryptoGost.coding.Hex; if (iv) algorithm.iv = Hex.decode(iv); const cipher = GostEngine.getGostCipher(algorithm); const out = Hex.encode(cipher.decrypt(Hex.decode(key), Hex.decode(input))); return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); } catch (err) { throw new OperationError(err); } } } export default GOSTDecrypt; ================================================ FILE: src/core/operations/GOSTEncrypt.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { toHexFast, fromHex } from "../lib/Hex.mjs"; import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; /** * GOST Encrypt operation */ class GOSTEncrypt extends Operation { /** * GOSTEncrypt constructor */ constructor() { super(); this.name = "GOST Encrypt"; this.module = "Ciphers"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Key", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "IV", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "Input type", type: "option", value: ["Raw", "Hex"] }, { name: "Output type", type: "option", value: ["Hex", "Raw"] }, { name: "Algorithm", type: "argSelector", value: [ { name: "GOST 28147 (1989)", on: [5] }, { name: "GOST R 34.12 (Magma, 2015)", off: [5] }, { name: "GOST R 34.12 (Kuznyechik, 2015)", off: [5] } ] }, { name: "sBox", type: "option", value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] }, { name: "Block mode", type: "option", value: ["ECB", "CFB", "OFB", "CTR", "CBC"] }, { name: "Key meshing mode", type: "option", value: ["NO", "CP"] }, { name: "Padding", type: "option", value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args; const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); let blockLength, versionNum; switch (version) { case "GOST 28147 (1989)": versionNum = 1989; blockLength = 64; break; case "GOST R 34.12 (Magma, 2015)": versionNum = 2015; blockLength = 64; break; case "GOST R 34.12 (Kuznyechik, 2015)": versionNum = 2015; blockLength = 128; break; default: throw new OperationError(`Unknown algorithm version: ${version}`); } const sBoxVal = versionNum === 1989 ? sBox : null; const algorithm = { version: versionNum, length: blockLength, mode: "ES", sBox: sBoxVal, block: blockMode, keyMeshing: keyMeshing, padding: padding }; try { const Hex = CryptoGost.coding.Hex; if (iv) algorithm.iv = Hex.decode(iv); const cipher = GostEngine.getGostCipher(algorithm); const out = Hex.encode(cipher.encrypt(Hex.decode(key), Hex.decode(input))); return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); } catch (err) { throw new OperationError(err); } } } export default GOSTEncrypt; ================================================ FILE: src/core/operations/GOSTHash.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import GostDigest from "../vendor/gost/gostDigest.mjs"; import { toHexFast } from "../lib/Hex.mjs"; /** * GOST hash operation */ class GOSTHash extends Operation { /** * GOSTHash constructor */ constructor() { super(); this.name = "GOST Hash"; this.module = "Hashing"; 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 Information Technology – Cryptographic Information Security – Hash Function. The equivalent standard used by other member-states of the CIS is GOST 34.311-95.

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.

The GOST hash function is based on the GOST block cipher."; this.infoURL = "https://wikipedia.org/wiki/GOST_(hash_function)"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Algorithm", type: "argSelector", value: [ { name: "GOST 28147 (1994)", off: [1], on: [2] }, { name: "GOST R 34.11 (Streebog, 2012)", on: [1], off: [2] } ] }, { name: "Digest length", type: "option", value: ["256", "512"] }, { name: "sBox", type: "option", value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [version, length, sBox] = args; const versionNum = version === "GOST 28147 (1994)" ? 1994 : 2012; const algorithm = { name: versionNum === 1994 ? "GOST 28147" : "GOST R 34.10", version: versionNum, mode: "HASH" }; if (versionNum === 1994) { algorithm.sBox = sBox; } else { algorithm.length = parseInt(length, 10); } try { const gostDigest = new GostDigest(algorithm); return toHexFast(gostDigest.digest(input)); } catch (err) { throw new OperationError(err); } } } export default GOSTHash; ================================================ FILE: src/core/operations/GOSTKeyUnwrap.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { toHexFast, fromHex } from "../lib/Hex.mjs"; import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; /** * GOST Key Unwrap operation */ class GOSTKeyUnwrap extends Operation { /** * GOSTKeyUnwrap constructor */ constructor() { super(); this.name = "GOST Key Unwrap"; this.module = "Ciphers"; this.description = "A decryptor for keys wrapped using one of the GOST block ciphers."; this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Key", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "User Key Material", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "Input type", type: "option", value: ["Hex", "Raw"] }, { name: "Output type", type: "option", value: ["Raw", "Hex"] }, { name: "Algorithm", type: "argSelector", value: [ { name: "GOST 28147 (1989)", on: [5] }, { name: "GOST R 34.12 (Magma, 2015)", off: [5] }, { name: "GOST R 34.12 (Kuznyechik, 2015)", off: [5] } ] }, { name: "sBox", type: "option", value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] }, { name: "Key wrapping", type: "option", value: ["NO", "CP", "SC"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args; const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); let blockLength, versionNum; switch (version) { case "GOST 28147 (1989)": versionNum = 1989; blockLength = 64; break; case "GOST R 34.12 (Magma, 2015)": versionNum = 2015; blockLength = 64; break; case "GOST R 34.12 (Kuznyechik, 2015)": versionNum = 2015; blockLength = 128; break; default: throw new OperationError(`Unknown algorithm version: ${version}`); } const sBoxVal = versionNum === 1989 ? sBox : null; const algorithm = { version: versionNum, length: blockLength, mode: "KW", sBox: sBoxVal, keyWrapping: keyWrapping }; try { const Hex = CryptoGost.coding.Hex; algorithm.ukm = Hex.decode(ukm); const cipher = GostEngine.getGostCipher(algorithm); const out = Hex.encode(cipher.unwrapKey(Hex.decode(key), Hex.decode(input))); return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); } catch (err) { if (err.toString().includes("Invalid typed array length")) { throw new OperationError("Incorrect input length. Must be a multiple of the block size."); } throw new OperationError(err); } } } export default GOSTKeyUnwrap; ================================================ FILE: src/core/operations/GOSTKeyWrap.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { toHexFast, fromHex } from "../lib/Hex.mjs"; import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; /** * GOST Key Wrap operation */ class GOSTKeyWrap extends Operation { /** * GOSTKeyWrap constructor */ constructor() { super(); this.name = "GOST Key Wrap"; this.module = "Ciphers"; this.description = "A key wrapping algorithm for protecting keys in untrusted storage using one of the GOST block cipers."; this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Key", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "User Key Material", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "Input type", type: "option", value: ["Raw", "Hex"] }, { name: "Output type", type: "option", value: ["Hex", "Raw"] }, { name: "Algorithm", type: "argSelector", value: [ { name: "GOST 28147 (1989)", on: [5] }, { name: "GOST R 34.12 (Magma, 2015)", off: [5] }, { name: "GOST R 34.12 (Kuznyechik, 2015)", off: [5] } ] }, { name: "sBox", type: "option", value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] }, { name: "Key wrapping", type: "option", value: ["NO", "CP", "SC"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args; const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); let blockLength, versionNum; switch (version) { case "GOST 28147 (1989)": versionNum = 1989; blockLength = 64; break; case "GOST R 34.12 (Magma, 2015)": versionNum = 2015; blockLength = 64; break; case "GOST R 34.12 (Kuznyechik, 2015)": versionNum = 2015; blockLength = 128; break; default: throw new OperationError(`Unknown algorithm version: ${version}`); } const sBoxVal = versionNum === 1989 ? sBox : null; const algorithm = { version: versionNum, length: blockLength, mode: "KW", sBox: sBoxVal, keyWrapping: keyWrapping }; try { const Hex = CryptoGost.coding.Hex; algorithm.ukm = Hex.decode(ukm); const cipher = GostEngine.getGostCipher(algorithm); const out = Hex.encode(cipher.wrapKey(Hex.decode(key), Hex.decode(input))); return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); } catch (err) { if (err.toString().includes("Invalid typed array length")) { throw new OperationError("Incorrect input length. Must be a multiple of the block size."); } throw new OperationError(err); } } } export default GOSTKeyWrap; ================================================ FILE: src/core/operations/GOSTSign.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { toHexFast, fromHex } from "../lib/Hex.mjs"; import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; /** * GOST Sign operation */ class GOSTSign extends Operation { /** * GOSTSign constructor */ constructor() { super(); this.name = "GOST Sign"; this.module = "Ciphers"; this.description = "Sign a plaintext message using one of the GOST block ciphers."; this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Key", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "IV", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "Input type", type: "option", value: ["Raw", "Hex"] }, { name: "Output type", type: "option", value: ["Hex", "Raw"] }, { name: "Algorithm", type: "argSelector", value: [ { name: "GOST 28147 (1989)", on: [5] }, { name: "GOST R 34.12 (Magma, 2015)", off: [5] }, { name: "GOST R 34.12 (Kuznyechik, 2015)", off: [5] } ] }, { name: "sBox", type: "option", value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] }, { name: "MAC length", type: "number", value: 32, min: 8, max: 64, step: 8 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [keyObj, ivObj, inputType, outputType, version, sBox, macLength] = args; const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); let blockLength, versionNum; switch (version) { case "GOST 28147 (1989)": versionNum = 1989; blockLength = 64; break; case "GOST R 34.12 (Magma, 2015)": versionNum = 2015; blockLength = 64; break; case "GOST R 34.12 (Kuznyechik, 2015)": versionNum = 2015; blockLength = 128; break; default: throw new OperationError(`Unknown algorithm version: ${version}`); } const sBoxVal = versionNum === 1989 ? sBox : null; const algorithm = { version: versionNum, length: blockLength, mode: "MAC", sBox: sBoxVal, macLength: macLength }; try { const Hex = CryptoGost.coding.Hex; if (iv) algorithm.iv = Hex.decode(iv); const cipher = GostEngine.getGostCipher(algorithm); const out = Hex.encode(cipher.sign(Hex.decode(key), Hex.decode(input))); return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); } catch (err) { throw new OperationError(err); } } } export default GOSTSign; ================================================ FILE: src/core/operations/GOSTVerify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { toHexFast } from "../lib/Hex.mjs"; import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; /** * GOST Verify operation */ class GOSTVerify extends Operation { /** * GOSTVerify constructor */ constructor() { super(); this.name = "GOST Verify"; this.module = "Ciphers"; this.description = "Verify the signature of a plaintext message using one of the GOST block ciphers. Enter the signature in the MAC field."; this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Key", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "IV", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "MAC", type: "toggleString", value: "", toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] }, { name: "Input type", type: "option", value: ["Raw", "Hex"] }, { name: "Algorithm", type: "argSelector", value: [ { name: "GOST 28147 (1989)", on: [5] }, { name: "GOST R 34.12 (Magma, 2015)", off: [5] }, { name: "GOST R 34.12 (Kuznyechik, 2015)", off: [5] } ] }, { name: "sBox", type: "option", value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [keyObj, ivObj, macObj, inputType, version, sBox] = args; const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option)); input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); let blockLength, versionNum; switch (version) { case "GOST 28147 (1989)": versionNum = 1989; blockLength = 64; break; case "GOST R 34.12 (Magma, 2015)": versionNum = 2015; blockLength = 64; break; case "GOST R 34.12 (Kuznyechik, 2015)": versionNum = 2015; blockLength = 128; break; default: throw new OperationError(`Unknown algorithm version: ${version}`); } const sBoxVal = versionNum === 1989 ? sBox : null; const algorithm = { version: versionNum, length: blockLength, mode: "MAC", sBox: sBoxVal, macLength: mac.length * 4 }; try { const Hex = CryptoGost.coding.Hex; if (iv) algorithm.iv = Hex.decode(iv); const cipher = GostEngine.getGostCipher(algorithm); const out = cipher.verify(Hex.decode(key), Hex.decode(mac), Hex.decode(input)); return out ? "The signature matches" : "The signature does not match"; } catch (err) { throw new OperationError(err); } } } export default GOSTVerify; ================================================ FILE: src/core/operations/GenerateAllChecksums.mjs ================================================ /** * @author r4mos [2k95ljkhg@mozmail.com] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Adler32Checksum from "./Adler32Checksum.mjs"; import CRCChecksum from "./CRCChecksum.mjs"; import Fletcher8Checksum from "./Fletcher8Checksum.mjs"; import Fletcher16Checksum from "./Fletcher16Checksum.mjs"; import Fletcher32Checksum from "./Fletcher32Checksum.mjs"; import Fletcher64Checksum from "./Fletcher64Checksum.mjs"; /** * Generate all checksums operation */ class GenerateAllChecksums extends Operation { /** * GenerateAllChecksums constructor */ constructor() { super(); this.name = "Generate all checksums"; this.module = "Crypto"; this.description = "Generates all available checksums for the input."; this.infoURL = "https://wikipedia.org/wiki/Checksum"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Length (bits)", type: "option", value: [ "All", "3", "4", "5", "6", "7", "8", "10", "11", "12", "13", "14", "15", "16", "17", "21", "24", "30", "31", "32", "40", "64", "82" ] }, { name: "Include names", type: "boolean", value: true }, ]; const adler32 = new Adler32Checksum; const crc = new CRCChecksum; const fletcher8 = new Fletcher8Checksum; const fletcher16 = new Fletcher16Checksum; const fletcher32 = new Fletcher32Checksum; const fletcher64 = new Fletcher64Checksum; this.checksums = [ {name: "CRC-3/GSM", algo: crc, params: ["CRC-3/GSM"]}, {name: "CRC-3/ROHC", algo: crc, params: ["CRC-3/ROHC"]}, {name: "CRC-4/G-704", algo: crc, params: ["CRC-4/G-704"]}, {name: "CRC-4/INTERLAKEN", algo: crc, params: ["CRC-4/INTERLAKEN"]}, {name: "CRC-4/ITU", algo: crc, params: ["CRC-4/ITU"]}, {name: "CRC-5/EPC", algo: crc, params: ["CRC-5/EPC"]}, {name: "CRC-5/EPC-C1G2", algo: crc, params: ["CRC-5/EPC-C1G2"]}, {name: "CRC-5/G-704", algo: crc, params: ["CRC-5/G-704"]}, {name: "CRC-5/ITU", algo: crc, params: ["CRC-5/ITU"]}, {name: "CRC-5/USB", algo: crc, params: ["CRC-5/USB"]}, {name: "CRC-6/CDMA2000-A", algo: crc, params: ["CRC-6/CDMA2000-A"]}, {name: "CRC-6/CDMA2000-B", algo: crc, params: ["CRC-6/CDMA2000-B"]}, {name: "CRC-6/DARC", algo: crc, params: ["CRC-6/DARC"]}, {name: "CRC-6/G-704", algo: crc, params: ["CRC-6/G-704"]}, {name: "CRC-6/GSM", algo: crc, params: ["CRC-6/GSM"]}, {name: "CRC-6/ITU", algo: crc, params: ["CRC-6/ITU"]}, {name: "CRC-7/MMC", algo: crc, params: ["CRC-7/MMC"]}, {name: "CRC-7/ROHC", algo: crc, params: ["CRC-7/ROHC"]}, {name: "CRC-7/UMTS", algo: crc, params: ["CRC-7/UMTS"]}, {name: "CRC-8", algo: crc, params: ["CRC-8"]}, {name: "CRC-8/8H2F", algo: crc, params: ["CRC-8/8H2F"]}, {name: "CRC-8/AES", algo: crc, params: ["CRC-8/AES"]}, {name: "CRC-8/AUTOSAR", algo: crc, params: ["CRC-8/AUTOSAR"]}, {name: "CRC-8/BLUETOOTH", algo: crc, params: ["CRC-8/BLUETOOTH"]}, {name: "CRC-8/CDMA2000", algo: crc, params: ["CRC-8/CDMA2000"]}, {name: "CRC-8/DARC", algo: crc, params: ["CRC-8/DARC"]}, {name: "CRC-8/DVB-S2", algo: crc, params: ["CRC-8/DVB-S2"]}, {name: "CRC-8/EBU", algo: crc, params: ["CRC-8/EBU"]}, {name: "CRC-8/GSM-A", algo: crc, params: ["CRC-8/GSM-A"]}, {name: "CRC-8/GSM-B", algo: crc, params: ["CRC-8/GSM-B"]}, {name: "CRC-8/HITAG", algo: crc, params: ["CRC-8/HITAG"]}, {name: "CRC-8/I-432-1", algo: crc, params: ["CRC-8/I-432-1"]}, {name: "CRC-8/I-CODE", algo: crc, params: ["CRC-8/I-CODE"]}, {name: "CRC-8/ITU", algo: crc, params: ["CRC-8/ITU"]}, {name: "CRC-8/LTE", algo: crc, params: ["CRC-8/LTE"]}, {name: "CRC-8/MAXIM", algo: crc, params: ["CRC-8/MAXIM"]}, {name: "CRC-8/MAXIM-DOW", algo: crc, params: ["CRC-8/MAXIM-DOW"]}, {name: "CRC-8/MIFARE-MAD", algo: crc, params: ["CRC-8/MIFARE-MAD"]}, {name: "CRC-8/NRSC-5", algo: crc, params: ["CRC-8/NRSC-5"]}, {name: "CRC-8/OPENSAFETY", algo: crc, params: ["CRC-8/OPENSAFETY"]}, {name: "CRC-8/ROHC", algo: crc, params: ["CRC-8/ROHC"]}, {name: "CRC-8/SAE-J1850", algo: crc, params: ["CRC-8/SAE-J1850"]}, {name: "CRC-8/SAE-J1850-ZERO", algo: crc, params: ["CRC-8/SAE-J1850-ZERO"]}, {name: "CRC-8/SMBUS", algo: crc, params: ["CRC-8/SMBUS"]}, {name: "CRC-8/TECH-3250", algo: crc, params: ["CRC-8/TECH-3250"]}, {name: "CRC-8/WCDMA", algo: crc, params: ["CRC-8/WCDMA"]}, {name: "Fletcher-8", algo: fletcher8, params: []}, {name: "CRC-10/ATM", algo: crc, params: ["CRC-10/ATM"]}, {name: "CRC-10/CDMA2000", algo: crc, params: ["CRC-10/CDMA2000"]}, {name: "CRC-10/GSM", algo: crc, params: ["CRC-10/GSM"]}, {name: "CRC-10/I-610", algo: crc, params: ["CRC-10/I-610"]}, {name: "CRC-11/FLEXRAY", algo: crc, params: ["CRC-11/FLEXRAY"]}, {name: "CRC-11/UMTS", algo: crc, params: ["CRC-11/UMTS"]}, {name: "CRC-12/3GPP", algo: crc, params: ["CRC-12/3GPP"]}, {name: "CRC-12/CDMA2000", algo: crc, params: ["CRC-12/CDMA2000"]}, {name: "CRC-12/DECT", algo: crc, params: ["CRC-12/DECT"]}, {name: "CRC-12/GSM", algo: crc, params: ["CRC-12/GSM"]}, {name: "CRC-12/UMTS", algo: crc, params: ["CRC-12/UMTS"]}, {name: "CRC-13/BBC", algo: crc, params: ["CRC-13/BBC"]}, {name: "CRC-14/DARC", algo: crc, params: ["CRC-14/DARC"]}, {name: "CRC-14/GSM", algo: crc, params: ["CRC-14/GSM"]}, {name: "CRC-15/CAN", algo: crc, params: ["CRC-15/CAN"]}, {name: "CRC-15/MPT1327", algo: crc, params: ["CRC-15/MPT1327"]}, {name: "CRC-16", algo: crc, params: ["CRC-16"]}, {name: "CRC-16/A", algo: crc, params: ["CRC-16/A"]}, {name: "CRC-16/ACORN", algo: crc, params: ["CRC-16/ACORN"]}, {name: "CRC-16/ARC", algo: crc, params: ["CRC-16/ARC"]}, {name: "CRC-16/AUG-CCITT", algo: crc, params: ["CRC-16/AUG-CCITT"]}, {name: "CRC-16/AUTOSAR", algo: crc, params: ["CRC-16/AUTOSAR"]}, {name: "CRC-16/B", algo: crc, params: ["CRC-16/B"]}, {name: "CRC-16/BLUETOOTH", algo: crc, params: ["CRC-16/BLUETOOTH"]}, {name: "CRC-16/BUYPASS", algo: crc, params: ["CRC-16/BUYPASS"]}, {name: "CRC-16/CCITT", algo: crc, params: ["CRC-16/CCITT"]}, {name: "CRC-16/CCITT-FALSE", algo: crc, params: ["CRC-16/CCITT-FALSE"]}, {name: "CRC-16/CCITT-TRUE", algo: crc, params: ["CRC-16/CCITT-TRUE"]}, {name: "CRC-16/CCITT-ZERO", algo: crc, params: ["CRC-16/CCITT-ZERO"]}, {name: "CRC-16/CDMA2000", algo: crc, params: ["CRC-16/CDMA2000"]}, {name: "CRC-16/CMS", algo: crc, params: ["CRC-16/CMS"]}, {name: "CRC-16/DARC", algo: crc, params: ["CRC-16/DARC"]}, {name: "CRC-16/DDS-110", algo: crc, params: ["CRC-16/DDS-110"]}, {name: "CRC-16/DECT-R", algo: crc, params: ["CRC-16/DECT-R"]}, {name: "CRC-16/DECT-X", algo: crc, params: ["CRC-16/DECT-X"]}, {name: "CRC-16/DNP", algo: crc, params: ["CRC-16/DNP"]}, {name: "CRC-16/EN-13757", algo: crc, params: ["CRC-16/EN-13757"]}, {name: "CRC-16/EPC", algo: crc, params: ["CRC-16/EPC"]}, {name: "CRC-16/EPC-C1G2", algo: crc, params: ["CRC-16/EPC-C1G2"]}, {name: "CRC-16/GENIBUS", algo: crc, params: ["CRC-16/GENIBUS"]}, {name: "CRC-16/GSM", algo: crc, params: ["CRC-16/GSM"]}, {name: "CRC-16/I-CODE", algo: crc, params: ["CRC-16/I-CODE"]}, {name: "CRC-16/IBM", algo: crc, params: ["CRC-16/IBM"]}, {name: "CRC-16/IBM-3740", algo: crc, params: ["CRC-16/IBM-3740"]}, {name: "CRC-16/IBM-SDLC", algo: crc, params: ["CRC-16/IBM-SDLC"]}, {name: "CRC-16/IEC-61158-2", algo: crc, params: ["CRC-16/IEC-61158-2"]}, {name: "CRC-16/ISO-HDLC", algo: crc, params: ["CRC-16/ISO-HDLC"]}, {name: "CRC-16/ISO-IEC-14443-3-A", algo: crc, params: ["CRC-16/ISO-IEC-14443-3-A"]}, {name: "CRC-16/ISO-IEC-14443-3-B", algo: crc, params: ["CRC-16/ISO-IEC-14443-3-B"]}, {name: "CRC-16/KERMIT", algo: crc, params: ["CRC-16/KERMIT"]}, {name: "CRC-16/LHA", algo: crc, params: ["CRC-16/LHA"]}, {name: "CRC-16/LJ1200", algo: crc, params: ["CRC-16/LJ1200"]}, {name: "CRC-16/LTE", algo: crc, params: ["CRC-16/LTE"]}, {name: "CRC-16/M17", algo: crc, params: ["CRC-16/M17"]}, {name: "CRC-16/MAXIM", algo: crc, params: ["CRC-16/MAXIM"]}, {name: "CRC-16/MAXIM-DOW", algo: crc, params: ["CRC-16/MAXIM-DOW"]}, {name: "CRC-16/MCRF4XX", algo: crc, params: ["CRC-16/MCRF4XX"]}, {name: "CRC-16/MODBUS", algo: crc, params: ["CRC-16/MODBUS"]}, {name: "CRC-16/NRSC-5", algo: crc, params: ["CRC-16/NRSC-5"]}, {name: "CRC-16/OPENSAFETY-A", algo: crc, params: ["CRC-16/OPENSAFETY-A"]}, {name: "CRC-16/OPENSAFETY-B", algo: crc, params: ["CRC-16/OPENSAFETY-B"]}, {name: "CRC-16/PROFIBUS", algo: crc, params: ["CRC-16/PROFIBUS"]}, {name: "CRC-16/RIELLO", algo: crc, params: ["CRC-16/RIELLO"]}, {name: "CRC-16/SPI-FUJITSU", algo: crc, params: ["CRC-16/SPI-FUJITSU"]}, {name: "CRC-16/T10-DIF", algo: crc, params: ["CRC-16/T10-DIF"]}, {name: "CRC-16/TELEDISK", algo: crc, params: ["CRC-16/TELEDISK"]}, {name: "CRC-16/TMS37157", algo: crc, params: ["CRC-16/TMS37157"]}, {name: "CRC-16/UMTS", algo: crc, params: ["CRC-16/UMTS"]}, {name: "CRC-16/USB", algo: crc, params: ["CRC-16/USB"]}, {name: "CRC-16/V-41-LSB", algo: crc, params: ["CRC-16/V-41-LSB"]}, {name: "CRC-16/V-41-MSB", algo: crc, params: ["CRC-16/V-41-MSB"]}, {name: "CRC-16/VERIFONE", algo: crc, params: ["CRC-16/VERIFONE"]}, {name: "CRC-16/X-25", algo: crc, params: ["CRC-16/X-25"]}, {name: "CRC-16/XMODEM", algo: crc, params: ["CRC-16/XMODEM"]}, {name: "CRC-16/ZMODEM", algo: crc, params: ["CRC-16/ZMODEM"]}, {name: "Fletcher-16", algo: fletcher16, params: []}, {name: "CRC-17/CAN-FD", algo: crc, params: ["CRC-17/CAN-FD"]}, {name: "CRC-21/CAN-FD", algo: crc, params: ["CRC-21/CAN-FD"]}, {name: "CRC-24/BLE", algo: crc, params: ["CRC-24/BLE"]}, {name: "CRC-24/FLEXRAY-A", algo: crc, params: ["CRC-24/FLEXRAY-A"]}, {name: "CRC-24/FLEXRAY-B", algo: crc, params: ["CRC-24/FLEXRAY-B"]}, {name: "CRC-24/INTERLAKEN", algo: crc, params: ["CRC-24/INTERLAKEN"]}, {name: "CRC-24/LTE-A", algo: crc, params: ["CRC-24/LTE-A"]}, {name: "CRC-24/LTE-B", algo: crc, params: ["CRC-24/LTE-B"]}, {name: "CRC-24/OPENPGP", algo: crc, params: ["CRC-24/OPENPGP"]}, {name: "CRC-24/OS-9", algo: crc, params: ["CRC-24/OS-9"]}, {name: "CRC-30/CDMA", algo: crc, params: ["CRC-30/CDMA"]}, {name: "CRC-31/PHILIPS", algo: crc, params: ["CRC-31/PHILIPS"]}, {name: "Adler-32", algo: adler32, params: []}, {name: "CRC-32", algo: crc, params: ["CRC-32"]}, {name: "CRC-32/AAL5", algo: crc, params: ["CRC-32/AAL5"]}, {name: "CRC-32/ADCCP", algo: crc, params: ["CRC-32/ADCCP"]}, {name: "CRC-32/AIXM", algo: crc, params: ["CRC-32/AIXM"]}, {name: "CRC-32/AUTOSAR", algo: crc, params: ["CRC-32/AUTOSAR"]}, {name: "CRC-32/BASE91-C", algo: crc, params: ["CRC-32/BASE91-C"]}, {name: "CRC-32/BASE91-D", algo: crc, params: ["CRC-32/BASE91-D"]}, {name: "CRC-32/BZIP2", algo: crc, params: ["CRC-32/BZIP2"]}, {name: "CRC-32/C", algo: crc, params: ["CRC-32/C"]}, {name: "CRC-32/CASTAGNOLI", algo: crc, params: ["CRC-32/CASTAGNOLI"]}, {name: "CRC-32/CD-ROM-EDC", algo: crc, params: ["CRC-32/CD-ROM-EDC"]}, {name: "CRC-32/CKSUM", algo: crc, params: ["CRC-32/CKSUM"]}, {name: "CRC-32/D", algo: crc, params: ["CRC-32/D"]}, {name: "CRC-32/DECT-B", algo: crc, params: ["CRC-32/DECT-B"]}, {name: "CRC-32/INTERLAKEN", algo: crc, params: ["CRC-32/INTERLAKEN"]}, {name: "CRC-32/ISCSI", algo: crc, params: ["CRC-32/ISCSI"]}, {name: "CRC-32/ISO-HDLC", algo: crc, params: ["CRC-32/ISO-HDLC"]}, {name: "CRC-32/JAMCRC", algo: crc, params: ["CRC-32/JAMCRC"]}, {name: "CRC-32/MEF", algo: crc, params: ["CRC-32/MEF"]}, {name: "CRC-32/MPEG-2", algo: crc, params: ["CRC-32/MPEG-2"]}, {name: "CRC-32/NVME", algo: crc, params: ["CRC-32/NVME"]}, {name: "CRC-32/PKZIP", algo: crc, params: ["CRC-32/PKZIP"]}, {name: "CRC-32/POSIX", algo: crc, params: ["CRC-32/POSIX"]}, {name: "CRC-32/Q", algo: crc, params: ["CRC-32/Q"]}, {name: "CRC-32/SATA", algo: crc, params: ["CRC-32/SATA"]}, {name: "CRC-32/V-42", algo: crc, params: ["CRC-32/V-42"]}, {name: "CRC-32/XFER", algo: crc, params: ["CRC-32/XFER"]}, {name: "CRC-32/XZ", algo: crc, params: ["CRC-32/XZ"]}, {name: "Fletcher-32", algo: fletcher32, params: []}, {name: "CRC-40/GSM", algo: crc, params: ["CRC-40/GSM"]}, {name: "CRC-64/ECMA-182", algo: crc, params: ["CRC-64/ECMA-182"]}, {name: "CRC-64/GO-ECMA", algo: crc, params: ["CRC-64/GO-ECMA"]}, {name: "CRC-64/GO-ISO", algo: crc, params: ["CRC-64/GO-ISO"]}, {name: "CRC-64/MS", algo: crc, params: ["CRC-64/MS"]}, {name: "CRC-64/NVME", algo: crc, params: ["CRC-64/NVME"]}, {name: "CRC-64/REDIS", algo: crc, params: ["CRC-64/REDIS"]}, {name: "CRC-64/WE", algo: crc, params: ["CRC-64/WE"]}, {name: "CRC-64/XZ", algo: crc, params: ["CRC-64/XZ"]}, {name: "Fletcher-64", algo: fletcher64, params: []}, {name: "CRC-82/DARC", algo: crc, params: ["CRC-82/DARC"]} ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [length, includeNames] = args; let output = ""; this.checksums.forEach(checksum => { const checksumLength = checksum.name.match(new RegExp("-(\\d{1,2})(\\/|$)"))[1]; if (length === "All" || length === checksumLength) { const value = checksum.algo.run(new Uint8Array(input), checksum.params || []); output += includeNames ? `${checksum.name}:${" ".repeat(25-checksum.name.length)}${value}\n`: `${value}\n`; } }); return output; } } export default GenerateAllChecksums; ================================================ FILE: src/core/operations/GenerateAllHashes.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author john19696 [john19696@protonmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import MD2 from "./MD2.mjs"; import MD4 from "./MD4.mjs"; import MD5 from "./MD5.mjs"; import MD6 from "./MD6.mjs"; import SHA0 from "./SHA0.mjs"; import SHA1 from "./SHA1.mjs"; import SHA2 from "./SHA2.mjs"; import SHA3 from "./SHA3.mjs"; import Keccak from "./Keccak.mjs"; import Shake from "./Shake.mjs"; import RIPEMD from "./RIPEMD.mjs"; import HAS160 from "./HAS160.mjs"; import Whirlpool from "./Whirlpool.mjs"; import SSDEEP from "./SSDEEP.mjs"; import CTPH from "./CTPH.mjs"; import BLAKE2b from "./BLAKE2b.mjs"; import BLAKE2s from "./BLAKE2s.mjs"; import Streebog from "./Streebog.mjs"; import GOSTHash from "./GOSTHash.mjs"; import LMHash from "./LMHash.mjs"; import NTHash from "./NTHash.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Generate all hashes operation */ class GenerateAllHashes extends Operation { /** * GenerateAllHashes constructor */ constructor() { super(); this.name = "Generate all hashes"; this.module = "Crypto"; this.description = "Generates all available hashes and checksums for the input."; this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Length (bits)", type: "option", value: [ "All", "128", "160", "224", "256", "320", "384", "512" ] }, { name: "Include names", type: "boolean", value: true }, ]; this.hashes = [ {name: "MD2", algo: (new MD2()), inputType: "arrayBuffer", params: []}, {name: "MD4", algo: (new MD4()), inputType: "arrayBuffer", params: []}, {name: "MD5", algo: (new MD5()), inputType: "arrayBuffer", params: []}, {name: "MD6", algo: (new MD6()), inputType: "str", params: []}, {name: "SHA0", algo: (new SHA0()), inputType: "arrayBuffer", params: []}, {name: "SHA1", algo: (new SHA1()), inputType: "arrayBuffer", params: []}, {name: "SHA2 224", algo: (new SHA2()), inputType: "arrayBuffer", params: ["224"]}, {name: "SHA2 256", algo: (new SHA2()), inputType: "arrayBuffer", params: ["256"]}, {name: "SHA2 384", algo: (new SHA2()), inputType: "arrayBuffer", params: ["384"]}, {name: "SHA2 512", algo: (new SHA2()), inputType: "arrayBuffer", params: ["512"]}, {name: "SHA3 224", algo: (new SHA3()), inputType: "arrayBuffer", params: ["224"]}, {name: "SHA3 256", algo: (new SHA3()), inputType: "arrayBuffer", params: ["256"]}, {name: "SHA3 384", algo: (new SHA3()), inputType: "arrayBuffer", params: ["384"]}, {name: "SHA3 512", algo: (new SHA3()), inputType: "arrayBuffer", params: ["512"]}, {name: "Keccak 224", algo: (new Keccak()), inputType: "arrayBuffer", params: ["224"]}, {name: "Keccak 256", algo: (new Keccak()), inputType: "arrayBuffer", params: ["256"]}, {name: "Keccak 384", algo: (new Keccak()), inputType: "arrayBuffer", params: ["384"]}, {name: "Keccak 512", algo: (new Keccak()), inputType: "arrayBuffer", params: ["512"]}, {name: "Shake 128", algo: (new Shake()), inputType: "arrayBuffer", params: ["128", 256]}, {name: "Shake 256", algo: (new Shake()), inputType: "arrayBuffer", params: ["256", 512]}, {name: "RIPEMD-128", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["128"]}, {name: "RIPEMD-160", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["160"]}, {name: "RIPEMD-256", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["256"]}, {name: "RIPEMD-320", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["320"]}, {name: "HAS-160", algo: (new HAS160()), inputType: "arrayBuffer", params: []}, {name: "Whirlpool-0", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-0"]}, {name: "Whirlpool-T", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-T"]}, {name: "Whirlpool", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool"]}, {name: "BLAKE2b-128", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["128", "Hex", {string: "", option: "UTF8"}]}, {name: "BLAKE2b-160", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["160", "Hex", {string: "", option: "UTF8"}]}, {name: "BLAKE2b-256", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]}, {name: "BLAKE2b-384", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["384", "Hex", {string: "", option: "UTF8"}]}, {name: "BLAKE2b-512", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["512", "Hex", {string: "", option: "UTF8"}]}, {name: "BLAKE2s-128", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["128", "Hex", {string: "", option: "UTF8"}]}, {name: "BLAKE2s-160", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["160", "Hex", {string: "", option: "UTF8"}]}, {name: "BLAKE2s-256", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]}, {name: "Streebog-256", algo: (new Streebog), inputType: "arrayBuffer", params: ["256"]}, {name: "Streebog-512", algo: (new Streebog), inputType: "arrayBuffer", params: ["512"]}, {name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["GOST 28147 (1994)", "256", "D-A"]}, {name: "LM Hash", algo: (new LMHash), inputType: "str", params: []}, {name: "NT Hash", algo: (new NTHash), inputType: "str", params: []}, {name: "SSDEEP", algo: (new SSDEEP()), inputType: "str"}, {name: "CTPH", algo: (new CTPH()), inputType: "str"} ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [length, includeNames] = args; this.inputArrayBuffer = input; this.inputStr = Utils.arrayBufferToStr(input, false); this.inputByteArray = new Uint8Array(input); let digest, output = ""; // iterate over each of the hashes this.hashes.forEach(hash => { digest = this.executeAlgo(hash.algo, hash.inputType, hash.params || []); output += this.formatDigest(digest, length, includeNames, hash.name); }); return output; } /** * Executes a hash or checksum algorithm * * @param {Function} algo - The hash or checksum algorithm * @param {string} inputType * @param {Object[]} [params=[]] * @returns {string} */ executeAlgo(algo, inputType, params=[]) { let digest = null; switch (inputType) { case "arrayBuffer": digest = algo.run(this.inputArrayBuffer, params); break; case "str": digest = algo.run(this.inputStr, params); break; case "byteArray": digest = algo.run(this.inputByteArray, params); break; default: throw new OperationError("Unknown hash input type: " + inputType); } return digest; } /** * Formats the digest depending on user-specified arguments * @param {string} digest * @param {string} length * @param {boolean} includeNames * @param {string} name * @returns {string} */ formatDigest(digest, length, includeNames, name) { if (length !== "All" && (digest.length * 4) !== parseInt(length, 10)) return ""; if (!includeNames) return digest + "\n"; return `${name}:${" ".repeat(13-name.length)}${digest}\n`; } } export default GenerateAllHashes; ================================================ FILE: src/core/operations/GenerateDeBruijnSequence.mjs ================================================ /** * @author gchq77703 [gchq77703@gchq.gov.uk] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Generate De Bruijn Sequence operation */ class GenerateDeBruijnSequence extends Operation { /** * GenerateDeBruijnSequence constructor */ constructor() { super(); this.name = "Generate De Bruijn Sequence"; this.module = "Default"; this.description = "Generates rolling keycode combinations given a certain alphabet size and key length."; this.infoURL = "https://wikipedia.org/wiki/De_Bruijn_sequence"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Alphabet size (k)", type: "number", value: 2 }, { name: "Key length (n)", type: "number", value: 3 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [k, n] = args; if (k < 2 || k > 9) { throw new OperationError("Invalid alphabet size, required to be between 2 and 9 (inclusive)."); } if (n < 2) { throw new OperationError("Invalid key length, required to be at least 2."); } if (Math.pow(k, n) > 50000) { throw new OperationError("Too many permutations, please reduce k^n to under 50,000."); } const a = new Array(k * n).fill(0); const sequence = []; (function db(t = 1, p = 1) { if (t > n) { if (n % p !== 0) return; for (let j = 1; j <= p; j++) { sequence.push(a[j]); } return; } a[t] = a[t - p]; db(t + 1, p); for (let j = a[t - p] + 1; j < k; j++) { a[t] = j; db(t + 1, t); } })(); return sequence.join(""); } } export default GenerateDeBruijnSequence; ================================================ FILE: src/core/operations/GenerateECDSAKeyPair.mjs ================================================ /** * @author cplussharp * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { cryptNotice } from "../lib/Crypt.mjs"; import r from "jsrsasign"; /** * Generate ECDSA Key Pair operation */ class GenerateECDSAKeyPair extends Operation { /** * GenerateECDSAKeyPair constructor */ constructor() { super(); this.name = "Generate ECDSA Key Pair"; this.module = "Ciphers"; this.description = `Generate an ECDSA key pair with a given Curve.

${cryptNotice}`; this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Elliptic Curve", type: "option", value: [ "P-256", "P-384", "P-521" ] }, { name: "Output Format", type: "option", value: [ "PEM", "DER", "JWK" ] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [curveName, outputFormat] = args; return new Promise((resolve, reject) => { let internalCurveName; switch (curveName) { case "P-256": internalCurveName = "secp256r1"; break; case "P-384": internalCurveName = "secp384r1"; break; case "P-521": internalCurveName = "secp521r1"; break; } const keyPair = r.KEYUTIL.generateKeypair("EC", internalCurveName); let pubKey; let privKey; let result; switch (outputFormat) { case "PEM": pubKey = r.KEYUTIL.getPEM(keyPair.pubKeyObj).replace(/\r/g, ""); privKey = r.KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV").replace(/\r/g, ""); result = pubKey + "\n" + privKey; break; case "DER": result = keyPair.prvKeyObj.prvKeyHex; break; case "JWK": pubKey = r.KEYUTIL.getJWKFromKey(keyPair.pubKeyObj); pubKey.key_ops = ["verify"]; // eslint-disable-line camelcase pubKey.kid = "PublicKey"; privKey = r.KEYUTIL.getJWKFromKey(keyPair.prvKeyObj); privKey.key_ops = ["sign"]; // eslint-disable-line camelcase privKey.kid = "PrivateKey"; result = JSON.stringify({keys: [privKey, pubKey]}, null, 4); break; } resolve(result); }); } } export default GenerateECDSAKeyPair; ================================================ FILE: src/core/operations/GenerateHOTP.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import * as OTPAuth from "otpauth"; /** * Generate HOTP operation */ class GenerateHOTP extends Operation { /** * */ constructor() { super(); this.name = "Generate HOTP"; this.module = "Default"; 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.

Enter the secret as the input or leave it blank for a random secret to be generated."; this.infoURL = "https://wikipedia.org/wiki/HMAC-based_One-time_Password_algorithm"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Name", "type": "string", "value": "" }, { "name": "Code length", "type": "number", "value": 6 }, { "name": "Counter", "type": "number", "value": 0 } ]; } /** * */ run(input, args) { const secretStr = new TextDecoder("utf-8").decode(input).trim(); const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : ""; const hotp = new OTPAuth.HOTP({ issuer: "", label: args[0], algorithm: "SHA1", digits: args[1], counter: args[2], secret: OTPAuth.Secret.fromBase32(secret) }); const uri = hotp.toString(); const code = hotp.generate(); return `URI: ${uri}\n\nPassword: ${code}`; } } export default GenerateHOTP; ================================================ FILE: src/core/operations/GenerateImage.mjs ================================================ /** * @author pointhi [thomas.pointhuber@gmx.at] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime, ResizeStrategy, rgbaToInt } from "jimp"; /** * Generate Image operation */ class GenerateImage extends Operation { /** * GenerateImage constructor */ constructor() { super(); this.name = "Generate Image"; this.module = "Image"; this.description = "Generates an image using the input as pixel values."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Mode", type: "option", value: ["Greyscale", "RG", "RGB", "RGBA", "Bits"], }, { name: "Pixel Scale Factor", type: "number", value: 8, }, { name: "Pixels per row", type: "number", value: 64, }, ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {ArrayBuffer} */ async run(input, args) { const [mode, scale, width] = args; input = new Uint8Array(input); if (scale <= 0) { throw new OperationError("Pixel Scale Factor needs to be > 0"); } if (width <= 0) { throw new OperationError("Pixels per Row needs to be > 0"); } const bytePerPixelMap = { Greyscale: 1, RG: 2, RGB: 3, RGBA: 4, Bits: 1 / 8, }; const bytesPerPixel = bytePerPixelMap[mode]; if (bytesPerPixel > 0 && input.length % bytesPerPixel !== 0) { throw new OperationError( `Number of bytes is not a divisor of ${bytesPerPixel}`, ); } const height = Math.ceil(input.length / bytesPerPixel / width); const image = new Jimp({ width, height }); if (isWorkerEnvironment()) self.sendStatusMessage("Generating image from data..."); if (mode === "Bits") { let index = 0; for (let j = 0; j < input.length; j++) { const curByte = Utils.bin(input[j]); for (let k = 0; k < 8; k++, index++) { const x = index % width; const y = Math.floor(index / width); const value = curByte[k] === "0" ? 0xff : 0x00; const pixel = rgbaToInt(value, value, value, 0xff); image.setPixelColor(pixel, x, y); } } } else { let i = 0; while (i < input.length) { const index = i / bytesPerPixel; const x = index % width; const y = Math.floor(index / width); let red = 0x00; let green = 0x00; let blue = 0x00; let alpha = 0xff; switch (mode) { case "Greyscale": red = green = blue = input[i++]; break; case "RG": red = input[i++]; green = input[i++]; break; case "RGB": red = input[i++]; green = input[i++]; blue = input[i++]; break; case "RGBA": red = input[i++]; green = input[i++]; blue = input[i++]; alpha = input[i++]; break; default: throw new OperationError(`Unsupported Mode: (${mode})`); } try { const pixel = rgbaToInt(red, green, blue, alpha); image.setPixelColor(pixel, x, y); } catch (err) { throw new OperationError( `Error while generating image from pixel values. (${err})`, ); } } } if (scale !== 1) { if (isWorkerEnvironment()) self.sendStatusMessage("Scaling image..."); image.scaleToFit({ w: width * scale, h: height * scale, mode: ResizeStrategy.NEAREST_NEIGHBOR, }); } try { const imageBuffer = await image.getBuffer(JimpMime.png); return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error generating image. (${err})`); } } /** * Displays the generated image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default GenerateImage; ================================================ FILE: src/core/operations/GenerateLoremIpsum.mjs ================================================ /** * @author klaxon [klaxon@veyr.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum.mjs"; /** * Generate Lorem Ipsum operation */ class GenerateLoremIpsum extends Operation { /** * GenerateLoremIpsum constructor */ constructor() { super(); this.name = "Generate Lorem Ipsum"; this.module = "Default"; this.description = "Generate varying length lorem ipsum placeholder text."; this.infoURL = "https://wikipedia.org/wiki/Lorem_ipsum"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Length", "type": "number", "value": "3" }, { "name": "Length in", "type": "option", "value": ["Paragraphs", "Sentences", "Words", "Bytes"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [length, lengthType] = args; if (length < 1) { throw new OperationError("Length must be greater than 0"); } switch (lengthType) { case "Paragraphs": return GenerateParagraphs(length); case "Sentences": return GenerateSentences(length); case "Words": return GenerateWords(length); case "Bytes": return GenerateBytes(length); default: throw new OperationError("Invalid length type"); } } } export default GenerateLoremIpsum; ================================================ FILE: src/core/operations/GeneratePGPKeyPair.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @author Matt C [matt@artemisbot.uk] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import kbpgp from "kbpgp"; import { getSubkeySize, ASP } from "../lib/PGP.mjs"; import { cryptNotice } from "../lib/Crypt.mjs"; import * as es6promisify from "es6-promisify"; const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; /** * Generate PGP Key Pair operation */ class GeneratePGPKeyPair extends Operation { /** * GeneratePGPKeyPair constructor */ constructor() { super(); this.name = "Generate PGP Key Pair"; this.module = "PGP"; this.description = `Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.

${cryptNotice}`; this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key type", "type": "option", "value": ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384", "ECC-521"] }, { "name": "Password (optional)", "type": "string", "value": "" }, { "name": "Name (optional)", "type": "string", "value": "" }, { "name": "Email (optional)", "type": "string", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { let [keyType, keySize] = args[0].split("-"); const password = args[1], name = args[2], email = args[3]; let userIdentifier = ""; keyType = keyType.toLowerCase(); keySize = parseInt(keySize, 10); if (name) userIdentifier += name; if (email) userIdentifier += ` <${email}>`; let flags = kbpgp.const.openpgp.certify_keys; flags |= kbpgp.const.openpgp.sign_data; flags |= kbpgp.const.openpgp.auth; flags |= kbpgp.const.openpgp.encrypt_comm; flags |= kbpgp.const.openpgp.encrypt_storage; const keyGenerationOptions = { userid: userIdentifier, ecc: keyType === "ecc", primary: { "nbits": keySize, "flags": flags, "expire_in": 0 }, subkeys: [{ "nbits": getSubkeySize(keySize), "flags": kbpgp.const.openpgp.sign_data, "expire_in": 86400 * 365 * 8 }, { "nbits": getSubkeySize(keySize), "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, "expire_in": 86400 * 365 * 2 }], asp: ASP }; return new Promise(async (resolve, reject) => { try { const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions); await promisify(unsignedKey.sign.bind(unsignedKey))({}); const signedKey = unsignedKey, privateKeyExportOptions = {}; if (password) privateKeyExportOptions.passphrase = password; const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions); const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({}); resolve(privateKey + "\n" + publicKey.trim()); } catch (err) { reject(`Error whilst generating key pair: ${err}`); } }); } } export default GeneratePGPKeyPair; ================================================ FILE: src/core/operations/GenerateQRCode.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { generateQrCode } from "../lib/QRCode.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isImage } from "../lib/FileType.mjs"; import Utils from "../Utils.mjs"; /** * Generate QR Code operation */ class GenerateQRCode extends Operation { /** * GenerateQRCode constructor */ constructor() { super(); this.name = "Generate QR Code"; this.module = "Image"; this.description = "Generates a Quick Response (QR) code from the input text.

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."; this.infoURL = "https://wikipedia.org/wiki/QR_code"; this.inputType = "string"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { "name": "Image Format", "type": "option", "value": ["PNG", "SVG", "EPS", "PDF"] }, { "name": "Module size (px)", "type": "number", "value": 5, "min": 1 }, { "name": "Margin (num modules)", "type": "number", "value": 4, "min": 0 }, { "name": "Error correction", "type": "option", "value": ["Low", "Medium", "Quartile", "High"], "defaultIndex": 1 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const [format, size, margin, errorCorrection] = args; return generateQrCode(input, format, size, margin, errorCorrection); } /** * Displays the QR image using HTML for web apps * * @param {ArrayBuffer} data * @returns {html} */ present(data, args) { if (!data.byteLength && !data.length) return ""; const dataArray = new Uint8Array(data), [format] = args; if (format === "PNG") { const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } return Utils.arrayBufferToStr(data); } } export default GenerateQRCode; ================================================ FILE: src/core/operations/GenerateRSAKeyPair.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @author gchq77703 [] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import forge from "node-forge"; import { cryptNotice } from "../lib/Crypt.mjs"; /** * Generate RSA Key Pair operation */ class GenerateRSAKeyPair extends Operation { /** * GenerateRSAKeyPair constructor */ constructor() { super(); this.name = "Generate RSA Key Pair"; this.module = "Ciphers"; this.description = `Generate an RSA key pair with a given number of bits.

${cryptNotice}`; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "RSA Key Length", type: "option", value: [ "1024", "2048", "4096" ] }, { name: "Output Format", type: "option", value: [ "PEM", "JSON", "DER" ] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [keyLength, outputFormat] = args; return new Promise((resolve, reject) => { forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: -1, workerScript: "assets/forge/prime.worker.min.js" }, (err, keypair) => { if (err) return reject(err); let result; switch (outputFormat) { case "PEM": result = forge.pki.publicKeyToPem(keypair.publicKey) + "\n" + forge.pki.privateKeyToPem(keypair.privateKey); break; case "JSON": result = JSON.stringify(keypair); break; case "DER": result = forge.asn1.toDer(forge.pki.privateKeyToAsn1(keypair.privateKey)).getBytes(); break; } resolve(result); }); }); } } export default GenerateRSAKeyPair; ================================================ FILE: src/core/operations/GenerateTOTP.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import * as OTPAuth from "otpauth"; /** * Generate TOTP operation */ class GenerateTOTP extends Operation { /** * */ constructor() { super(); this.name = "Generate TOTP"; this.module = "Default"; 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.

Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds."; this.infoURL = "https://wikipedia.org/wiki/Time-based_One-time_Password_algorithm"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Name", "type": "string", "value": "" }, { "name": "Code length", "type": "number", "value": 6 }, { "name": "Epoch offset (T0)", "type": "number", "value": 0 }, { "name": "Interval (T1)", "type": "number", "value": 30 } ]; } /** * */ run(input, args) { const secretStr = new TextDecoder("utf-8").decode(input).trim(); const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : ""; const totp = new OTPAuth.TOTP({ issuer: "", label: args[0], algorithm: "SHA1", digits: args[1], period: args[3], epoch: args[2] * 1000, // Convert seconds to milliseconds secret: OTPAuth.Secret.fromBase32(secret) }); const uri = totp.toString(); const code = totp.generate(); return `URI: ${uri}\n\nPassword: ${code}`; } } export default GenerateTOTP; ================================================ FILE: src/core/operations/GenerateUUID.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import * as uuid from "uuid"; import OperationError from "../errors/OperationError.mjs"; /** * Generate UUID operation */ class GenerateUUID extends Operation { /** * GenerateUUID constructor */ constructor() { super(); this.name = "Generate UUID"; this.module = "Crypto"; this.description = "Generates an RFC 9562 (formerly RFC 4122) compliant Universally Unique Identifier (UUID), " + "also known as a Globally Unique Identifier (GUID).
" + "
" + "We currently support generating the following UUID versions:
" + "
    " + "
  • v1: Timestamp-based
  • " + "
  • v3: Namespace w/ MD5
  • " + "
  • v4: Random (default)
  • " + "
  • v5: Namespace w/ SHA-1
  • " + "
  • v6: Timestamp, reordered
  • " + "
  • v7: Unix Epoch time-based
  • " + "
" + "UUIDs are generated using the
uuid package.
"; this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Version", hint: "UUID version", type: "option", value: ["v1", "v3", "v4", "v5", "v6", "v7"], defaultIndex: 2, }, { name: "Namespace", hint: "UUID namespace (UUID; valid for v3 and v5)", type: "string", value: "1b671a64-40d5-491e-99b0-da01ff1f3341" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [version, namespace] = args; const hasDesiredVersion = typeof uuid[version] === "function"; if (!hasDesiredVersion) throw new OperationError("Invalid UUID version"); const requiresNamespace = ["v3", "v5"].includes(version); if (!requiresNamespace) return uuid[version](); const hasValidNamespace = typeof namespace === "string" && uuid.validate(namespace); if (!hasValidNamespace) throw new OperationError("Invalid UUID namespace"); return uuid[version](input, namespace); } } export default GenerateUUID; ================================================ FILE: src/core/operations/GenericCodeBeautify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Generic Code Beautify operation */ class GenericCodeBeautify extends Operation { /** * GenericCodeBeautify constructor */ constructor() { super(); this.name = "Generic Code Beautify"; this.module = "Code"; this.description = "Attempts to pretty print C-style languages such as C, C++, C#, Java, PHP, JavaScript etc.

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.

Things which will not work properly:
  • For loop formatting
  • Do-While loop formatting
  • Switch/Case indentation
  • Certain bit shift operators
"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const preservedTokens = []; let code = input, t = 0, m; // Remove strings const sstrings = /'([^'\\]|\\.)*'/g; while ((m = sstrings.exec(code))) { code = preserveToken(code, m, t++); sstrings.lastIndex = m.index; } const dstrings = /"([^"\\]|\\.)*"/g; while ((m = dstrings.exec(code))) { code = preserveToken(code, m, t++); dstrings.lastIndex = m.index; } // Remove comments const scomments = /\/\/[^\n\r]*/g; while ((m = scomments.exec(code))) { code = preserveToken(code, m, t++); scomments.lastIndex = m.index; } const mcomments = /\/\*[\s\S]*?\*\//gm; while ((m = mcomments.exec(code))) { code = preserveToken(code, m, t++); mcomments.lastIndex = m.index; } const hcomments = /(^|\n)#[^\n\r#]+/g; while ((m = hcomments.exec(code))) { code = preserveToken(code, m, t++); hcomments.lastIndex = m.index; } // Remove regexes const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi; while ((m = regexes.exec(code))) { code = preserveToken(code, m, t++); regexes.lastIndex = m.index; } code = code // Create newlines after ; .replace(/;/g, ";\n") // Create newlines after { and around } .replace(/{/g, "{\n") .replace(/}/g, "\n}\n") // Remove carriage returns .replace(/\r/g, "") // Remove all indentation .replace(/^\s+/g, "") .replace(/\n\s+/g, "\n") // Remove trailing spaces .replace(/\s*$/g, "") .replace(/\n{/g, "{"); // Indent let i = 0, level = 0, indent; while (i < code.length) { switch (code[i]) { case "{": level++; break; case "\n": if (i+1 >= code.length) break; if (code[i+1] === "}") level--; indent = (level >= 0) ? Array(level*4+1).join(" ") : ""; code = code.substring(0, i+1) + indent + code.substring(i+1); if (level > 0) i += level*4; break; } i++; } code = code // Add strategic spaces .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ") .replace(/\s*<([=]?)\s*/g, " <$1 ") .replace(/\s*>([=]?)\s*/g, " >$1 ") .replace(/([^+])\+([^+=])/g, "$1 + $2") .replace(/([^-])-([^-=])/g, "$1 - $2") .replace(/([^*])\*([^*=])/g, "$1 * $2") .replace(/([^/])\/([^/=])/g, "$1 / $2") .replace(/\s*,\s*/g, ", ") .replace(/\s*{/g, " {") .replace(/}\n/g, "}\n\n") // Hacky horribleness .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3") .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3") .replace(/else\s*\n([^{])/gim, "else\n $1") .replace(/else\s+([^{])/gim, "else $1") // Remove strategic spaces .replace(/\s+;/g, ";") .replace(/\{\s+\}/g, "{}") .replace(/\[\s+\]/g, "[]") .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1"); // Replace preserved tokens const ptokens = /###preservedToken(\d+)###/g; while ((m = ptokens.exec(code))) { const ti = parseInt(m[1], 10); code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length); ptokens.lastIndex = m.index; } return code; /** * Replaces a matched token with a placeholder value. */ function preserveToken(str, match, t) { preservedTokens[t] = match[0]; return str.substring(0, match.index) + "###preservedToken" + t + "###" + str.substring(match.index + match[0].length); } } } export default GenericCodeBeautify; ================================================ FILE: src/core/operations/GetAllCasings.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Permutate String operation */ class GetAllCasings extends Operation { /** * GetAllCasings constructor */ constructor() { super(); this.name = "Get All Casings"; this.module = "Default"; this.description = "Outputs all possible casing variations of a string."; this.infoURL = ""; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const length = input.length; const max = 1 << length; input = input.toLowerCase(); let result = ""; for (let i = 0; i < max; i++) { const temp = input.split(""); for (let j = 0; j < length; j++) { if (((i >> j) & 1) === 1) { temp[j] = temp[j].toUpperCase(); } } result += temp.join("") + "\n"; } return result.slice(0, -1); } } export default GetAllCasings; ================================================ FILE: src/core/operations/GetTime.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import {UNITS} from "../lib/DateTime.mjs"; /** * Get Time operation */ class GetTime extends Operation { /** * GetTime constructor */ constructor() { super(); this.name = "Get Time"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Unix_time"; this.inputType = "string"; this.outputType = "number"; this.args = [ { name: "Granularity", type: "option", value: UNITS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {number} */ run(input, args) { const nowMs = (performance.timeOrigin + performance.now()), granularity = args[0]; switch (granularity) { case "Nanoseconds (ns)": return Math.round(nowMs * 1000 * 1000); case "Microseconds (μs)": return Math.round(nowMs * 1000); case "Milliseconds (ms)": return Math.round(nowMs); case "Seconds (s)": return Math.round(nowMs / 1000); default: throw new OperationError("Unknown granularity value: " + granularity); } } } export default GetTime; ================================================ FILE: src/core/operations/GroupIPAddresses.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import {IP_DELIM_OPTIONS} from "../lib/Delim.mjs"; import {ipv6ToStr, genIpv6Mask, IPV4_REGEX, strToIpv6, ipv4ToStr, IPV6_REGEX, strToIpv4} from "../lib/IP.mjs"; /** * Group IP addresses operation */ class GroupIPAddresses extends Operation { /** * GroupIPAddresses constructor */ constructor() { super(); this.name = "Group IP addresses"; this.module = "Default"; this.description = "Groups a list of IP addresses into subnets. Supports both IPv4 and IPv6 addresses."; this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": IP_DELIM_OPTIONS }, { "name": "Subnet (CIDR)", "type": "number", "value": 24 }, { "name": "Only show the subnets", "type": "boolean", "value": false, } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = Utils.charRep(args[0]), cidr = args[1], onlySubnets = args[2], ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF, ipv6Mask = genIpv6Mask(cidr), ips = input.split(delim), ipv4Networks = {}, ipv6Networks = {}; let match = null, output = "", ip = null, network = null, networkStr = "", i; if (cidr < 0 || cidr > 127) { throw new OperationError("CIDR must be less than 32 for IPv4 or 128 for IPv6"); } // Parse all IPs and add to network dictionary for (i = 0; i < ips.length; i++) { if ((match = IPV4_REGEX.exec(ips[i]))) { ip = strToIpv4(match[1]) >>> 0; network = ip & ipv4Mask; if (network in ipv4Networks) { ipv4Networks[network].push(ip); } else { ipv4Networks[network] = [ip]; } } else if ((match = IPV6_REGEX.exec(ips[i]))) { ip = strToIpv6(match[1]); network = []; networkStr = ""; for (let j = 0; j < 8; j++) { network.push(ip[j] & ipv6Mask[j]); } networkStr = ipv6ToStr(network, true); if (networkStr in ipv6Networks) { ipv6Networks[networkStr].push(ip); } else { ipv6Networks[networkStr] = [ip]; } } } // Sort IPv4 network dictionaries and print for (network in ipv4Networks) { ipv4Networks[network] = ipv4Networks[network].sort(); output += ipv4ToStr(network) + "/" + cidr + "\n"; if (!onlySubnets) { for (i = 0; i < ipv4Networks[network].length; i++) { output += " " + ipv4ToStr(ipv4Networks[network][i]) + "\n"; } output += "\n"; } } // Sort IPv6 network dictionaries and print for (networkStr in ipv6Networks) { // ipv6Networks[networkStr] = ipv6Networks[networkStr].sort(); TODO output += networkStr + "/" + cidr + "\n"; if (!onlySubnets) { for (i = 0; i < ipv6Networks[networkStr].length; i++) { output += " " + ipv6ToStr(ipv6Networks[networkStr][i], true) + "\n"; } output += "\n"; } } return output; } } export default GroupIPAddresses; ================================================ FILE: src/core/operations/Gunzip.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import gunzip from "zlibjs/bin/gunzip.min.js"; const Zlib = gunzip.Zlib; /** * Gunzip operation */ class Gunzip extends Operation { /** * Gunzip constructor */ constructor() { super(); this.name = "Gunzip"; this.module = "Compression"; this.description = "Decompresses data which has been compressed using the deflate algorithm with gzip headers."; this.infoURL = "https://wikipedia.org/wiki/Gzip"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = []; this.checks = [ { pattern: "^\\x1f\\x8b\\x08", flags: "", args: [] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {File} */ run(input, args) { const gzipObj = new Zlib.Gunzip(new Uint8Array(input)); return new Uint8Array(gzipObj.decompress()).buffer; } } export default Gunzip; ================================================ FILE: src/core/operations/Gzip.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; import gzip from "zlibjs/bin/gzip.min.js"; const Zlib = gzip.Zlib; /** * Gzip operation */ class Gzip extends Operation { /** * Gzip constructor */ constructor() { super(); this.name = "Gzip"; this.module = "Compression"; this.description = "Compresses data using the deflate algorithm with gzip headers."; this.infoURL = "https://wikipedia.org/wiki/Gzip"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { name: "Compression type", type: "option", value: COMPRESSION_TYPE }, { name: "Filename (optional)", type: "string", value: "" }, { name: "Comment (optional)", type: "string", value: "" }, { name: "Include file checksum", type: "boolean", value: false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const filename = args[1], comment = args[2], options = { deflateOptions: { compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] }, flags: { fhcrc: args[3] } }; if (filename.length) { options.flags.fname = true; options.filename = filename; } if (comment.length) { options.flags.comment = true; options.comment = comment; } const gzipObj = new Zlib.Gzip(new Uint8Array(input), options); const compressed = new Uint8Array(gzipObj.compress()); if (options.flags.comment && !(compressed[3] & 0x10)) { compressed[3] |= 0x10; } return compressed.buffer; } } export default Gzip; ================================================ FILE: src/core/operations/HAS160.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * HAS-160 operation */ class HAS160 extends Operation { /** * HAS-160 constructor */ constructor() { super(); this.name = "HAS-160"; this.module = "Crypto"; 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.

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.

The message digest algorithm consists, by default, of 80 rounds."; this.infoURL = "https://wikipedia.org/wiki/HAS-160"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Rounds", type: "number", value: 80, min: 1, max: 80 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { return runHash("has160", input, {rounds: args[0]}); } } export default HAS160; ================================================ FILE: src/core/operations/HASSHClientFingerprint.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 * * HASSH created by Salesforce * Ben Reardon (@benreardon) * Adel Karimi (@0x4d31) * and the JA3 crew: * John B. Althouse * Jeff Atkinson * Josh Atkins * * Algorithm released under the BSD-3-clause licence */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import Stream from "../lib/Stream.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * HASSH Client Fingerprint operation */ class HASSHClientFingerprint extends Operation { /** * HASSHClientFingerprint constructor */ constructor() { super(); this.name = "HASSH Client Fingerprint"; this.module = "Crypto"; this.description = "Generates a HASSH fingerprint to help identify SSH clients based on hashing together values from the Client Key Exchange Init message.

Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Client to Server."; this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Input format", type: "option", value: ["Hex", "Base64", "Raw"] }, { name: "Output format", type: "option", value: ["Hash digest", "HASSH algorithms string", "Full details"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [inputFormat, outputFormat] = args; input = Utils.convertToByteArray(input, inputFormat); const s = new Stream(new Uint8Array(input)); // Length const length = s.readInt(4); if (s.length !== length + 4) throw new OperationError("Incorrect packet length."); // Padding length const paddingLength = s.readInt(1); // Message code const messageCode = s.readInt(1); if (messageCode !== 20) throw new OperationError("Not a Key Exchange Init."); // Cookie s.moveForwardsBy(16); // KEX Algorithms const kexAlgosLength = s.readInt(4); const kexAlgos = s.readString(kexAlgosLength); // Server Host Key Algorithms const serverHostKeyAlgosLength = s.readInt(4); s.moveForwardsBy(serverHostKeyAlgosLength); // Encryption Algorithms Client to Server const encAlgosC2SLength = s.readInt(4); const encAlgosC2S = s.readString(encAlgosC2SLength); // Encryption Algorithms Server to Client const encAlgosS2CLength = s.readInt(4); s.moveForwardsBy(encAlgosS2CLength); // MAC Algorithms Client to Server const macAlgosC2SLength = s.readInt(4); const macAlgosC2S = s.readString(macAlgosC2SLength); // MAC Algorithms Server to Client const macAlgosS2CLength = s.readInt(4); s.moveForwardsBy(macAlgosS2CLength); // Compression Algorithms Client to Server const compAlgosC2SLength = s.readInt(4); const compAlgosC2S = s.readString(compAlgosC2SLength); // Compression Algorithms Server to Client const compAlgosS2CLength = s.readInt(4); s.moveForwardsBy(compAlgosS2CLength); // Languages Client to Server const langsC2SLength = s.readInt(4); s.moveForwardsBy(langsC2SLength); // Languages Server to Client const langsS2CLength = s.readInt(4); s.moveForwardsBy(langsS2CLength); // First KEX packet follows s.moveForwardsBy(1); // Reserved s.moveForwardsBy(4); // Padding string s.moveForwardsBy(paddingLength); // Output const hassh = [ kexAlgos, encAlgosC2S, macAlgosC2S, compAlgosC2S ]; const hasshStr = hassh.join(";"); const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr)); switch (outputFormat) { case "HASSH algorithms string": return hasshStr; case "Full details": return `Hash digest: ${hasshHash} Full HASSH algorithms string: ${hasshStr} Key Exchange Algorithms: ${kexAlgos} Encryption Algorithms Client to Server: ${encAlgosC2S} MAC Algorithms Client to Server: ${macAlgosC2S} Compression Algorithms Client to Server: ${compAlgosC2S}`; case "Hash digest": default: return hasshHash; } } } export default HASSHClientFingerprint; ================================================ FILE: src/core/operations/HASSHServerFingerprint.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 * * HASSH created by Salesforce * Ben Reardon (@benreardon) * Adel Karimi (@0x4d31) * and the JA3 crew: * John B. Althouse * Jeff Atkinson * Josh Atkins * * Algorithm released under the BSD-3-clause licence */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import Stream from "../lib/Stream.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * HASSH Server Fingerprint operation */ class HASSHServerFingerprint extends Operation { /** * HASSHServerFingerprint constructor */ constructor() { super(); this.name = "HASSH Server Fingerprint"; this.module = "Crypto"; this.description = "Generates a HASSH fingerprint to help identify SSH servers based on hashing together values from the Server Key Exchange Init message.

Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Server to Client."; this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Input format", type: "option", value: ["Hex", "Base64", "Raw"] }, { name: "Output format", type: "option", value: ["Hash digest", "HASSH algorithms string", "Full details"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [inputFormat, outputFormat] = args; input = Utils.convertToByteArray(input, inputFormat); const s = new Stream(new Uint8Array(input)); // Length const length = s.readInt(4); if (s.length !== length + 4) throw new OperationError("Incorrect packet length."); // Padding length const paddingLength = s.readInt(1); // Message code const messageCode = s.readInt(1); if (messageCode !== 20) throw new OperationError("Not a Key Exchange Init."); // Cookie s.moveForwardsBy(16); // KEX Algorithms const kexAlgosLength = s.readInt(4); const kexAlgos = s.readString(kexAlgosLength); // Server Host Key Algorithms const serverHostKeyAlgosLength = s.readInt(4); s.moveForwardsBy(serverHostKeyAlgosLength); // Encryption Algorithms Client to Server const encAlgosC2SLength = s.readInt(4); s.moveForwardsBy(encAlgosC2SLength); // Encryption Algorithms Server to Client const encAlgosS2CLength = s.readInt(4); const encAlgosS2C = s.readString(encAlgosS2CLength); // MAC Algorithms Client to Server const macAlgosC2SLength = s.readInt(4); s.moveForwardsBy(macAlgosC2SLength); // MAC Algorithms Server to Client const macAlgosS2CLength = s.readInt(4); const macAlgosS2C = s.readString(macAlgosS2CLength); // Compression Algorithms Client to Server const compAlgosC2SLength = s.readInt(4); s.moveForwardsBy(compAlgosC2SLength); // Compression Algorithms Server to Client const compAlgosS2CLength = s.readInt(4); const compAlgosS2C = s.readString(compAlgosS2CLength); // Languages Client to Server const langsC2SLength = s.readInt(4); s.moveForwardsBy(langsC2SLength); // Languages Server to Client const langsS2CLength = s.readInt(4); s.moveForwardsBy(langsS2CLength); // First KEX packet follows s.moveForwardsBy(1); // Reserved s.moveForwardsBy(4); // Padding string s.moveForwardsBy(paddingLength); // Output const hassh = [ kexAlgos, encAlgosS2C, macAlgosS2C, compAlgosS2C ]; const hasshStr = hassh.join(";"); const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr)); switch (outputFormat) { case "HASSH algorithms string": return hasshStr; case "Full details": return `Hash digest: ${hasshHash} Full HASSH algorithms string: ${hasshStr} Key Exchange Algorithms: ${kexAlgos} Encryption Algorithms Server to Client: ${encAlgosS2C} MAC Algorithms Server to Client: ${macAlgosS2C} Compression Algorithms Server to Client: ${compAlgosS2C}`; case "Hash digest": default: return hasshHash; } } } export default HASSHServerFingerprint; ================================================ FILE: src/core/operations/HMAC.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import CryptoApi from "crypto-api/src/crypto-api.mjs"; /** * HMAC operation */ class HMAC extends Operation { /** * HMAC constructor */ constructor() { super(); this.name = "HMAC"; this.module = "Crypto"; this.description = "Keyed-Hash Message Authentication Codes (HMAC) are a mechanism for message authentication using cryptographic hash functions."; this.infoURL = "https://wikipedia.org/wiki/HMAC"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"] }, { "name": "Hashing function", "type": "option", "value": [ "MD2", "MD4", "MD5", "SHA0", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA512/224", "SHA512/256", "RIPEMD128", "RIPEMD160", "RIPEMD256", "RIPEMD320", "HAS160", "Whirlpool", "Whirlpool-0", "Whirlpool-T", "Snefru" ] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteString(args[0].string || "", args[0].option), hashFunc = args[1].toLowerCase(), msg = Utils.arrayBufferToStr(input, false), hasher = CryptoApi.getHasher(hashFunc); const mac = CryptoApi.getHmac(key, hasher); mac.update(msg); return CryptoApi.encoder.toHex(mac.finalize()); } } export default HMAC; ================================================ FILE: src/core/operations/HTMLToText.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * HTML To Text operation */ class HTMLToText extends Operation { /** * HTMLToText constructor */ constructor() { super(); this.name = "HTML To Text"; this.module = "Default"; this.description = "Converts an HTML output from an operation to a readable string instead of being rendered in the DOM."; this.infoURL = ""; this.inputType = "html"; this.outputType = "string"; this.args = []; } /** * @param {html} input * @param {Object[]} args * @returns {string} */ run(input, args) { return input; } } export default HTMLToText; ================================================ FILE: src/core/operations/HTTPRequest.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * HTTP request operation */ class HTTPRequest extends Operation { /** * HTTPRequest constructor */ constructor() { super(); this.name = "HTTP request"; this.module = "Default"; this.description = [ "Makes an HTTP request and returns the response.", "

", "This operation supports different HTTP verbs like GET, POST, PUT, etc.", "

", "You can add headers line by line in the format Key: Value", "

", "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.", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields"; this.inputType = "string"; this.outputType = "string"; this.manualBake = true; this.args = [ { "name": "Method", "type": "option", "value": [ "GET", "POST", "HEAD", "PUT", "PATCH", "DELETE", "CONNECT", "TRACE", "OPTIONS" ] }, { "name": "URL", "type": "string", "value": "" }, { "name": "Headers", "type": "text", "value": "" }, { "name": "Mode", "type": "option", "value": [ "Cross-Origin Resource Sharing", "No CORS (limited to HEAD, GET or POST)", ] }, { "name": "Show response metadata", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [method, url, headersText, mode, showResponseMetadata] = args; if (url.length === 0) return ""; const headers = new Headers(); headersText.split(/\r?\n/).forEach(line => { line = line.trim(); if (line.length === 0) return; const split = line.split(":"); if (split.length !== 2) throw `Could not parse header in line: ${line}`; headers.set(split[0].trim(), split[1].trim()); }); const config = { method: method, headers: headers, mode: modeLookup[mode], cache: "no-cache", }; if (method !== "GET" && method !== "HEAD") { config.body = input; } return fetch(url, config) .then(r => { if (r.status === 0 && r.type === "opaque") { throw new OperationError("Error: Null response. Try setting the connection mode to CORS."); } if (showResponseMetadata) { let headers = ""; for (const pair of r.headers.entries()) { headers += " " + pair[0] + ": " + pair[1] + "\n"; } return r.text().then(b => { return "####\n Status: " + r.status + " " + r.statusText + "\n Exposed headers:\n" + headers + "####\n\n" + b; }); } return r.text(); }) .catch(e => { throw new OperationError(e.toString() + "\n\nThis error could be caused by one of the following:\n" + " - An invalid URL\n" + " - Making a request to an insecure resource (HTTP) from a secure source (HTTPS)\n" + " - Making a cross-origin request to a server which does not support CORS\n"); }); } } /** * Lookup table for HTTP modes * * @private */ const modeLookup = { "Cross-Origin Resource Sharing": "cors", "No CORS (limited to HEAD, GET or POST)": "no-cors", }; export default HTTPRequest; ================================================ FILE: src/core/operations/HammingDistance.mjs ================================================ /** * @author GCHQ Contributor [2] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {fromHex} from "../lib/Hex.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Hamming Distance operation */ class HammingDistance extends Operation { /** * HammingDistance constructor */ constructor() { super(); this.name = "Hamming Distance"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Hamming_distance"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "binaryShortString", "value": "\\n\\n" }, { "name": "Unit", "type": "option", "value": ["Byte", "Bit"] }, { "name": "Input type", "type": "option", "value": ["Raw string", "Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = args[0], byByte = args[1] === "Byte", inputType = args[2], samples = input.split(delim); if (samples.length !== 2) { 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."); } if (samples[0].length !== samples[1].length) { throw new OperationError("Error: Both inputs must be of the same length."); } if (inputType === "Hex") { samples[0] = fromHex(samples[0]); samples[1] = fromHex(samples[1]); } else { samples[0] = new Uint8Array(Utils.strToArrayBuffer(samples[0])); samples[1] = new Uint8Array(Utils.strToArrayBuffer(samples[1])); } let dist = 0; for (let i = 0; i < samples[0].length; i++) { const lhs = samples[0][i], rhs = samples[1][i]; if (byByte && lhs !== rhs) { dist++; } else if (!byByte) { let xord = lhs ^ rhs; while (xord) { dist++; xord &= xord - 1; } } } return dist.toString(); } } export default HammingDistance; ================================================ FILE: src/core/operations/HaversineDistance.mjs ================================================ /** * @author Dachande663 [dachande663@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * HaversineDistance operation */ class HaversineDistance extends Operation { /** * HaversineDistance constructor */ constructor() { super(); this.name = "Haversine distance"; this.module = "Default"; this.description = "Returns the distance between two pairs of GPS latitude and longitude co-ordinates in metres.

e.g. 51.487263,-0.124323, 38.9517,-77.1467"; this.infoURL = "https://wikipedia.org/wiki/Haversine_formula"; this.inputType = "string"; this.outputType = "number"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {number} */ run(input, args) { const values = input.match(/^(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?)$/); if (!values) { throw new OperationError("Input must in the format lat1, lng1, lat2, lng2"); } const lat1 = parseFloat(values[1]); const lng1 = parseFloat(values[3]); const lat2 = parseFloat(values[5]); const lng2 = parseFloat(values[7]); const TO_RAD = Math.PI / 180; const dLat = (lat2-lat1) * TO_RAD; const dLng = (lng2-lng1) * TO_RAD; 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); const metres = 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return metres; } } export default HaversineDistance; ================================================ FILE: src/core/operations/Head.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * Head operation */ class Head extends Operation { /** * Head constructor */ constructor() { super(); this.name = "Head"; this.module = "Default"; this.description = "Like the UNIX head utility.
Gets the first n lines.
You can select all but the last n lines by entering a negative value for n.
The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": INPUT_DELIM_OPTIONS }, { "name": "Number", "type": "number", "value": 10 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let delimiter = args[0]; const number = args[1]; delimiter = Utils.charRep(delimiter); const splitInput = input.split(delimiter); return splitInput .filter((line, lineIndex) => { lineIndex += 1; if (number < 0) { return lineIndex <= splitInput.length + number; } else { return lineIndex <= number; } }) .join(delimiter); } } export default Head; ================================================ FILE: src/core/operations/HeatmapChart.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import * as d3temp from "d3"; import * as nodomtemp from "nodom"; import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; const d3 = d3temp.default ? d3temp.default : d3temp; const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; /** * Heatmap chart operation */ class HeatmapChart extends Operation { /** * HeatmapChart constructor */ constructor() { super(); this.name = "Heatmap chart"; this.module = "Charts"; this.description = "A heatmap is a graphical representation of data where the individual values contained in a matrix are represented as colors."; this.infoURL = "https://wikipedia.org/wiki/Heat_map"; this.inputType = "string"; this.outputType = "html"; this.args = [ { name: "Record delimiter", type: "option", value: RECORD_DELIMITER_OPTIONS, }, { name: "Field delimiter", type: "option", value: FIELD_DELIMITER_OPTIONS, }, { name: "Number of vertical bins", type: "number", value: 25, }, { name: "Number of horizontal bins", type: "number", value: 25, }, { name: "Use column headers as labels", type: "boolean", value: true, }, { name: "X label", type: "string", value: "", }, { name: "Y label", type: "string", value: "", }, { name: "Draw bin edges", type: "boolean", value: false, }, { name: "Min colour value", type: "string", value: COLOURS.min, }, { name: "Max colour value", type: "string", value: COLOURS.max, }, ]; } /** * Heatmap chart operation. * * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const recordDelimiter = Utils.charRep(args[0]), fieldDelimiter = Utils.charRep(args[1]), vBins = args[2], hBins = args[3], columnHeadingsAreIncluded = args[4], drawEdges = args[7], minColour = args[8], maxColour = args[9], dimension = 500; if (vBins <= 0) throw new OperationError("Number of vertical bins must be greater than 0"); if (hBins <= 0) throw new OperationError("Number of horizontal bins must be greater than 0"); let xLabel = args[5], yLabel = args[6]; const { headings, values } = getScatterValues( input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded ); if (headings) { xLabel = headings.x; yLabel = headings.y; } const document = new nodom.Document(); let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") .attr("height", "100%") .attr("viewBox", `0 0 ${dimension} ${dimension}`); const margin = { top: 10, right: 0, bottom: 40, left: 30, }, width = dimension - margin.left - margin.right, height = dimension - margin.top - margin.bottom, binWidth = width / hBins, binHeight = height/ vBins, marginedSpace = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); const bins = this.getHeatmapPacking(values, vBins, hBins), maxCount = Math.max(...bins.map(row => { const lengths = row.map(cell => cell.length); return Math.max(...lengths); })); const xExtent = d3.extent(values, d => d[0]), yExtent = d3.extent(values, d => d[1]); const xAxis = d3.scaleLinear() .domain(xExtent) .range([0, width]); const yAxis = d3.scaleLinear() .domain(yExtent) .range([height, 0]); const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) .domain([0, maxCount]); marginedSpace.append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", height); marginedSpace.append("g") .attr("class", "bins") .attr("clip-path", "url(#clip)") .selectAll("g") .data(bins) .enter() .append("g") .selectAll("rect") .data(d => d) .enter() .append("rect") .attr("x", (d) => binWidth * d.x) .attr("y", (d) => (height - binHeight * (d.y + 1))) .attr("width", binWidth) .attr("height", binHeight) .attr("fill", (d) => colour(d.length)) .attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none") .attr("stroke-width", drawEdges ? "0.5" : "none") .append("title") .text(d => { const count = d.length, perc = 100.0 * d.length / values.length, tooltip = `Count: ${count}\n Percentage: ${perc.toFixed(2)}%\n `.replace(/\s{2,}/g, "\n"); return tooltip; }); marginedSpace.append("g") .attr("class", "axis axis--y") .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); svg.append("text") .attr("transform", "rotate(-90)") .attr("y", -margin.left) .attr("x", -(height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text(yLabel); marginedSpace.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); svg.append("text") .attr("x", width / 2) .attr("y", dimension) .style("text-anchor", "middle") .text(xLabel); return svg._groups[0][0].outerHTML; } /** * Packs a list of x, y coordinates into a number of bins for use in a heatmap. * * @param {Object[]} points * @param {number} number of vertical bins * @param {number} number of horizontal bins * @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points */ getHeatmapPacking(values, vBins, hBins) { const xBounds = d3.extent(values, d => d[0]), yBounds = d3.extent(values, d => d[1]), bins = []; if (xBounds[0] === xBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum X coordinate."; if (yBounds[0] === yBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum Y coordinate."; for (let y = 0; y < vBins; y++) { bins.push([]); for (let x = 0; x < hBins; x++) { const item = []; item.y = y; item.x = x; bins[y].push(item); } // x } // y const epsilon = 0.000000001; // This is to clamp values that are exactly the maximum; values.forEach(v => { const fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]), fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]), y = Math.floor(vBins * fractionOfY), x = Math.floor(hBins * fractionOfX); bins[y][x].push({x: v[0], y: v[1]}); }); return bins; } } export default HeatmapChart; ================================================ FILE: src/core/operations/HexDensityChart.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import * as d3temp from "d3"; import * as d3hexbintemp from "d3-hexbin"; import * as nodomtemp from "nodom"; import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; const d3 = d3temp.default ? d3temp.default : d3temp; const d3hexbin = d3hexbintemp.default ? d3hexbintemp.default : d3hexbintemp; const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; /** * Hex Density chart operation */ class HexDensityChart extends Operation { /** * HexDensityChart constructor */ constructor() { super(); this.name = "Hex Density chart"; this.module = "Charts"; 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."; this.inputType = "string"; this.outputType = "html"; this.args = [ { name: "Record delimiter", type: "option", value: RECORD_DELIMITER_OPTIONS, }, { name: "Field delimiter", type: "option", value: FIELD_DELIMITER_OPTIONS, }, { name: "Pack radius", type: "number", value: 25, }, { name: "Draw radius", type: "number", value: 15, }, { name: "Use column headers as labels", type: "boolean", value: true, }, { name: "X label", type: "string", value: "", }, { name: "Y label", type: "string", value: "", }, { name: "Draw hexagon edges", type: "boolean", value: false, }, { name: "Min colour value", type: "string", value: COLOURS.min, }, { name: "Max colour value", type: "string", value: COLOURS.max, }, { name: "Draw empty hexagons within data boundaries", type: "boolean", value: false, } ]; } /** * Hex Bin chart operation. * * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const recordDelimiter = Utils.charRep(args[0]), fieldDelimiter = Utils.charRep(args[1]), packRadius = args[2], drawRadius = args[3], columnHeadingsAreIncluded = args[4], drawEdges = args[7], minColour = args[8], maxColour = args[9], drawEmptyHexagons = args[10], dimension = 500; let xLabel = args[5], yLabel = args[6]; const { headings, values } = getScatterValues( input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded ); if (headings) { xLabel = headings.x; yLabel = headings.y; } const document = new nodom.Document(); let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") .attr("height", "100%") .attr("viewBox", `0 0 ${dimension} ${dimension}`); const margin = { top: 10, right: 0, bottom: 40, left: 30, }, width = dimension - margin.left - margin.right, height = dimension - margin.top - margin.bottom, marginedSpace = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); const hexbin = d3hexbin.hexbin() .radius(packRadius) .extent([0, 0], [width, height]); const hexPoints = hexbin(values), maxCount = Math.max(...hexPoints.map(b => b.length)); const xExtent = d3.extent(hexPoints, d => d.x), yExtent = d3.extent(hexPoints, d => d.y); xExtent[0] -= 2 * packRadius; xExtent[1] += 3 * packRadius; yExtent[0] -= 2 * packRadius; yExtent[1] += 2 * packRadius; const xAxis = d3.scaleLinear() .domain(xExtent) .range([0, width]); const yAxis = d3.scaleLinear() .domain(yExtent) .range([height, 0]); const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) .domain([0, maxCount]); marginedSpace.append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", height); if (drawEmptyHexagons) { marginedSpace.append("g") .attr("class", "empty-hexagon") .selectAll("path") .data(this.getEmptyHexagons(hexPoints, packRadius)) .enter() .append("path") .attr("d", d => { return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; }) .attr("fill", (d) => colour(0)) .attr("stroke", drawEdges ? "black" : "none") .attr("stroke-width", drawEdges ? "0.5" : "none") .append("title") .text(d => { const count = 0, perc = 0, tooltip = `Count: ${count}\n Percentage: ${perc.toFixed(2)}%\n Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n `.replace(/\s{2,}/g, "\n"); return tooltip; }); } marginedSpace.append("g") .attr("class", "hexagon") .attr("clip-path", "url(#clip)") .selectAll("path") .data(hexPoints) .enter() .append("path") .attr("d", d => { return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; }) .attr("fill", (d) => colour(d.length)) .attr("stroke", drawEdges ? "black" : "none") .attr("stroke-width", drawEdges ? "0.5" : "none") .append("title") .text(d => { const count = d.length, perc = 100.0 * d.length / values.length, CX = d.x, CY = d.y, xMin = Math.min(...d.map(d => d[0])), xMax = Math.max(...d.map(d => d[0])), yMin = Math.min(...d.map(d => d[1])), yMax = Math.max(...d.map(d => d[1])), tooltip = `Count: ${count}\n Percentage: ${perc.toFixed(2)}%\n Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n Min X: ${xMin.toFixed(2)}\n Max X: ${xMax.toFixed(2)}\n Min Y: ${yMin.toFixed(2)}\n Max Y: ${yMax.toFixed(2)} `.replace(/\s{2,}/g, "\n"); return tooltip; }); marginedSpace.append("g") .attr("class", "axis axis--y") .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); svg.append("text") .attr("transform", "rotate(-90)") .attr("y", -margin.left) .attr("x", -(height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text(yLabel); marginedSpace.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); svg.append("text") .attr("x", width / 2) .attr("y", dimension) .style("text-anchor", "middle") .text(xLabel); return svg._groups[0][0].outerHTML; } /** * Hex Bin chart operation. * * @param {Object[]} - centres * @param {number} - radius * @returns {Object[]} */ getEmptyHexagons(centres, radius) { const emptyCentres = [], boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)], hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius, hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius; let indent = false; for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) { for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) { let cx = x; const cy = y; if (indent && x >= boundingRect[0][1]) break; if (indent) cx += hexagonCenterToEdge; emptyCentres.push({x: cx, y: cy}); } indent = !indent; } return emptyCentres; } } export default HexDensityChart; ================================================ FILE: src/core/operations/HexToObjectIdentifier.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; /** * Hex to Object Identifier operation */ class HexToObjectIdentifier extends Operation { /** * HexToObjectIdentifier constructor */ constructor() { super(); this.name = "Hex to Object Identifier"; this.module = "PublicKey"; this.description = "Converts a hexadecimal string into an object identifier (OID)."; this.infoURL = "https://wikipedia.org/wiki/Object_identifier"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\s/g, "")); } } export default HexToObjectIdentifier; ================================================ FILE: src/core/operations/HexToPEM.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; /** * Hex to PEM operation */ class HexToPEM extends Operation { /** * HexToPEM constructor */ constructor() { super(); this.name = "Hex to PEM"; this.module = "PublicKey"; this.description = "Converts a hexadecimal DER (Distinguished Encoding Rules) string into PEM (Privacy Enhanced Mail) format."; this.infoURL = "https://wikipedia.org/wiki/Privacy-Enhanced_Mail"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Header string", "type": "string", "value": "CERTIFICATE" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\s/g, ""), args[0]); } } export default HexToPEM; ================================================ FILE: src/core/operations/IPv6TransitionAddresses.mjs ================================================ /** * @author jb30795 [jb30795@proton.me] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * IPv6 Transition Addresses operation */ class IPv6TransitionAddresses extends Operation { /** * IPv6TransitionAddresses constructor */ constructor() { super(); this.name = "IPv6 Transition Addresses"; this.module = "Default"; 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.

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.

Only /24 ranges and currently handled. Remove headers to easily copy out results."; this.infoURL = "https://wikipedia.org/wiki/IPv6_transition_mechanism"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Ignore ranges", "type": "boolean", "value": true }, { "name": "Remove headers", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { 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"}; /** * Function to convert to hex */ function hexify(octet) { return Number(octet).toString(16).padStart(2, "0"); } /** * Function to convert Hex to Int */ function intify(hex) { return parseInt(hex, 16); } /** * Function converts IPv4 to IPv6 Transtion address */ function ipTransition(input, range) { let output = ""; const HEXIP = input.split("."); /** * 6to4 */ if (!args[1]) { output += "6to4: "; } output += "2002:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); if (range) { output += "00::/40\n"; } else { output += hexify(HEXIP[3]) + "::/48\n"; } /** * Mapped */ if (!args[1]) { output += "IPv4 Mapped: "; } output += "::ffff:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); if (range) { output += "00/120\n"; } else { output += hexify(HEXIP[3]) + "\n"; } /** * Translated */ if (!args[1]) { output += "IPv4 Translated: "; } output += "::ffff:0:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); if (range) { output += "00/120\n"; } else { output += hexify(HEXIP[3]) + "\n"; } /** * Nat64 */ if (!args[1]) { output += "Nat 64: "; } output += "64:ff9b::" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); if (range) { output += "00/120\n"; } else { output += hexify(HEXIP[3]) + "\n"; } return output; } /** * Convert MAC to EUI-64 */ function macTransition(input) { let output = ""; const MACPARTS = input.split(":"); if (!args[1]) { output += "EUI-64 Interface ID: "; } const MAC = MACPARTS[0] + MACPARTS[1] + ":" + MACPARTS[2] + "ff:fe" + MACPARTS[3] + ":" + MACPARTS[4] + MACPARTS[5]; output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2); return output; } /** * Convert IPv6 address to its original IPv4 or MAC address */ function unTransition(input) { let output = ""; let hextets = ""; /** * 6to4 */ if (input.startsWith("2002:")) { if (!args[1]) { output += "IPv4: "; } 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"; } 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:")) { /** * Mapped/Translated/Nat64 */ 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"); if (!args[1]) { output += "IPv4: "; } 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"; } else if (input.slice(-12, -7).toUpperCase() === "FF:FE") { /** * EUI-64 */ if (!args[1]) { output += "Mac Address: "; } 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(); output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2) + "\n"; } return output; } /** * Main */ let output = ""; let inputs = input.split("\n"); // Remove blank rows inputs = inputs.filter(Boolean); for (let i = 0; i < inputs.length; i++) { // if ignore ranges is checked and input is a range, skip if ((args[0] && !inputs[i].includes("/")) || (!args[0])) { if (/^[0-9]{1,3}(?:\.[0-9]{1,3}){3}$/.test(inputs[i])) { output += ipTransition(inputs[i], false); } else if (/\/24$/.test(inputs[i])) { output += ipTransition(inputs[i], true); } else if (/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(inputs[i])) { output += macTransition(inputs[i]); } 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])) { output += unTransition(inputs[i]); } else { output = "Enter compressed or expanded IPv6 address, IPv4 address or MAC Address."; } } } return output; } } export default IPv6TransitionAddresses; ================================================ FILE: src/core/operations/ImageBrightnessContrast.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Image Brightness / Contrast operation */ class ImageBrightnessContrast extends Operation { /** * ImageBrightnessContrast constructor */ constructor() { super(); this.name = "Image Brightness / Contrast"; this.module = "Image"; this.description = "Adjust the brightness or contrast of an image."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Brightness", type: "number", value: 0, min: -100, max: 100, }, { name: "Contrast", type: "number", value: 0, min: -100, max: 100, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { const [brightness, contrast] = args; if (!isImage(input)) { throw new OperationError("Invalid file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (brightness !== 0) { if (isWorkerEnvironment()) self.sendStatusMessage("Changing image brightness..."); image.brightness(brightness / 100); } if (contrast !== 0) { if (isWorkerEnvironment()) self.sendStatusMessage("Changing image contrast..."); image.contrast(contrast / 100); } let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError( `Error adjusting image brightness or contrast. (${err})`, ); } } /** * Displays the image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default ImageBrightnessContrast; ================================================ FILE: src/core/operations/ImageFilter.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Image Filter operation */ class ImageFilter extends Operation { /** * ImageFilter constructor */ constructor() { super(); this.name = "Image Filter"; this.module = "Image"; this.description = "Applies a greyscale or sepia filter to an image."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Filter type", type: "option", value: ["Greyscale", "Sepia"], }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { const [filterType] = args; if (!isImage(input)) { throw new OperationError("Invalid file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (isWorkerEnvironment()) self.sendStatusMessage( "Applying " + filterType.toLowerCase() + " filter to image...", ); if (filterType === "Greyscale") { image.greyscale(); } else { image.sepia(); } let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError( `Error applying filter to image. (${err})`, ); } } /** * Displays the blurred image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default ImageFilter; ================================================ FILE: src/core/operations/ImageHueSaturationLightness.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Image Hue/Saturation/Lightness operation */ class ImageHueSaturationLightness extends Operation { /** * ImageHueSaturationLightness constructor */ constructor() { super(); this.name = "Image Hue/Saturation/Lightness"; this.module = "Image"; this.description = "Adjusts the hue / saturation / lightness (HSL) values of an image."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Hue", type: "number", value: 0, min: -360, max: 360, }, { name: "Saturation", type: "number", value: 0, min: -100, max: 100, }, { name: "Lightness", type: "number", value: 0, min: -100, max: 100, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { const [hue, saturation, lightness] = args; if (!isImage(input)) { throw new OperationError("Invalid file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (hue !== 0) { if (isWorkerEnvironment()) self.sendStatusMessage("Changing image hue..."); image.color([ { apply: "hue", params: [hue], }, ]); } if (saturation !== 0) { if (isWorkerEnvironment()) self.sendStatusMessage("Changing image saturation..."); image.color([ { apply: "saturate", params: [saturation], }, ]); } if (lightness !== 0) { if (isWorkerEnvironment()) self.sendStatusMessage("Changing image lightness..."); image.color([ { apply: "lighten", params: [lightness], }, ]); } let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError( `Error adjusting image hue / saturation / lightness. (${err})`, ); } } /** * Displays the image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default ImageHueSaturationLightness; ================================================ FILE: src/core/operations/ImageOpacity.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Image Opacity operation */ class ImageOpacity extends Operation { /** * ImageOpacity constructor */ constructor() { super(); this.name = "Image Opacity"; this.module = "Image"; this.description = "Adjust the opacity of an image."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Opacity (%)", type: "number", value: 100, min: 0, max: 100, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { const [opacity] = args; if (!isImage(input)) { throw new OperationError("Invalid file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (isWorkerEnvironment()) self.sendStatusMessage("Changing image opacity..."); image.opacity(opacity / 100); let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error changing image opacity. (${err})`); } } /** * Displays the image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default ImageOpacity; ================================================ FILE: src/core/operations/IndexOfCoincidence.mjs ================================================ /** * @author George O [georgeomnet+cyberchef@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Index of Coincidence operation */ class IndexOfCoincidence extends Operation { /** * IndexOfCoincidence constructor */ constructor() { super(); this.name = "Index of Coincidence"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Index_of_coincidence"; this.inputType = "string"; this.outputType = "number"; this.presentType = "html"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {number} */ run(input, args) { const text = input.toLowerCase().replace(/[^a-z]/g, ""), frequencies = new Array(26).fill(0), alphabet = Utils.expandAlphRange("a-z"); let coincidence = 0.00, density = 0.00, result = 0.00, i; for (i=0; i < alphabet.length; i++) { frequencies[i] = text.count(alphabet[i]); } for (i=0; i < frequencies.length; i++) { coincidence += frequencies[i] * (frequencies[i] - 1); } density = frequencies.sum(); // Ensure that we don't divide by 0 if (density < 2) density = 2; result = coincidence / (density * (density - 1)); return result; } /** * Displays the IC as a scale bar for web apps. * * @param {number} ic * @returns {html} */ present(ic) { return `Index of Coincidence: ${ic} Normalized: ${ic * 26}

- 0 represents complete randomness (all characters are unique), whereas 1 represents no randomness (all characters are identical). - English text generally has an IC of between 0.67 to 0.78. - 'Random' text is determined by the probability that each letter occurs the same number of times as another. The graph shows the IC of the input data. A low IC generally means that the text is random, compressed or encrypted. `; } } export default IndexOfCoincidence; ================================================ FILE: src/core/operations/InvertImage.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Invert Image operation */ class InvertImage extends Operation { /** * InvertImage constructor */ constructor() { super(); this.name = "Invert Image"; this.module = "Image"; this.description = "Invert the colours of an image."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { if (!isImage(input)) { throw new OperationError("Invalid input file format."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (isWorkerEnvironment()) self.sendStatusMessage("Inverting image..."); image.invert(); let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error inverting image. (${err})`); } } /** * Displays the inverted image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default InvertImage; ================================================ FILE: src/core/operations/JA3Fingerprint.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 * * JA3 created by Salesforce * John B. Althouse * Jeff Atkinson * Josh Atkins * * Algorithm released under the BSD-3-clause licence */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import Stream from "../lib/Stream.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * JA3 Fingerprint operation */ class JA3Fingerprint extends Operation { /** * JA3Fingerprint constructor */ constructor() { super(); this.name = "JA3 Fingerprint"; this.module = "Crypto"; this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.

Input: A hex stream of the TLS Client Hello packet application layer."; this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Input format", type: "option", value: ["Hex", "Base64", "Raw"] }, { name: "Output format", type: "option", value: ["Hash digest", "JA3 string", "Full details"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [inputFormat, outputFormat] = args; input = Utils.convertToByteArray(input, inputFormat); const s = new Stream(new Uint8Array(input)); const handshake = s.readInt(1); if (handshake !== 0x16) throw new OperationError("Not handshake data."); // Version s.moveForwardsBy(2); // Length const length = s.readInt(2); if (s.length !== length + 5) throw new OperationError("Incorrect handshake length."); // Handshake type const handshakeType = s.readInt(1); if (handshakeType !== 1) throw new OperationError("Not a Client Hello."); // Handshake length const handshakeLength = s.readInt(3); if (s.length !== handshakeLength + 9) throw new OperationError("Not enough data in Client Hello."); // Hello version const helloVersion = s.readInt(2); // Random s.moveForwardsBy(32); // Session ID const sessionIDLength = s.readInt(1); s.moveForwardsBy(sessionIDLength); // Cipher suites const cipherSuitesLength = s.readInt(2); const cipherSuites = s.getBytes(cipherSuitesLength); const cs = new Stream(cipherSuites); const cipherSegment = parseJA3Segment(cs, 2); // Compression Methods const compressionMethodsLength = s.readInt(1); s.moveForwardsBy(compressionMethodsLength); // Extensions const extensionsLength = s.readInt(2); const extensions = s.getBytes(extensionsLength); const es = new Stream(extensions); let ecsLen, ecs, ellipticCurves = "", ellipticCurvePointFormats = ""; const exts = []; while (es.hasMore()) { const type = es.readInt(2); const length = es.readInt(2); switch (type) { case 0x0a: // Elliptic curves ecsLen = es.readInt(2); ecs = new Stream(es.getBytes(ecsLen)); ellipticCurves = parseJA3Segment(ecs, 2); break; case 0x0b: // Elliptic curve point formats ecsLen = es.readInt(1); ecs = new Stream(es.getBytes(ecsLen)); ellipticCurvePointFormats = parseJA3Segment(ecs, 1); break; default: es.moveForwardsBy(length); } if (!GREASE_CIPHERSUITES.includes(type)) exts.push(type); } // Output const ja3 = [ helloVersion.toString(), cipherSegment, exts.join("-"), ellipticCurves, ellipticCurvePointFormats ]; const ja3Str = ja3.join(","); const ja3Hash = runHash("md5", Utils.strToArrayBuffer(ja3Str)); switch (outputFormat) { case "JA3 string": return ja3Str; case "Full details": return `Hash digest: ${ja3Hash} Full JA3 string: ${ja3Str} TLS Version: ${helloVersion.toString()} Cipher Suites: ${cipherSegment} Extensions: ${exts.join("-")} Elliptic Curves: ${ellipticCurves} Elliptic Curve Point Formats: ${ellipticCurvePointFormats}`; case "Hash digest": default: return ja3Hash; } } } /** * Parses a JA3 segment, returning a "-" separated list * * @param {Stream} stream * @returns {string} */ function parseJA3Segment(stream, size=2) { const segment = []; while (stream.hasMore()) { const element = stream.readInt(size); if (!GREASE_CIPHERSUITES.includes(element)) segment.push(element); } return segment.join("-"); } const GREASE_CIPHERSUITES = [ 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa ]; export default JA3Fingerprint; ================================================ FILE: src/core/operations/JA3SFingerprint.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 * * JA3S created by Salesforce * John B. Althouse * Jeff Atkinson * Josh Atkins * * Algorithm released under the BSD-3-clause licence */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import Stream from "../lib/Stream.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * JA3S Fingerprint operation */ class JA3SFingerprint extends Operation { /** * JA3SFingerprint constructor */ constructor() { super(); this.name = "JA3S Fingerprint"; this.module = "Crypto"; this.description = "Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.

Input: A hex stream of the TLS Server Hello record application layer."; this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Input format", type: "option", value: ["Hex", "Base64", "Raw"] }, { name: "Output format", type: "option", value: ["Hash digest", "JA3S string", "Full details"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [inputFormat, outputFormat] = args; input = Utils.convertToByteArray(input, inputFormat); const s = new Stream(new Uint8Array(input)); const handshake = s.readInt(1); if (handshake !== 0x16) throw new OperationError("Not handshake data."); // Version s.moveForwardsBy(2); // Length const length = s.readInt(2); if (s.length !== length + 5) throw new OperationError("Incorrect handshake length."); // Handshake type const handshakeType = s.readInt(1); if (handshakeType !== 2) throw new OperationError("Not a Server Hello."); // Handshake length const handshakeLength = s.readInt(3); if (s.length !== handshakeLength + 9) throw new OperationError("Not enough data in Server Hello."); // Hello version const helloVersion = s.readInt(2); // Random s.moveForwardsBy(32); // Session ID const sessionIDLength = s.readInt(1); s.moveForwardsBy(sessionIDLength); // Cipher suite const cipherSuite = s.readInt(2); // Compression Method s.moveForwardsBy(1); // Extensions const extensionsLength = s.readInt(2); const extensions = s.getBytes(extensionsLength); const es = new Stream(extensions); const exts = []; while (es.hasMore()) { const type = es.readInt(2); const length = es.readInt(2); es.moveForwardsBy(length); exts.push(type); } // Output const ja3s = [ helloVersion.toString(), cipherSuite, exts.join("-") ]; const ja3sStr = ja3s.join(","); const ja3sHash = runHash("md5", Utils.strToArrayBuffer(ja3sStr)); switch (outputFormat) { case "JA3S string": return ja3sStr; case "Full details": return `Hash digest: ${ja3sHash} Full JA3S string: ${ja3sStr} TLS Version: ${helloVersion.toString()} Cipher Suite: ${cipherSuite} Extensions: ${exts.join("-")}`; case "Hash digest": default: return ja3sHash; } } } export default JA3SFingerprint; ================================================ FILE: src/core/operations/JA4Fingerprint.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {toJA4} from "../lib/JA4.mjs"; /** * JA4 Fingerprint operation */ class JA4Fingerprint extends Operation { /** * JA4Fingerprint constructor */ constructor() { super(); this.name = "JA4 Fingerprint"; this.module = "Crypto"; this.description = "Generates a JA4 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.

Input: A hex stream of the TLS or QUIC Client Hello packet application layer."; this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Input format", type: "option", value: ["Hex", "Base64", "Raw"] }, { name: "Output format", type: "option", value: ["JA4", "JA4 Original Rendering", "JA4 Raw", "JA4 Raw Original Rendering", "All"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [inputFormat, outputFormat] = args; input = Utils.convertToByteArray(input, inputFormat); const ja4 = toJA4(new Uint8Array(input)); // Output switch (outputFormat) { case "JA4": return ja4.JA4; case "JA4 Original Rendering": return ja4.JA4_o; case "JA4 Raw": return ja4.JA4_r; case "JA4 Raw Original Rendering": return ja4.JA4_ro; case "All": default: return `JA4: ${ja4.JA4} JA4_o: ${ja4.JA4_o} JA4_r: ${ja4.JA4_r} JA4_ro: ${ja4.JA4_ro}`; } } } export default JA4Fingerprint; ================================================ FILE: src/core/operations/JA4ServerFingerprint.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {toJA4S} from "../lib/JA4.mjs"; /** * JA4Server Fingerprint operation */ class JA4ServerFingerprint extends Operation { /** * JA4ServerFingerprint constructor */ constructor() { super(); this.name = "JA4Server Fingerprint"; this.module = "Crypto"; this.description = "Generates a JA4Server Fingerprint (JA4S) to help identify TLS servers or sessions based on hashing together values from the Server Hello.

Input: A hex stream of the TLS or QUIC Server Hello packet application layer."; this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Input format", type: "option", value: ["Hex", "Base64", "Raw"] }, { name: "Output format", type: "option", value: ["JA4S", "JA4S Raw", "Both"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [inputFormat, outputFormat] = args; input = Utils.convertToByteArray(input, inputFormat); const ja4s = toJA4S(new Uint8Array(input)); // Output switch (outputFormat) { case "JA4S": return ja4s.JA4S; case "JA4S Raw": return ja4s.JA4S_r; case "Both": default: return `JA4S: ${ja4s.JA4S}\nJA4S_r: ${ja4s.JA4S_r}`; } } } export default JA4ServerFingerprint; ================================================ FILE: src/core/operations/JPathExpression.mjs ================================================ /** * @author Matt C (matt@artemisbot.uk) * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import {JSONPath} from "jsonpath-plus"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * JPath expression operation */ class JPathExpression extends Operation { /** * JPathExpression constructor */ constructor() { super(); this.name = "JPath expression"; this.module = "Code"; this.description = "Extract information from a JSON object with a JPath query."; this.infoURL = "http://goessner.net/articles/JsonPath/"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Query", type: "string", value: "" }, { name: "Result delimiter", type: "binaryShortString", value: "\\n" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [query, delimiter] = args; let results, jsonObj; try { jsonObj = JSON.parse(input); } catch (err) { throw new OperationError(`Invalid input JSON: ${err.message}`); } try { results = JSONPath({ path: query, json: jsonObj }); } catch (err) { throw new OperationError(`Invalid JPath expression: ${err.message}`); } return results.map(result => JSON.stringify(result)).join(delimiter); } } export default JPathExpression; ================================================ FILE: src/core/operations/JSONBeautify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author Phillip Nordwall [phillip.nordwall@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import JSON5 from "json5"; import OperationError from "../errors/OperationError.mjs"; import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * JSON Beautify operation */ class JSONBeautify extends Operation { /** * JSONBeautify constructor */ constructor() { super(); this.name = "JSON Beautify"; this.module = "Code"; this.description = "Indents and pretty prints JavaScript Object Notation (JSON) code.

Tags: json viewer, prettify, syntax highlighting"; this.inputType = "string"; this.outputType = "string"; this.presentType = "html"; this.args = [ { name: "Indent string", type: "binaryShortString", value: " " }, { name: "Sort Object Keys", type: "boolean", value: false }, { name: "Formatted", type: "boolean", value: true } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!input) return ""; const [indentStr, sortBool] = args; let json = null; try { json = JSON5.parse(input); } catch (err) { throw new OperationError("Unable to parse input as JSON.\n" + err); } if (sortBool) json = sortKeys(json); return JSON.stringify(json, null, indentStr); } /** * Adds various dynamic features to the JSON blob * * @param {string} data * @param {Object[]} args * @returns {html} */ present(data, args) { const formatted = args[2]; if (!formatted) return Utils.escapeHtml(data); const json = JSON5.parse(data); const options = { withLinks: true, bigNumbers: true }; let html = '
'; if (isCollapsable(json)) { const isArr = json instanceof Array; html += '
' + `` + json2html(json, options) + "
"; } else { html += json2html(json, options); } html += "
"; return html; } } /** * Sort keys in a JSON object * * @author Phillip Nordwall [phillip.nordwall@gmail.com] * @param {object} o * @returns {object} */ function sortKeys(o) { if (Array.isArray(o)) { return o.map(sortKeys); } else if ("[object Object]" === Object.prototype.toString.call(o)) { return Object.keys(o).sort().reduce(function(a, k) { a[k] = sortKeys(o[k]); return a; }, {}); } return o; } /** * Check if arg is either an array with at least 1 element, or a dict with at least 1 key * @returns {boolean} */ function isCollapsable(arg) { return arg instanceof Object && Object.keys(arg).length > 0; } /** * Check if a string looks like a URL, based on protocol * @returns {boolean} */ function isUrl(string) { const protocols = ["http", "https", "ftp", "ftps"]; for (let i = 0; i < protocols.length; i++) { if (string.startsWith(protocols[i] + "://")) { return true; } } return false; } /** * Transform a json object into html representation * * Adapted for CyberChef by @n1474335 from jQuery json-viewer * @author Alexandre Bodelot * @link https://github.com/abodelot/jquery.json-viewer * @license MIT * * @returns {string} */ function json2html(json, options) { let html = ""; if (typeof json === "string") { // Escape tags and quotes json = Utils.escapeHtml(json); if (options.withLinks && isUrl(json)) { html += `
${json}`; } else { // Escape double quotes in the rendered non-URL string. json = json.replace(/"/g, "\\""); html += `"${json}"`; } } else if (typeof json === "number" || typeof json === "bigint") { html += `${json}`; } else if (typeof json === "boolean") { html += `${json}`; } else if (json === null) { html += 'null'; } else if (json instanceof Array) { if (json.length > 0) { html += '[
    '; for (let i = 0; i < json.length; i++) { html += "
  1. "; // Add toggle button if item is collapsable if (isCollapsable(json[i])) { const isArr = json[i] instanceof Array; html += '
    ' + `` + json2html(json[i], options) + "
    "; } else { html += json2html(json[i], options); } // Add comma if item is not last if (i < json.length - 1) { html += ','; } html += "
  2. "; } html += '
]'; } else { html += '[]'; } } else if (typeof json === "object") { // Optional support different libraries for big numbers // json.isLosslessNumber: package lossless-json // json.toExponential(): packages bignumber.js, big.js, decimal.js, decimal.js-light, others? if (options.bigNumbers && (typeof json.toExponential === "function" || json.isLosslessNumber)) { html += `${json.toString()}`; } else { let keyCount = Object.keys(json).length; if (keyCount > 0) { html += '{
    '; for (const key in json) { if (Object.prototype.hasOwnProperty.call(json, key)) { const safeKey = Utils.escapeHtml(key); html += "
  • "; // Add toggle button if item is collapsable if (isCollapsable(json[key])) { const isArr = json[key] instanceof Array; html += '
    ' + `${safeKey}: ` + json2html(json[key], options) + "
    "; } else { html += safeKey + ': ' + json2html(json[key], options); } // Add comma if item is not last if (--keyCount > 0) { html += ','; } html += "
  • "; } } html += '
}'; } else { html += '{}'; } } } return html; } export default JSONBeautify; ================================================ FILE: src/core/operations/JSONMinify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import vkbeautify from "vkbeautify"; import Operation from "../Operation.mjs"; /** * JSON Minify operation */ class JSONMinify extends Operation { /** * JSONMinify constructor */ constructor() { super(); this.name = "JSON Minify"; this.module = "Code"; this.description = "Compresses JavaScript Object Notation (JSON) code."; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!input) return ""; return vkbeautify.jsonmin(input); } } export default JSONMinify; ================================================ FILE: src/core/operations/JSONToCSV.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import * as flat from "flat"; const flatten = flat.default ? flat.default.flatten : flat.flatten; /** * JSON to CSV operation */ class JSONToCSV extends Operation { /** * JSONToCSV constructor */ constructor() { super(); this.name = "JSON to CSV"; this.module = "Default"; this.description = "Converts JSON data to a CSV based on the definition in RFC 4180."; this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; this.inputType = "JSON"; this.outputType = "string"; this.args = [ { name: "Cell delimiter", type: "binaryShortString", value: "," }, { name: "Row delimiter", type: "binaryShortString", value: "\\r\\n" } ]; } /** * Converts JSON to a CSV equivalent. * * @param {boolean} force - Whether to force conversion of data to fit in a cell * @returns {string} */ toCSV(force=false) { const self = this; // If the JSON is an array of arrays, this is easy if (this.flattened[0] instanceof Array) { return this.flattened .map(row => row .map(d => self.escapeCellContents(d, force)) .join(this.cellDelim) ) .join(this.rowDelim) + this.rowDelim; } // If it's an array of dictionaries... const header = Object.keys(this.flattened[0]); return header .map(d => self.escapeCellContents(d, force)) .join(this.cellDelim) + this.rowDelim + this.flattened .map(row => header .map(h => row[h]) .map(d => self.escapeCellContents(d, force)) .join(this.cellDelim) ) .join(this.rowDelim) + this.rowDelim; } /** * @param {JSON} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [cellDelim, rowDelim] = args; // Record values so they don't have to be passed to other functions explicitly this.cellDelim = cellDelim; this.rowDelim = rowDelim; this.flattened = input; if (!(this.flattened instanceof Array)) { this.flattened = [input]; } try { return this.toCSV(); } catch (err) { try { this.flattened = flatten(input); if (!(this.flattened instanceof Array)) { this.flattened = [this.flattened]; } return this.toCSV(true); } catch (err) { throw new OperationError("Unable to parse JSON to CSV: " + err.toString()); } } } /** * Correctly escapes a cell's contents based on the cell and row delimiters. * * @param {string} data * @param {boolean} force - Whether to force conversion of data to fit in a cell * @returns {string} */ escapeCellContents(data, force=false) { if (data !== "string") { const isPrimitive = data == null || typeof data !== "object"; if (isPrimitive) data = `${data}`; else if (force) data = JSON.stringify(data); } // Double quotes should be doubled up data = data.replace(/"/g, '""'); // If the cell contains a cell or row delimiter or a double quote, it must be enclosed in double quotes if ( data.indexOf(this.cellDelim) >= 0 || data.indexOf(this.rowDelim) >= 0 || data.indexOf("\n") >= 0 || data.indexOf("\r") >= 0 || data.indexOf('"') >= 0 ) { data = `"${data}"`; } return data; } } export default JSONToCSV; ================================================ FILE: src/core/operations/JSONtoYAML.mjs ================================================ /** * @author ccarpo [ccarpo@gmx.net] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import YAML from "yaml"; /** * JSON to YAML operation */ class JSONtoYAML extends Operation { /** * JSONtoYAML constructor */ constructor() { super(); this.name = "JSON to YAML"; this.module = "Default"; this.description = "Format a JSON object into YAML"; this.infoURL = "https://en.wikipedia.org/wiki/YAML"; this.inputType = "JSON"; this.outputType = "string"; this.args = []; } /** * @param {JSON} input * @param {Object[]} args * @returns {string} */ run(input, args) { try { return YAML.stringify(input); } catch (err) { throw new OperationError("Test"); } } } export default JSONtoYAML; ================================================ FILE: src/core/operations/JWKToPem.mjs ================================================ /** * @author cplussharp * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * PEM to JWK operation */ class PEMToJWK extends Operation { /** * PEMToJWK constructor */ constructor() { super(); this.name = "JWK to PEM"; this.module = "PublicKey"; this.description = "Converts Keys in JSON Web Key format to PEM format (PKCS#8)."; this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = [ { "pattern": "\"kty\":\\s*\"(EC|RSA)\"", "flags": "gm", "args": [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const inputJson = JSON.parse(input); let keys = []; if (Array.isArray(inputJson)) { // list of keys => transform all keys keys = inputJson; } else if (Array.isArray(inputJson.keys)) { // JSON Web Key Set => transform all keys keys = inputJson.keys; } else if (typeof inputJson === "object") { // single key keys.push(inputJson); } else { throw new OperationError("Input is not a JSON Web Key"); } let output = ""; for (let i=0; i" + e.message); } return result; } } export default JavaScriptBeautify; ================================================ FILE: src/core/operations/JavaScriptMinify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import OperationError from "../errors/OperationError.mjs"; import Operation from "../Operation.mjs"; import * as terser from "terser"; /** * JavaScript Minify operation */ class JavaScriptMinify extends Operation { /** * JavaScriptMinify constructor */ constructor() { super(); this.name = "JavaScript Minify"; this.module = "Code"; this.description = "Compresses JavaScript code."; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const result = await terser.minify(input); if (result.error) { throw new OperationError(`Error minifying JavaScript. (${result.error})`); } return result.code; } } export default JavaScriptMinify; ================================================ FILE: src/core/operations/JavaScriptParser.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import * as esprima from "esprima"; /** * JavaScript Parser operation */ class JavaScriptParser extends Operation { /** * JavaScriptParser constructor */ constructor() { super(); this.name = "JavaScript Parser"; this.module = "Code"; this.description = "Returns an Abstract Syntax Tree for valid JavaScript code."; this.infoURL = "https://wikipedia.org/wiki/Abstract_syntax_tree"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Location info", "type": "boolean", "value": false }, { "name": "Range info", "type": "boolean", "value": false }, { "name": "Include tokens array", "type": "boolean", "value": false }, { "name": "Include comments array", "type": "boolean", "value": false }, { "name": "Report errors and try to continue", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [parseLoc, parseRange, parseTokens, parseComment, parseTolerant] = args, options = { loc: parseLoc, range: parseRange, tokens: parseTokens, comment: parseComment, tolerant: parseTolerant }; let result = {}; result = esprima.parseScript(input, options); return JSON.stringify(result, null, 2); } } export default JavaScriptParser; ================================================ FILE: src/core/operations/Jq.mjs ================================================ /** * @author zhzy0077 [zhzy0077@hotmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import jq from "jq-web"; /** * jq operation */ class Jq extends Operation { /** * Jq constructor */ constructor() { super(); this.name = "Jq"; this.module = "Jq"; this.description = "jq is a lightweight and flexible command-line JSON processor."; this.infoURL = "https://github.com/jqlang/jq"; this.inputType = "JSON"; this.outputType = "string"; this.args = [ { name: "Query", type: "string", value: "" } ]; } /** * @param {JSON} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [query] = args; let result; try { result = jq.json(input, query); } catch (err) { throw new OperationError(`Invalid jq expression: ${err.message}`); } return JSON.stringify(result); } } export default Jq; ================================================ FILE: src/core/operations/Jsonata.mjs ================================================ /** * @author Jon K (jon@ajarsoftware.com) * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import jsonata from "jsonata"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Jsonata Query operation */ class JsonataQuery extends Operation { /** * JsonataQuery constructor */ constructor() { super(); this.name = "Jsonata Query"; this.module = "Code"; this.description = "Query and transform JSON data with a jsonata query."; this.infoURL = "https://docs.jsonata.org/overview.html"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Query", type: "text", value: "string", }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [query] = args; let result, jsonObj; try { jsonObj = JSON.parse(input); } catch (err) { throw new OperationError(`Invalid input JSON: ${err.message}`); } try { const expression = jsonata(query); result = await expression.evaluate(jsonObj); } catch (err) { throw new OperationError( `Invalid Jsonata Expression: ${err.message}` ); } return JSON.stringify(result === undefined ? "" : result); } } export default JsonataQuery; ================================================ FILE: src/core/operations/Jump.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { getLabelIndex } from "../lib/FlowControl.mjs"; /** * Jump operation */ class Jump extends Operation { /** * Jump constructor */ constructor() { super(); this.name = "Jump"; this.flowControl = true; this.module = "Default"; this.description = "Jump forwards or backwards to the specified Label"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Label name", "type": "string", "value": "" }, { "name": "Maximum jumps (if jumping backwards)", "type": "number", "value": 10 } ]; } /** * @param {Object} state - The current state of the recipe. * @param {number} state.progress - The current position in the recipe. * @param {Dish} state.dish - The Dish being operated on. * @param {Operation[]} state.opList - The list of operations in the recipe. * @param {number} state.numJumps - The number of jumps taken so far. * @returns {Object} The updated state of the recipe. */ run(state) { const ings = state.opList[state.progress].ingValues; const [label, maxJumps] = ings; const jmpIndex = getLabelIndex(label, state); if (state.numJumps >= maxJumps || jmpIndex === -1) { state.numJumps = 0; return state; } state.progress = jmpIndex; state.numJumps++; return state; } } export default Jump; ================================================ FILE: src/core/operations/Keccak.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import JSSHA3 from "js-sha3"; import OperationError from "../errors/OperationError.mjs"; /** * Keccak operation */ class Keccak extends Operation { /** * Keccak constructor */ constructor() { super(); this.name = "Keccak"; this.module = "Crypto"; 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.

This version of the algorithm is Keccak[c=2d] and differs from the SHA-3 specification."; this.infoURL = "https://wikipedia.org/wiki/SHA-3"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Size", "type": "option", "value": ["512", "384", "256", "224"] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const size = parseInt(args[0], 10); let algo; switch (size) { case 224: algo = JSSHA3.keccak224; break; case 384: algo = JSSHA3.keccak384; break; case 256: algo = JSSHA3.keccak256; break; case 512: algo = JSSHA3.keccak512; break; default: throw new OperationError("Invalid size"); } return algo(input); } } export default Keccak; ================================================ FILE: src/core/operations/LMHash.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {smbhash} from "ntlm"; /** * LM Hash operation */ class LMHash extends Operation { /** * LMHash constructor */ constructor() { super(); this.name = "LM Hash"; this.module = "Crypto"; 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."; this.infoURL = "https://wikipedia.org/wiki/LAN_Manager#Password_hashing_algorithm"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return smbhash.lmhash(input); } } export default LMHash; ================================================ FILE: src/core/operations/LS47Decrypt.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import * as LS47 from "../lib/LS47.mjs"; /** * LS47 Decrypt operation */ class LS47Decrypt extends Operation { /** * LS47Decrypt constructor */ constructor() { super(); this.name = "LS47 Decrypt"; this.module = "Crypto"; 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.
The LS47 alphabet consists of following characters: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()
An LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption."; this.infoURL = "https://github.com/exaexa/ls47"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Password", type: "string", value: "" }, { name: "Padding", type: "number", value: 10 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { this.paddingSize = parseInt(args[1], 10); LS47.initTiles(); const key = LS47.deriveKey(args[0]); return LS47.decryptPad(key, input, this.paddingSize); } } export default LS47Decrypt; ================================================ FILE: src/core/operations/LS47Encrypt.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import * as LS47 from "../lib/LS47.mjs"; /** * LS47 Encrypt operation */ class LS47Encrypt extends Operation { /** * LS47Encrypt constructor */ constructor() { super(); this.name = "LS47 Encrypt"; this.module = "Crypto"; 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.
The LS47 alphabet consists of following characters: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()
A LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption."; this.infoURL = "https://github.com/exaexa/ls47"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Password", type: "string", value: "" }, { name: "Padding", type: "number", value: 10 }, { name: "Signature", type: "string", value: "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { this.paddingSize = parseInt(args[1], 10); LS47.initTiles(); const key = LS47.deriveKey(args[0]); return LS47.encryptPad(key, input, args[2], this.paddingSize); } } export default LS47Encrypt; ================================================ FILE: src/core/operations/LZ4Compress.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import lz4 from "lz4js"; /** * LZ4 Compress operation */ class LZ4Compress extends Operation { /** * LZ4Compress constructor */ constructor() { super(); this.name = "LZ4 Compress"; this.module = "Compression"; 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."; this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const inBuf = new Uint8Array(input); const compressed = lz4.compress(inBuf); return compressed.buffer; } } export default LZ4Compress; ================================================ FILE: src/core/operations/LZ4Decompress.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import lz4 from "lz4js"; /** * LZ4 Decompress operation */ class LZ4Decompress extends Operation { /** * LZ4Decompress constructor */ constructor() { super(); this.name = "LZ4 Decompress"; this.module = "Compression"; 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."; this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const inBuf = new Uint8Array(input); const decompressed = lz4.decompress(inBuf); return decompressed.buffer; } } export default LZ4Decompress; ================================================ FILE: src/core/operations/LZMACompress.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { compress } from "@blu3r4y/lzma"; import {isWorkerEnvironment} from "../Utils.mjs"; /** * LZMA Compress operation */ class LZMACompress extends Operation { /** * LZMACompress constructor */ constructor() { super(); this.name = "LZMA Compress"; this.module = "Compression"; 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"; this.infoURL = "https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { name: "Compression Mode", type: "option", value: [ "1", "2", "3", "4", "5", "6", "7", "8", "9" ], "defaultIndex": 6 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ async run(input, args) { const mode = Number(args[0]); return new Promise((resolve, reject) => { compress(new Uint8Array(input), mode, (result, error) => { if (error) { reject(new OperationError(`Failed to compress input: ${error.message}`)); } // The compression returns as an Int8Array, but we can just get the unsigned data from the buffer resolve(new Int8Array(result).buffer); }, (percent) => { if (isWorkerEnvironment()) self.sendStatusMessage(`Compressing input: ${(percent*100).toFixed(2)}%`); }); }); } } export default LZMACompress; ================================================ FILE: src/core/operations/LZMADecompress.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import {decompress} from "@blu3r4y/lzma"; import Utils, {isWorkerEnvironment} from "../Utils.mjs"; /** * LZMA Decompress operation */ class LZMADecompress extends Operation { /** * LZMADecompress constructor */ constructor() { super(); this.name = "LZMA Decompress"; this.module = "Compression"; this.description = "Decompresses data using the Lempel-Ziv-Markov chain Algorithm."; this.infoURL = "https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ async run(input, args) { return new Promise((resolve, reject) => { decompress(new Uint8Array(input), (result, error) => { if (error) { reject(new OperationError(`Failed to decompress input: ${error.message}`)); } // The decompression returns either a String or an untyped unsigned int8 array, but we can just get the unsigned data from the buffer if (typeof result == "string") { resolve(Utils.strToArrayBuffer(result)); } else { resolve(new Int8Array(result).buffer); } }, (percent) => { if (isWorkerEnvironment()) self.sendStatusMessage(`Decompressing input: ${(percent*100).toFixed(2)}%`); }); }); } } export default LZMADecompress; ================================================ FILE: src/core/operations/LZNT1Decompress.mjs ================================================ /** * @author 0xThiebaut [thiebaut.dev] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {decompress} from "../lib/LZNT1.mjs"; /** * LZNT1 Decompress operation */ class LZNT1Decompress extends Operation { /** * LZNT1 Decompress constructor */ constructor() { super(); this.name = "LZNT1 Decompress"; this.module = "Compression"; this.description = "Decompresses data using the LZNT1 algorithm.

Similar to the Windows API RtlDecompressBuffer."; this.infoURL = "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-xca/5655f4a3-6ba4-489b-959f-e1f407c52f15"; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = []; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { return decompress(input); } } export default LZNT1Decompress; ================================================ FILE: src/core/operations/LZStringCompress.mjs ================================================ /** * @author crespyl [peter@crespyl.net] * @copyright Peter Jacobs 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import {COMPRESSION_OUTPUT_FORMATS, COMPRESSION_FUNCTIONS} from "../lib/LZString.mjs"; /** * LZString Compress operation */ class LZStringCompress extends Operation { /** * LZStringCompress constructor */ constructor() { super(); this.name = "LZString Compress"; this.module = "Compression"; this.description = "Compress the input with lz-string."; this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Compression Format", type: "option", defaultIndex: 0, value: COMPRESSION_OUTPUT_FORMATS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const compress = COMPRESSION_FUNCTIONS[args[0]]; if (compress) { return compress(input); } else { throw new OperationError("Unable to find compression function"); } } } export default LZStringCompress; ================================================ FILE: src/core/operations/LZStringDecompress.mjs ================================================ /** * @author crespyl [peter@crespyl.net] * @copyright Peter Jacobs 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import {COMPRESSION_OUTPUT_FORMATS, DECOMPRESSION_FUNCTIONS} from "../lib/LZString.mjs"; /** * LZString Decompress operation */ class LZStringDecompress extends Operation { /** * LZStringDecompress constructor */ constructor() { super(); this.name = "LZString Decompress"; this.module = "Compression"; this.description = "Decompresses data that was compressed with lz-string."; this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Compression Format", type: "option", defaultIndex: 0, value: COMPRESSION_OUTPUT_FORMATS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const decompress = DECOMPRESSION_FUNCTIONS[args[0]]; if (decompress) { return decompress(input); } else { throw new OperationError("Unable to find decompression function"); } } } export default LZStringDecompress; ================================================ FILE: src/core/operations/Label.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Label operation. For use with Jump and Conditional Jump. */ class Label extends Operation { /** * Label constructor */ constructor() { super(); this.name = "Label"; this.flowControl = true; this.module = "Default"; this.description = "Provides a location for conditional and fixed jumps to redirect execution to."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Name", "type": "shortString", "value": "" } ]; } /** * @param {Object} state - The current state of the recipe. * @param {number} state.progress - The current position in the recipe. * @param {Dish} state.dish - The Dish being operated on. * @param {Operation[]} state.opList - The list of operations in the recipe. * @returns {Object} The updated state of the recipe. */ run(state) { return state; } } export default Label; ================================================ FILE: src/core/operations/LevenshteinDistance.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Levenshtein Distance operation */ class LevenshteinDistance extends Operation { /** * LevenshteinDistance constructor */ constructor() { super(); this.name = "Levenshtein Distance"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Levenshtein_distance"; this.inputType = "string"; this.outputType = "number"; this.args = [ { name: "Sample delimiter", type: "binaryString", value: "\\n" }, { name: "Insertion cost", type: "number", value: 1 }, { name: "Deletion cost", type: "number", value: 1 }, { name: "Substitution cost", type: "number", value: 1 }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {number} */ run(input, args) { const [delim, insCost, delCost, subCost] = args; const samples = input.split(delim); if (samples.length !== 2) { throw new OperationError("Incorrect number of samples. Check your input and/or delimiter."); } if (insCost < 0 || delCost < 0 || subCost < 0) { throw new OperationError("Negative costs are not allowed."); } const src = samples[0], dest = samples[1]; let currentCost = new Array(src.length + 1); let nextCost = new Array(src.length + 1); for (let i = 0; i < currentCost.length; i++) { currentCost[i] = delCost * i; } for (let i = 0; i < dest.length; i++) { const destc = dest.charAt(i); nextCost[0] = currentCost[0] + insCost; for (let j = 0; j < src.length; j++) { let candidate; // insertion let optCost = currentCost[j + 1] + insCost; // deletion candidate = nextCost[j] + delCost; if (candidate < optCost) optCost = candidate; // substitution or matched character candidate = currentCost[j]; if (src.charAt(j) !== destc) candidate += subCost; if (candidate < optCost) optCost = candidate; // store calculated cost nextCost[j + 1] = optCost; } const tempCost = nextCost; nextCost = currentCost; currentCost = tempCost; } return currentCost[currentCost.length - 1]; } } export default LevenshteinDistance; ================================================ FILE: src/core/operations/Lorenz.mjs ================================================ /** * Emulation of the Lorenz SZ40/42a/42b cipher attachment. * * Tested against the Colossus Rebuild at Bletchley Park's TNMOC * using a variety of inputs and settings to confirm correctness. * * @author VirtualColossus [martin@virtualcolossus.co.uk] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Lorenz operation */ class Lorenz extends Operation { /** * Lorenz constructor */ constructor() { super(); this.name = "Lorenz"; this.module = "Bletchley"; 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.

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.

Each rotor has a different number of cams/lugs around their circumference which could be set active or inactive changing the key stream.

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.

The input can either be plaintext or ITA2 when sending and ITA2 when receiving.

To learn more, Virtual Lorenz, an online, browser based simulation of the Lorenz SZ40/42 is available at lorenz.virtualcolossus.co.uk.

A more detailed description of this operation can be found here."; this.infoURL = "https://wikipedia.org/wiki/Lorenz_cipher"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Model", type: "option", value: ["SZ40", "SZ42a", "SZ42b"] }, { name: "Wheel Pattern", type: "argSelector", value: [ { name: "KH Pattern", off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] }, { name: "ZMUG Pattern", off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] }, { name: "BREAM Pattern", off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] }, { name: "No Pattern", off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] }, { name: "Custom", on: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] } ] }, { name: "KT-Schalter", type: "boolean", value: false }, { name: "Mode", type: "argSelector", value: [ { name: "Send", on: [4], off: [5] }, { name: "Receive", off: [4], on: [5] } ] }, { name: "Input Type", type: "option", value: ["Plaintext", "ITA2"] }, { name: "Output Type", type: "option", value: ["Plaintext", "ITA2"] }, { name: "ITA2 Format", type: "option", value: ["5/8/9", "+/-/."] }, { name: "Ψ1 start (1-43)", type: "number", value: 1 }, { name: "Ψ2 start (1-47)", type: "number", value: 1 }, { name: "Ψ3 start (1-51)", type: "number", value: 1 }, { name: "Ψ4 start (1-53)", type: "number", value: 1 }, { name: "Ψ5 start (1-59)", type: "number", value: 1 }, { name: "Μ37 start (1-37)", type: "number", value: 1 }, { name: "Μ61 start (1-61)", type: "number", value: 1 }, { name: "Χ1 start (1-41)", type: "number", value: 1 }, { name: "Χ2 start (1-31)", type: "number", value: 1 }, { name: "Χ3 start (1-29)", type: "number", value: 1 }, { name: "Χ4 start (1-26)", type: "number", value: 1 }, { name: "Χ5 start (1-23)", type: "number", value: 1 }, { name: "Ψ1 lugs (43)", type: "string", value: ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x" }, { name: "Ψ2 lugs (47)", type: "string", value: ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x" }, { name: "Ψ3 lugs (51)", type: "string", value: ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x" }, { name: "Ψ4 lugs (53)", type: "string", value: ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x." }, { name: "Ψ5 lugs (59)", type: "string", value: "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x." }, { name: "Μ37 lugs (37)", type: "string", value: "x.x.x.x.x.x...x.x.x...x.x.x...x.x...." }, { name: "Μ61 lugs (61)", type: "string", value: ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx..." }, { name: "Χ1 lugs (41)", type: "string", value: ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx.." }, { name: "Χ2 lugs (31)", type: "string", value: "x..xxx...x.xxxx..xx..x..xx.xx.." }, { name: "Χ3 lugs (29)", type: "string", value: "..xx..x.xxx...xx...xx..xx.xx." }, { name: "Χ4 lugs (26)", type: "string", value: "xx..x..xxxx..xx.xxx....x.." }, { name: "Χ5 lugs (23)", type: "string", value: "xx..xx....xxxx.x..x.x.." } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const model = args[0], pattern = args[1], kt = args[2], mode = args[3], intype = args[4], outtype = args[5], format = args[6], lugs1 = args[19], lugs2 = args[20], lugs3 = args[21], lugs4 = args[22], lugs5 = args[23], lugm37 = args[24], lugm61 = args[25], lugx1 = args[26], lugx2 = args[27], lugx3 = args[28], lugx4 = args[29], lugx5 = args[30]; let s1 = args[7], s2 = args[8], s3 = args[9], s4 = args[10], s5 = args[11], m37 = args[12], m61 = args[13], x1 = args[14], x2 = args[15], x3 = args[16], x4 = args[17], x5 = args[18]; this.reverseTable(); if (s1<1 || s1>43) throw new OperationError("Ψ1 start must be between 1 and 43"); if (s2<1 || s2>47) throw new OperationError("Ψ2 start must be between 1 and 47"); if (s3<1 || s3>51) throw new OperationError("Ψ3 start must be between 1 and 51"); if (s4<1 || s4>53) throw new OperationError("Ψ4 start must be between 1 and 53"); if (s5<1 || s5>59) throw new OperationError("Ψ5 start must be between 1 and 59"); if (m37<1 || m37>37) throw new OperationError("Μ37 start must be between 1 and 37"); if (m61<1 || m61>61) throw new OperationError("Μ61 start must be between 1 and 61"); if (x1<1 || x1>41) throw new OperationError("Χ1 start must be between 1 and 41"); if (x2<1 || x2>31) throw new OperationError("Χ2 start must be between 1 and 31"); if (x3<1 || x3>29) throw new OperationError("Χ3 start must be between 1 and 29"); if (x4<1 || x4>26) throw new OperationError("Χ4 start must be between 1 and 26"); if (x5<1 || x5>23) throw new OperationError("Χ5 start must be between 1 and 23"); // Initialise chosen wheel pattern let chosenSetting = ""; if (pattern === "Custom") { const re = new RegExp("^[.xX]*$"); if (lugs1.length !== 43 || !re.test(lugs1)) throw new OperationError("Ψ1 custom lugs must be 43 long and can only include . or x "); if (lugs2.length !== 47 || !re.test(lugs2)) throw new OperationError("Ψ2 custom lugs must be 47 long and can only include . or x"); if (lugs3.length !== 51 || !re.test(lugs3)) throw new OperationError("Ψ3 custom lugs must be 51 long and can only include . or x"); if (lugs4.length !== 53 || !re.test(lugs4)) throw new OperationError("Ψ4 custom lugs must be 53 long and can only include . or x"); if (lugs5.length !== 59 || !re.test(lugs5)) throw new OperationError("Ψ5 custom lugs must be 59 long and can only include . or x"); if (lugm37.length !== 37 || !re.test(lugm37)) throw new OperationError("M37 custom lugs must be 37 long and can only include . or x"); if (lugm61.length !== 61 || !re.test(lugm61)) throw new OperationError("M61 custom lugs must be 61 long and can only include . or x"); if (lugx1.length !== 41 || !re.test(lugx1)) throw new OperationError("Χ1 custom lugs must be 41 long and can only include . or x"); if (lugx2.length !== 31 || !re.test(lugx2)) throw new OperationError("Χ2 custom lugs must be 31 long and can only include . or x"); if (lugx3.length !== 29 || !re.test(lugx3)) throw new OperationError("Χ3 custom lugs must be 29 long and can only include . or x"); if (lugx4.length !== 26 || !re.test(lugx4)) throw new OperationError("Χ4 custom lugs must be 26 long and can only include . or x"); if (lugx5.length !== 23 || !re.test(lugx5)) throw new OperationError("Χ5 custom lugs must be 23 long and can only include . or x"); chosenSetting = INIT_PATTERNS["No Pattern"]; chosenSetting.S[1] = this.readLugs(lugs1); chosenSetting.S[2] = this.readLugs(lugs2); chosenSetting.S[3] = this.readLugs(lugs3); chosenSetting.S[4] = this.readLugs(lugs4); chosenSetting.S[5] = this.readLugs(lugs5); chosenSetting.M[1] = this.readLugs(lugm61); chosenSetting.M[2] = this.readLugs(lugm37); chosenSetting.X[1] = this.readLugs(lugx1); chosenSetting.X[2] = this.readLugs(lugx2); chosenSetting.X[3] = this.readLugs(lugx3); chosenSetting.X[4] = this.readLugs(lugx4); chosenSetting.X[5] = this.readLugs(lugx5); } else { chosenSetting = INIT_PATTERNS[pattern]; } const chiSettings = chosenSetting.X; // Pin settings for Chi links (X) const psiSettings = chosenSetting.S; // Pin settings for Psi links (S) const muSettings = chosenSetting.M; // Pin settings for Motor links (M) // Convert input text to ITA2 (including figure/letter shifts) const ita2Input = this.convertToITA2(input, intype, mode); let thisPsi = []; let thisChi = []; let m61lug = muSettings[1][m61-1]; let m37lug = muSettings[2][m37-1]; const p5 = [0, 0, 0]; const self = this; const letters = Array.prototype.map.call(ita2Input, function(character) { const letter = character.toUpperCase(); // Store lugs used in limitations, need these later let x2bptr = x2+1; if (x2bptr===32) x2bptr=1; let s1bptr = s1+1; if (s1bptr===44) s1bptr=1; thisChi = [ chiSettings[1][x1-1], chiSettings[2][x2-1], chiSettings[3][x3-1], chiSettings[4][x4-1], chiSettings[5][x5-1] ]; thisPsi = [ psiSettings[1][s1-1], psiSettings[2][s2-1], psiSettings[3][s3-1], psiSettings[4][s4-1], psiSettings[5][s5-1] ]; if (typeof ITA2_TABLE[letter] == "undefined") { return ""; } // The encipher calculation // We calculate Bitwise XOR for each of the 5 bits across our input ( K XOR Psi XOR Chi ) const xorSum = []; for (let i=0;i<=4;i++) { xorSum[i] = ITA2_TABLE[letter][i] ^ thisPsi[i] ^ thisChi[i]; } const resultStr = xorSum.join(""); // Wheel movement // Chi wheels always move one back after each letter if (--x1 < 1) x1 = 41; if (--x2 < 1) x2 = 31; if (--x3 < 1) x3 = 29; if (--x4 < 1) x4 = 26; if (--x5 < 1) x5 = 23; // Motor wheel (61 pin) also moves one each letter if (--m61 < 1) m61 = 61; // If M61 is set, we also move M37 if (m61lug === 1) { if (--m37 < 1) m37 = 37; } // Psi wheels only move sometimes, dependent on M37 current setting and limitations const basicmotor = m37lug; let totalmotor; let lim = 0; p5[2] = p5[1]; p5[1] = p5[0]; if (mode==="Send") { p5[0] = parseInt(ITA2_TABLE[letter][4], 10); } else { p5[0] = parseInt(xorSum[4], 10); } // Limitations here if (model==="SZ42a") { // Chi 2 one back lim - The active character of Chi 2 (2nd Chi wheel) in the previous position lim = parseInt(chiSettings[2][x2bptr-1], 10); if (kt) { // p5 back 2 if (lim===p5[2]) { lim = 0; } else { lim=1; } } // If basic motor = 0 and limitation = 1, Total motor = 0 [no move], otherwise, total motor = 1 [move] if (basicmotor===0 && lim===1) { totalmotor = 0; } else { totalmotor = 1; } } else if (model==="SZ42b") { // Chi 2 one back + Psi 1 one back. const x2b1lug = parseInt(chiSettings[2][x2bptr-1], 10); const s1b1lug = parseInt(psiSettings[1][s1bptr-1], 10); lim = 1; if (x2b1lug===s1b1lug) lim=0; if (kt) { // p5 back 2 if (lim===p5[2]) { lim=0; } else { lim=1; } } // If basic motor = 0 and limitation = 1, Total motor = 0 [no move], otherwise, total motor = 1 [move] if (basicmotor===0 && lim===1) { totalmotor = 0; } else { totalmotor = 1; } } else if (model==="SZ40") { // SZ40 - just move based on the M37 motor wheel totalmotor = basicmotor; } else { throw new OperationError("Lorenz model type not recognised"); } // Move the Psi wheels when current totalmotor active if (totalmotor === 1) { if (--s1 < 1) s1 = 43; if (--s2 < 1) s2 = 47; if (--s3 < 1) s3 = 51; if (--s4 < 1) s4 = 53; if (--s5 < 1) s5 = 59; } m61lug = muSettings[1][m61-1]; m37lug = muSettings[2][m37-1]; let rtnstr = self.REVERSE_ITA2_TABLE[resultStr]; if (format==="5/8/9") { if (rtnstr==="+") rtnstr="5"; // + or 5 used to represent figure shift if (rtnstr==="-") rtnstr="8"; // - or 8 used to represent letter shift if (rtnstr===".") rtnstr="9"; // . or 9 used to represent space } return rtnstr; }); const ita2output = letters.join(""); return this.convertFromITA2(ita2output, outtype, mode); } /** * Reverses the ITA2 Code lookup table */ reverseTable() { this.REVERSE_ITA2_TABLE = {}; this.REVERSE_FIGSHIFT_TABLE = {}; for (const letter in ITA2_TABLE) { const code = ITA2_TABLE[letter]; this.REVERSE_ITA2_TABLE[code] = letter; } for (const letter in figShiftArr) { const ltr = figShiftArr[letter]; this.REVERSE_FIGSHIFT_TABLE[ltr] = letter; } } /** * Read lugs settings - convert to 0|1 */ readLugs(lugstr) { const arr = Array.prototype.map.call(lugstr, function(lug) { if (lug===".") { return 0; } else { return 1; } }); return arr; } /** * Convert input plaintext to ITA2 */ convertToITA2(input, intype, mode) { let result = ""; let figShifted = false; for (const character of input) { const letter = character.toUpperCase(); // Convert input text to ITA2 (including figure/letter shifts) if (intype === "ITA2" || mode === "Receive") { if (validITA2.indexOf(letter) === -1) { let errltr = letter; if (errltr==="\n") errltr = "Carriage Return"; if (errltr===" ") errltr = "Space"; throw new OperationError("Invalid ITA2 character : "+errltr); } result += letter; } else { if (validChars.indexOf(letter) === -1) throw new OperationError("Invalid Plaintext character : "+letter); if (!figShifted && figShiftedChars.indexOf(letter) !== -1) { // in letters mode and next char needs to be figure shifted figShifted = true; result += "55" + figShiftArr[letter]; } else if (figShifted) { // in figures mode and next char needs to be letter shifted if (letter==="\n") { result += "34"; } else if (letter==="\r") { result += "4"; } else if (figShiftedChars.indexOf(letter) === -1) { figShifted = false; result += "88" + letter; } else { result += figShiftArr[letter]; } } else { if (letter==="\n") { result += "34"; } else if (letter==="\r") { result += "4"; } else { result += letter; } } } } return result; } /** * Convert final result ITA2 to plaintext */ convertFromITA2(input, outtype, mode) { let result = ""; let figShifted = false; for (const letter of input) { if (mode === "Receive") { // Convert output ITA2 to plaintext (including figure/letter shifts) if (outtype === "Plaintext") { if (letter === "5" || letter === "+") { figShifted = true; } else if (letter === "8" || letter === "-") { figShifted = false; } else if (letter === "9") { result += " "; } else if (letter === "3") { result += "\n"; } else if (letter === "4") { result += ""; } else if (letter === "/") { result += "/"; } else { if (figShifted) { result += this.REVERSE_FIGSHIFT_TABLE[letter]; } else { result += letter; } } } else { result += letter; } } else { result += letter; } } return result; } } const ITA2_TABLE = { "A": "11000", "B": "10011", "C": "01110", "D": "10010", "E": "10000", "F": "10110", "G": "01011", "H": "00101", "I": "01100", "J": "11010", "K": "11110", "L": "01001", "M": "00111", "N": "00110", "O": "00011", "P": "01101", "Q": "11101", "R": "01010", "S": "10100", "T": "00001", "U": "11100", "V": "01111", "W": "11001", "X": "10111", "Y": "10101", "Z": "10001", "3": "00010", "4": "01000", "9": "00100", "/": "00000", " ": "00100", ".": "00100", "8": "11111", "5": "11011", "-": "11111", "+": "11011" }; const validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-'()/:=?,. \n\r"; const validITA2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ34589+-./"; const figShiftedChars = "1234567890+-'()/:=?,."; const figShiftArr = { "1": "Q", "2": "W", "3": "E", "4": "R", "5": "T", "6": "Y", "7": "U", "8": "I", "9": "O", "0": "P", " ": "9", "-": "A", "?": "B", ":": "C", "#": "D", "%": "F", "@": "G", "£": "H", "": "J", "(": "K", ")": "L", ".": "M", ",": "N", "'": "S", "=": "V", "/": "X", "+": "Z", "\n": "3", "\r": "4" }; const INIT_PATTERNS = { "No Pattern": { "X": { 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], 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], 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], 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], 5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, "S": { 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], 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], 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], 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], 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] }, "M": { 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], 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] } }, "KH Pattern": { "X": { 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], 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], 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], 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], 5: [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0] }, "S": { 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], 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], 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], 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], 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] }, "M": { 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], 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] } }, "ZMUG Pattern": { "X": { 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], 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], 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], 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], 5: [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1] }, "S": { 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], 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], 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], 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], 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] }, "M": { 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], 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] } }, "BREAM Pattern": { "X": { 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], 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], 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], 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], 5: [0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0] }, "S": { 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], 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], 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], 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], 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] }, "M": { 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], 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] } } }; export default Lorenz; ================================================ FILE: src/core/operations/LuhnChecksum.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @author k3ach [k3ach@proton.me] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Luhn Checksum operation */ class LuhnChecksum extends Operation { /** * LuhnChecksum constructor */ constructor() { super(); this.name = "Luhn Checksum"; this.module = "Default"; 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."; this.infoURL = "https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Radix", "type": "number", "value": 10 } ]; } /** * Generates the Luhn checksum from the input. * * @param {string} inputStr * @returns {number} */ checksum(inputStr, radix = 10) { let even = false; return inputStr.split("").reverse().reduce((acc, elem) => { // Convert element to an integer based on the provided radix. let temp = parseInt(elem, radix); // If element is not a valid number in the given radix. if (isNaN(temp)) { throw new Error("Character: " + elem + " is not valid in radix " + radix + "."); } // If element is in an even position if (even) { // Double the element and sum the quotient and remainder. temp = 2 * temp; temp = Math.floor(temp / radix) + (temp % radix); } even = !even; return acc + temp; }, 0) % radix; // Use radix as the modulus base } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!input) return ""; const radix = args[0]; if (radix < 2 || radix > 36) { throw new OperationError("Error: Radix argument must be between 2 and 36"); } if (radix % 2 !== 0) { throw new OperationError("Error: Radix argument must be divisible by 2"); } const checkSum = this.checksum(input, radix).toString(radix); let checkDigit = this.checksum(input + "0", radix); checkDigit = checkDigit === 0 ? 0 : (radix - checkDigit); checkDigit = checkDigit.toString(radix); return `Checksum: ${checkSum} Checkdigit: ${checkDigit} Luhn Validated String: ${input + "" + checkDigit}`; } } export default LuhnChecksum; ================================================ FILE: src/core/operations/MD2.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * MD2 operation */ class MD2 extends Operation { /** * MD2 constructor */ constructor() { super(); this.name = "MD2"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/MD2_(cryptography)"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Rounds", type: "number", value: 18, min: 0 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { return runHash("md2", input, {rounds: args[0]}); } } export default MD2; ================================================ FILE: src/core/operations/MD4.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * MD4 operation */ class MD4 extends Operation { /** * MD4 constructor */ constructor() { super(); this.name = "MD4"; this.module = "Crypto"; 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.

The security of MD4 has been severely compromised."; this.infoURL = "https://wikipedia.org/wiki/MD4"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { return runHash("md4", input); } } export default MD4; ================================================ FILE: src/core/operations/MD5.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * MD5 operation */ class MD5 extends Operation { /** * MD5 constructor */ constructor() { super(); this.name = "MD5"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/MD5"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { return runHash("md5", input); } } export default MD5; ================================================ FILE: src/core/operations/MD6.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import NodeMD6 from "node-md6"; /** * MD6 operation */ class MD6 extends Operation { /** * MD6 constructor */ constructor() { super(); this.name = "MD6"; this.module = "Crypto"; 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."; this.infoURL = "https://wikipedia.org/wiki/MD6"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Size", "type": "number", "value": 256 }, { "name": "Levels", "type": "number", "value": 64 }, { "name": "Key", "type": "string", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [size, levels, key] = args; if (size < 0 || size > 512) throw new OperationError("Size must be between 0 and 512"); if (levels < 0) throw new OperationError("Levels must be greater than 0"); return NodeMD6.getHashOfText(input, size, key, levels); } } export default MD6; ================================================ FILE: src/core/operations/MIMEDecoding.mjs ================================================ /** * @author mshwed [m@ttshwed.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { fromHex } from "../lib/Hex.mjs"; import { fromBase64 } from "../lib/Base64.mjs"; import cptable from "codepage"; /** * MIME Decoding operation */ class MIMEDecoding extends Operation { /** * MIMEDecoding constructor */ constructor() { super(); this.name = "MIME Decoding"; this.module = "Default"; this.description = "Enables the decoding of MIME message header extensions for non-ASCII text"; this.infoURL = "https://tools.ietf.org/html/rfc2047"; this.inputType = "byteArray"; this.outputType = "string"; this.args = []; } /** * @param {byteArray} input * @param {Object[]} args * @returns {string} */ run(input, args) { const mimeEncodedText = Utils.byteArrayToUtf8(input); const encodedHeaders = mimeEncodedText.replace(/\r\n/g, "\n"); const decodedHeader = this.decodeHeaders(encodedHeaders); return decodedHeader; } /** * Decode MIME header strings * * @param headerString */ decodeHeaders(headerString) { // No encoded words detected let i = headerString.indexOf("=?"); if (i === -1) return headerString; let decodedHeaders = headerString.slice(0, i); let header = headerString.slice(i); let isBetweenWords = false; let start, cur, charset, encoding, j, end, text; while (header.length > -1) { start = header.indexOf("=?"); if (start === -1) break; cur = start + "=?".length; i = header.slice(cur).indexOf("?"); if (i === -1) break; charset = header.slice(cur, cur + i); cur += i + "?".length; if (header.length < cur + "Q??=".length) break; encoding = header[cur]; cur += 1; if (header[cur] !== "?") break; cur += 1; j = header.slice(cur).indexOf("?="); if (j === -1) break; text = header.slice(cur, cur + j); end = cur + j + "?=".length; if (encoding.toLowerCase() === "b") { text = fromBase64(text); } else if (encoding.toLowerCase() === "q") { text = this.parseQEncodedWord(text); } else { isBetweenWords = false; decodedHeaders += header.slice(0, start + 2); header = header.slice(start + 2); } if (start > 0 && (!isBetweenWords || header.slice(0, start).search(/\S/g) > -1)) { decodedHeaders += header.slice(0, start); } decodedHeaders += this.convertFromCharset(charset, text); header = header.slice(end); isBetweenWords = true; } if (header.length > 0) { decodedHeaders += header; } return decodedHeaders; } /** * Converts decoded text for supported charsets. * Supports UTF-8, US-ASCII, ISO-8859-* * * @param encodedWord */ convertFromCharset(charset, encodedText) { charset = charset.toLowerCase(); const parsedCharset = charset.split("-"); if (parsedCharset.length === 2 && parsedCharset[0] === "utf" && charset === "utf-8") { return cptable.utils.decode(65001, encodedText); } else if (parsedCharset.length === 2 && charset === "us-ascii") { return cptable.utils.decode(20127, encodedText); } else if (parsedCharset.length === 3 && parsedCharset[0] === "iso" && parsedCharset[1] === "8859") { const isoCharset = parseInt(parsedCharset[2], 10); if (isoCharset >= 1 && isoCharset <= 16) { return cptable.utils.decode(28590 + isoCharset, encodedText); } } throw new OperationError("Unhandled Charset"); } /** * Parses a Q encoded word * * @param encodedWord */ parseQEncodedWord(encodedWord) { let decodedWord = ""; for (let i = 0; i < encodedWord.length; i++) { if (encodedWord[i] === "_") { decodedWord += " "; // Parse hex encoding } else if (encodedWord[i] === "=") { if ((i + 2) >= encodedWord.length) throw new OperationError("Incorrectly Encoded Word"); const decodedHex = Utils.byteArrayToChars(fromHex(encodedWord.substring(i + 1, i + 3))); decodedWord += decodedHex; i += 2; } else if ( (encodedWord[i].charCodeAt(0) >= " ".charCodeAt(0) && encodedWord[i].charCodeAt(0) <= "~".charCodeAt(0)) || encodedWord[i] === "\n" || encodedWord[i] === "\r" || encodedWord[i] === "\t") { decodedWord += encodedWord[i]; } else { throw new OperationError("Incorrectly Encoded Word"); } } return decodedWord; } } export default MIMEDecoding; ================================================ FILE: src/core/operations/Magic.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import Dish from "../Dish.mjs"; import MagicLib from "../lib/Magic.mjs"; /** * Magic operation */ class Magic extends Operation { /** * Magic constructor */ constructor() { super(); this.name = "Magic"; this.flowControl = true; this.module = "Default"; 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.

Options
Depth: 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.

Intensive mode: 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.

Extensive language support: 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.

Optionally enter a regular expression to match a string you expect to find to filter results (crib)."; this.infoURL = "https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic"; this.inputType = "ArrayBuffer"; this.outputType = "JSON"; this.presentType = "html"; this.args = [ { "name": "Depth", "type": "number", "value": 3 }, { "name": "Intensive mode", "type": "boolean", "value": false }, { "name": "Extensive language support", "type": "boolean", "value": false }, { "name": "Crib (known plaintext string or regex)", "type": "string", "value": "" } ]; } /** * @param {Object} state - The current state of the recipe. * @param {number} state.progress - The current position in the recipe. * @param {Dish} state.dish - The Dish being operated on. * @param {Operation[]} state.opList - The list of operations in the recipe. * @returns {Object} The updated state of the recipe. */ async run(state) { const ings = state.opList[state.progress].ingValues, [depth, intensive, extLang, crib] = ings, dish = state.dish, magic = new MagicLib(await dish.get(Dish.ARRAY_BUFFER)), cribRegex = (crib && crib.length) ? new RegExp(crib, "i") : null; let options = await magic.speculativeExecution(depth, extLang, intensive, [], false, cribRegex); // Filter down to results which matched the crib if (cribRegex) { options = options.filter(option => option.matchesCrib); } // Record the current state for use when presenting this.state = state; dish.set(options, Dish.JSON); return state; } /** * Displays Magic results in HTML for web apps. * * @param {JSON} options * @returns {html} */ present(options) { const currentRecipeConfig = this.state.opList.map(op => op.config); let output = ``; /** * Returns a CSS colour value based on an integer input. * * @param {number} val * @returns {string} */ function chooseColour(val) { if (val < 3) return "green"; if (val < 5) return "goldenrod"; return "red"; } options.forEach(option => { // Construct recipe URL // Replace this Magic op with the generated recipe const recipeConfig = currentRecipeConfig.slice(0, this.state.progress) .concat(option.recipe) .concat(currentRecipeConfig.slice(this.state.progress + 1)), recipeURL = "recipe=" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(recipeConfig)); let language = "", fileType = "", matchingOps = "", useful = ""; const entropy = `Entropy: ${option.entropy.toFixed(2)}`, validUTF8 = option.isUTF8 ? "Valid UTF8\n" : ""; if (option.languageScores[0].probability > 0) { let likelyLangs = option.languageScores.filter(l => l.probability > 0); if (likelyLangs.length < 1) likelyLangs = [option.languageScores[0]]; language = "" + "Possible languages:\n " + likelyLangs.map(lang => { return MagicLib.codeToLanguage(lang.lang); }).join("\n ") + "\n"; } if (option.fileType) { fileType = `File type: ${option.fileType.mime} (${option.fileType.ext})\n`; } if (option.matchingOps.length) { matchingOps = `Matching ops: ${[...new Set(option.matchingOps.map(op => op.op))].join(", ")}\n`; } if (option.useful) { useful = "Useful op detected\n"; } output += ``; }); output += "
Recipe (click to load) Result snippet Properties
${Utils.generatePrettyRecipe(option.recipe, true)} ${Utils.escapeHtml(Utils.escapeWhitespace(Utils.truncate(option.data, 99)))} ${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy}
"; if (!options.length) { output = "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?"; } return output; } } export default Magic; ================================================ FILE: src/core/operations/Mean.mjs ================================================ /** * @author bwhitn [brian.m.whitney@outlook.com] * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { mean, createNumArray } from "../lib/Arithmetic.mjs"; import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; import BigNumber from "bignumber.js"; /** * Mean operation */ class Mean extends Operation { /** * Mean constructor */ constructor() { super(); this.name = "Mean"; this.module = "Default"; 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.

e.g. 0x0a 8 .5 .5 becomes 4.75"; this.infoURL = "https://wikipedia.org/wiki/Arithmetic_mean"; this.inputType = "string"; this.outputType = "BigNumber"; this.args = [ { "name": "Delimiter", "type": "option", "value": ARITHMETIC_DELIM_OPTIONS, } ]; } /** * @param {string} input * @param {Object[]} args * @returns {BigNumber} */ run(input, args) { const val = mean(createNumArray(input, args[0])); return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); } } export default Mean; ================================================ FILE: src/core/operations/Median.mjs ================================================ /** * @author bwhitn [brian.m.whitney@outlook.com] * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import BigNumber from "bignumber.js"; import Operation from "../Operation.mjs"; import { median, createNumArray } from "../lib/Arithmetic.mjs"; import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; /** * Median operation */ class Median extends Operation { /** * Median constructor */ constructor() { super(); this.name = "Median"; this.module = "Default"; 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.

e.g. 0x0a 8 1 .5 becomes 4.5"; this.infoURL = "https://wikipedia.org/wiki/Median"; this.inputType = "string"; this.outputType = "BigNumber"; this.args = [ { "name": "Delimiter", "type": "option", "value": ARITHMETIC_DELIM_OPTIONS, } ]; } /** * @param {string} input * @param {Object[]} args * @returns {BigNumber} */ run(input, args) { const val = median(createNumArray(input, args[0])); return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); } } export default Median; ================================================ FILE: src/core/operations/Merge.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Merge operation */ class Merge extends Operation { /** * Merge constructor */ constructor() { super(); this.name = "Merge"; this.flowControl = true; this.module = "Default"; 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."; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Merge All", type: "boolean", value: true, } ]; } /** * @param {Object} state - The current state of the recipe. * @param {number} state.progress - The current position in the recipe. * @param {Dish} state.dish - The Dish being operated on. * @param {Operation[]} state.opList - The list of operations in the recipe. * @returns {Object} The updated state of the recipe. */ run(state) { // No need to actually do anything here. The fork operation will // merge when it sees this operation. return state; } } export default Merge; ================================================ FILE: src/core/operations/MicrosoftScriptDecoder.mjs ================================================ /** * @author bmwhitn [brian.m.whitney@outlook.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Microsoft Script Decoder operation */ class MicrosoftScriptDecoder extends Operation { /** * MicrosoftScriptDecoder constructor */ constructor() { super(); this.name = "Microsoft Script Decoder"; this.module = "Default"; 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.

Sample

Encoded:
#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&@*eEI@#@&@#@&.jm.raY 214Wv:zms/obI0xEAAA==^#~@

Decoded:
var my_msg = "Testing <1><2><3>!";\n\nVScript.Echo(my_msg);"; this.infoURL = "https://wikipedia.org/wiki/JScript.Encode"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = [ { pattern: "#@~\\^.{6}==(.+).{6}==\\^#~@", flags: "i", args: [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const matcher = /#@~\^.{6}==(.+).{6}==\^#~@/; const encodedData = matcher.exec(input); if (encodedData) { return MicrosoftScriptDecoder._decode(encodedData[1]); } else { return ""; } } /** * Decodes Microsoft Encoded Script files that can be read and executed by cscript.exe/wscript.exe. * This is a conversion of a Python script that was originally created by Didier Stevens * (https://DidierStevens.com). * * @private * @param {string} data * @returns {string} */ static _decode(data) { const result = []; let index = -1; data = data.replace(/@&/g, String.fromCharCode(10)) .replace(/@#/g, String.fromCharCode(13)) .replace(/@\*/g, ">") .replace(/@!/g, "<") .replace(/@\$/g, "@"); for (let i = 0; i < data.length; i++) { const byte = data.charCodeAt(i); let char = data.charAt(i); if (byte < 128) { index++; } if ((byte === 9 || byte > 31 && byte < 128) && byte !== 60 && byte !== 62 && byte !== 64) { char = D_DECODE[byte].charAt(D_COMBINATION[index % 64]); } result.push(char); } return result.join(""); } } const D_DECODE = [ "", "", "", "", "", "", "", "", "", "\x57\x6E\x7B", "\x4A\x4C\x41", "\x0B\x0B\x0B", "\x0C\x0C\x0C", "\x4A\x4C\x41", "\x0E\x0E\x0E", "\x0F\x0F\x0F", "\x10\x10\x10", "\x11\x11\x11", "\x12\x12\x12", "\x13\x13\x13", "\x14\x14\x14", "\x15\x15\x15", "\x16\x16\x16", "\x17\x17\x17", "\x18\x18\x18", "\x19\x19\x19", "\x1A\x1A\x1A", "\x1B\x1B\x1B", "\x1C\x1C\x1C", "\x1D\x1D\x1D", "\x1E\x1E\x1E", "\x1F\x1F\x1F", "\x2E\x2D\x32", "\x47\x75\x30", "\x7A\x52\x21", "\x56\x60\x29", "\x42\x71\x5B", "\x6A\x5E\x38", "\x2F\x49\x33", "\x26\x5C\x3D", "\x49\x62\x58", "\x41\x7D\x3A", "\x34\x29\x35", "\x32\x36\x65", "\x5B\x20\x39", "\x76\x7C\x5C", "\x72\x7A\x56", "\x43\x7F\x73", "\x38\x6B\x66", "\x39\x63\x4E", "\x70\x33\x45", "\x45\x2B\x6B", "\x68\x68\x62", "\x71\x51\x59", "\x4F\x66\x78", "\x09\x76\x5E", "\x62\x31\x7D", "\x44\x64\x4A", "\x23\x54\x6D", "\x75\x43\x71", "\x4A\x4C\x41", "\x7E\x3A\x60", "\x4A\x4C\x41", "\x5E\x7E\x53", "\x40\x4C\x40", "\x77\x45\x42", "\x4A\x2C\x27", "\x61\x2A\x48", "\x5D\x74\x72", "\x22\x27\x75", "\x4B\x37\x31", "\x6F\x44\x37", "\x4E\x79\x4D", "\x3B\x59\x52", "\x4C\x2F\x22", "\x50\x6F\x54", "\x67\x26\x6A", "\x2A\x72\x47", "\x7D\x6A\x64", "\x74\x39\x2D", "\x54\x7B\x20", "\x2B\x3F\x7F", "\x2D\x38\x2E", "\x2C\x77\x4C", "\x30\x67\x5D", "\x6E\x53\x7E", "\x6B\x47\x6C", "\x66\x34\x6F", "\x35\x78\x79", "\x25\x5D\x74", "\x21\x30\x43", "\x64\x23\x26", "\x4D\x5A\x76", "\x52\x5B\x25", "\x63\x6C\x24", "\x3F\x48\x2B", "\x7B\x55\x28", "\x78\x70\x23", "\x29\x69\x41", "\x28\x2E\x34", "\x73\x4C\x09", "\x59\x21\x2A", "\x33\x24\x44", "\x7F\x4E\x3F", "\x6D\x50\x77", "\x55\x09\x3B", "\x53\x56\x55", "\x7C\x73\x69", "\x3A\x35\x61", "\x5F\x61\x63", "\x65\x4B\x50", "\x46\x58\x67", "\x58\x3B\x51", "\x31\x57\x49", "\x69\x22\x4F", "\x6C\x6D\x46", "\x5A\x4D\x68", "\x48\x25\x7C", "\x27\x28\x36", "\x5C\x46\x70", "\x3D\x4A\x6E", "\x24\x32\x7A", "\x79\x41\x2F", "\x37\x3D\x5F", "\x60\x5F\x4B", "\x51\x4F\x5A", "\x20\x42\x2C", "\x36\x65\x57" ]; const D_COMBINATION = [ 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, 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 ]; export default MicrosoftScriptDecoder; ================================================ FILE: src/core/operations/MultipleBombe.mjs ================================================ /** * Emulation of the Bombe machine. * This version carries out multiple Bombe runs to handle unknown rotor configurations. * * @author s2224834 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { BombeMachine } from "../lib/Bombe.mjs"; import { ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector } from "../lib/Enigma.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * Convenience method for flattening the preset ROTORS object into a newline-separated string. * @param {Object[]} - Preset rotors object * @param {number} s - Start index * @param {number} n - End index * @returns {string} */ function rotorsFormat(rotors, s, n) { const res = []; for (const i of rotors.slice(s, n)) { res.push(i.value); } return res.join("\n"); } /** * Combinatorics choose function * @param {number} n * @param {number} k * @returns number */ function choose(n, k) { let res = 1; for (let i=1; i<=k; i++) { res *= (n + 1 - i) / i; } return res; } /** * Bombe operation */ class MultipleBombe extends Operation { /** * Bombe constructor */ constructor() { super(); this.name = "Multiple Bombe"; this.module = "Bletchley"; this.description = "Emulation of the Bombe machine used to attack Enigma. This version carries out multiple Bombe runs to handle unknown rotor configurations.

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.

More detailed descriptions of the Enigma, Typex and Bombe operations can be found here."; this.infoURL = "https://wikipedia.org/wiki/Bombe"; this.inputType = "string"; this.outputType = "JSON"; this.presentType = "html"; this.args = [ { "name": "Standard Enigmas", "type": "populateMultiOption", "value": [ { name: "German Service Enigma (First - 3 rotor)", value: [ rotorsFormat(ROTORS, 0, 5), "", rotorsFormat(REFLECTORS, 0, 1) ] }, { name: "German Service Enigma (Second - 3 rotor)", value: [ rotorsFormat(ROTORS, 0, 8), "", rotorsFormat(REFLECTORS, 0, 2) ] }, { name: "German Service Enigma (Third - 4 rotor)", value: [ rotorsFormat(ROTORS, 0, 8), rotorsFormat(ROTORS_FOURTH, 1, 2), rotorsFormat(REFLECTORS, 2, 3) ] }, { name: "German Service Enigma (Fourth - 4 rotor)", value: [ rotorsFormat(ROTORS, 0, 8), rotorsFormat(ROTORS_FOURTH, 1, 3), rotorsFormat(REFLECTORS, 2, 4) ] }, { name: "User defined", value: ["", "", ""] }, ], "target": [1, 2, 3] }, { name: "Main rotors", type: "text", value: "" }, { name: "4th rotor", type: "text", value: "" }, { name: "Reflectors", type: "text", value: "" }, { name: "Crib", type: "string", value: "" }, { name: "Crib offset", type: "number", value: 0 }, { name: "Use checking machine", type: "boolean", value: true } ]; } /** * Format and send a status update message. * @param {number} nLoops - Number of loops in the menu * @param {number} nStops - How many stops so far * @param {number} progress - Progress (as a float in the range 0..1) */ updateStatus(nLoops, nStops, progress, start) { const elapsed = Date.now() - start; const remaining = (elapsed / progress) * (1 - progress) / 1000; const hours = Math.floor(remaining / 3600); const minutes = `0${Math.floor((remaining % 3600) / 60)}`.slice(-2); const seconds = `0${Math.floor(remaining % 60)}`.slice(-2); const msg = `Bombe run with ${nLoops} loop${nLoops === 1 ? "" : "s"} in menu (2+ desirable): ${nStops} stops, ${Math.floor(100 * progress)}% done, ${hours}:${minutes}:${seconds} remaining`; self.sendStatusMessage(msg); } /** * Early rotor description string validation. * Drops stepping information. * @param {string} rstr - The rotor description string * @returns {string} - Rotor description with stepping stripped, if any */ validateRotor(rstr) { // The Bombe doesn't take stepping into account so we'll just ignore it here if (rstr.includes("<")) { rstr = rstr.split("<", 2)[0]; } // Duplicate the validation of the rotor strings here, otherwise you might get an error // thrown halfway into a big Bombe run if (!/^[A-Z]{26}$/.test(rstr)) { throw new OperationError("Rotor wiring must be 26 unique uppercase letters"); } if (new Set(rstr).size !== 26) { throw new OperationError("Rotor wiring must be 26 unique uppercase letters"); } return rstr; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const mainRotorsStr = args[1]; const fourthRotorsStr = args[2]; const reflectorsStr = args[3]; let crib = args[4]; const offset = args[5]; const check = args[6]; const rotors = []; const fourthRotors = []; const reflectors = []; for (let rstr of mainRotorsStr.split("\n")) { rstr = this.validateRotor(rstr); rotors.push(rstr); } if (rotors.length < 3) { throw new OperationError("A minimum of three rotors must be supplied"); } if (fourthRotorsStr !== "") { for (let rstr of fourthRotorsStr.split("\n")) { rstr = this.validateRotor(rstr); fourthRotors.push(rstr); } } if (fourthRotors.length === 0) { fourthRotors.push(""); } for (const rstr of reflectorsStr.split("\n")) { const reflector = new Reflector(rstr); reflectors.push(reflector); } if (reflectors.length === 0) { throw new OperationError("A minimum of one reflector must be supplied"); } if (crib.length === 0) { throw new OperationError("Crib cannot be empty"); } if (offset < 0) { throw new OperationError("Offset cannot be negative"); } // For symmetry with the Enigma op, for the input we'll just remove all invalid characters input = input.replace(/[^A-Za-z]/g, "").toUpperCase(); crib = crib.replace(/[^A-Za-z]/g, "").toUpperCase(); const ciphertext = input.slice(offset); let update; if (isWorkerEnvironment()) { update = this.updateStatus; } else { update = undefined; } let bombe = undefined; const output = {bombeRuns: []}; // I could use a proper combinatorics algorithm here... but it would be more code to // write one, and we don't seem to have one in our existing libraries, so massively nested // for loop it is const totalRuns = choose(rotors.length, 3) * 6 * fourthRotors.length * reflectors.length; let nRuns = 0; let nStops = 0; const start = Date.now(); for (const rotor1 of rotors) { for (const rotor2 of rotors) { if (rotor2 === rotor1) { continue; } for (const rotor3 of rotors) { if (rotor3 === rotor2 || rotor3 === rotor1) { continue; } for (const rotor4 of fourthRotors) { for (const reflector of reflectors) { nRuns++; const runRotors = [rotor1, rotor2, rotor3]; if (rotor4 !== "") { runRotors.push(rotor4); } if (bombe === undefined) { bombe = new BombeMachine(runRotors, reflector, ciphertext, crib, check); output.nLoops = bombe.nLoops; } else { bombe.changeRotors(runRotors, reflector); } const result = bombe.run(); nStops += result.length; if (update !== undefined) { update(bombe.nLoops, nStops, nRuns / totalRuns, start); } if (result.length > 0) { output.bombeRuns.push({ rotors: runRotors, reflector: reflector.pairs, result: result }); } } } } } } return output; } /** * Displays the MultiBombe results in an HTML table * * @param {Object} output * @param {number} output.nLoops * @param {Array[]} output.result * @returns {html} */ present(output) { let html = `Bombe run on menu with ${output.nLoops} loop${output.nLoops === 1 ? "" : "s"} (2+ desirable). Note: Rotors and rotor positions are listed left to right, ignore stepping and the ring setting, and positions start at the beginning of the crib. Some plugboard settings are determined. A decryption preview starting at the beginning of the crib and ignoring stepping is also provided.\n`; for (const run of output.bombeRuns) { html += `\nRotors: ${run.rotors.slice().reverse().join(", ")}\nReflector: ${run.reflector}\n`; html += "\n"; for (const [setting, stecker, decrypt] of run.result) { html += `\n`; } html += "
Rotor stops Partial plugboard Decryption preview
${setting} ${stecker} ${decrypt}
\n"; } return html; } } export default MultipleBombe; ================================================ FILE: src/core/operations/Multiply.mjs ================================================ /** * @author bwhitn [brian.m.whitney@outlook.com] * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import BigNumber from "bignumber.js"; import Operation from "../Operation.mjs"; import { multi, createNumArray } from "../lib/Arithmetic.mjs"; import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; /** * Multiply operation */ class Multiply extends Operation { /** * Multiply constructor */ constructor() { super(); this.name = "Multiply"; this.module = "Default"; this.description = "Multiplies a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 40"; this.infoURL = "https://wikipedia.org/wiki/Multiplication"; this.inputType = "string"; this.outputType = "BigNumber"; this.args = [ { "name": "Delimiter", "type": "option", "value": ARITHMETIC_DELIM_OPTIONS, } ]; } /** * @param {string} input * @param {Object[]} args * @returns {BigNumber} */ run(input, args) { const val = multi(createNumArray(input, args[0])); return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); } } export default Multiply; ================================================ FILE: src/core/operations/MurmurHash3.mjs ================================================ /** * Based on murmurhash-js (https://github.com/garycourt/murmurhash-js) * @author Gary Court * @license MIT * * @author AliceGrey [alice@grey.systems] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * MurmurHash3 operation */ class MurmurHash3 extends Operation { /** * MurmurHash3 constructor */ constructor() { super(); this.name = "MurmurHash3"; this.module = "Hashing"; this.description = "Generates a MurmurHash v3 for a string input and an optional seed input"; this.infoURL = "https://wikipedia.org/wiki/MurmurHash"; this.inputType = "string"; this.outputType = "number"; this.args = [ { name: "Seed", type: "number", value: 0 }, { name: "Convert to Signed", type: "boolean", value: false } ]; } /** * Calculates the MurmurHash3 hash of the input. * Based on Gary Court's JS MurmurHash implementation * @see http://github.com/garycourt/murmurhash-js * @author AliceGrey [alice@grey.systems] * @param {string} input ASCII only * @param {number} seed Positive integer only * @return {number} 32-bit positive integer hash */ mmh3(input, seed) { let h1b; let k1; const remainder = input.length & 3; // input.length % 4 const bytes = input.length - remainder; let h1 = seed; const c1 = 0xcc9e2d51; const c2 = 0x1b873593; let i = 0; while (i < bytes) { k1 = ((input.charCodeAt(i) & 0xff)) | ((input.charCodeAt(++i) & 0xff) << 8) | ((input.charCodeAt(++i) & 0xff) << 16) | ((input.charCodeAt(++i) & 0xff) << 24); ++i; k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; k1 = (k1 << 15) | (k1 >>> 17); k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; h1 ^= k1; h1 = (h1 << 13) | (h1 >>> 19); h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); } k1 = 0; if (remainder === 3) { k1 ^= (input.charCodeAt(i + 2) & 0xff) << 16; } if (remainder === 3 || remainder === 2) { k1 ^= (input.charCodeAt(i + 1) & 0xff) << 8; } if (remainder === 3 || remainder === 2 || remainder === 1) { k1 ^= (input.charCodeAt(i) & 0xff); k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; k1 = (k1 << 15) | (k1 >>> 17); k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; h1 ^= k1; } h1 ^= input.length; h1 ^= h1 >>> 16; h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; h1 ^= h1 >>> 13; h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; h1 ^= h1 >>> 16; return h1 >>> 0; } /** * Converts an unsigned 32-bit integer to a signed 32-bit integer * @author AliceGrey [alice@grey.systems] * @param {value} 32-bit unsigned integer * @return {number} 32-bit signed integer */ unsignedToSigned(value) { return value & 0x80000000 ? -0x100000000 + value : value; } /** * @param {string} input * @param {Object[]} args * @returns {number} */ run(input, args) { if (args && args.length >= 1) { const seed = args[0]; const hash = this.mmh3(input, seed); if (args.length > 1 && args[1]) { return this.unsignedToSigned(hash); } return hash; } return this.mmh3(input); } } export default MurmurHash3; ================================================ FILE: src/core/operations/NOT.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { bitOp, not } from "../lib/BitwiseOp.mjs"; /** * NOT operation */ class NOT extends Operation { /** * NOT constructor */ constructor() { super(); this.name = "NOT"; this.module = "Default"; this.description = "Returns the inverse of each byte."; this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#NOT"; this.inputType = "ArrayBuffer"; this.outputType = "byteArray"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { input = new Uint8Array(input); return bitOp(input, null, not); } /** * Highlight NOT * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight NOT in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default NOT; ================================================ FILE: src/core/operations/NTHash.mjs ================================================ /** * @author brun0ne [brunonblok@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * NT Hash operation */ class NTHash extends Operation { /** * NTHash constructor */ constructor() { super(); this.name = "NT Hash"; this.module = "Crypto"; 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."; this.infoURL = "https://wikipedia.org/wiki/NT_LAN_Manager"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { // Convert to UTF-16LE const buf = new ArrayBuffer(input.length * 2); const bufView = new Uint16Array(buf); for (let i = 0; i < input.length; i++) { bufView[i] = input.charCodeAt(i); } const hashed = runHash("md4", buf); return hashed.toUpperCase(); } } export default NTHash; ================================================ FILE: src/core/operations/NormaliseImage.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Normalise Image operation */ class NormaliseImage extends Operation { /** * NormaliseImage constructor */ constructor() { super(); this.name = "Normalise Image"; this.module = "Image"; this.description = "Normalise the image colours."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { if (!isImage(input)) { throw new OperationError("Invalid file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error opening image file. (${err})`); } try { image.normalize(); let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error normalising image. (${err})`); } } /** * Displays the normalised image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default NormaliseImage; ================================================ FILE: src/core/operations/NormaliseUnicode.mjs ================================================ /** * @author Matthieu [m@tthieu.xyz] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import {UNICODE_NORMALISATION_FORMS} from "../lib/ChrEnc.mjs"; import unorm from "unorm"; /** * Normalise Unicode operation */ class NormaliseUnicode extends Operation { /** * NormaliseUnicode constructor */ constructor() { super(); this.name = "Normalise Unicode"; this.module = "Encodings"; this.description = "Transform Unicode characters to one of the Normalisation Forms"; this.infoURL = "https://wikipedia.org/wiki/Unicode_equivalence#Normal_forms"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Normal Form", type: "option", value: UNICODE_NORMALISATION_FORMS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [normalForm] = args; switch (normalForm) { case "NFD": return unorm.nfd(input); case "NFC": return unorm.nfc(input); case "NFKD": return unorm.nfkd(input); case "NFKC": return unorm.nfkc(input); default: throw new OperationError("Unknown Normalisation Form"); } } } export default NormaliseUnicode; ================================================ FILE: src/core/operations/Numberwang.mjs ================================================ /** * @author Unknown Male 282 * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Numberwang operation. Remain indoors. */ class Numberwang extends Operation { /** * Numberwang constructor */ constructor() { super(); this.name = "Numberwang"; this.module = "Default"; this.description = "Based on the popular gameshow by Mitchell and Webb."; this.infoURL = "https://wikipedia.org/wiki/That_Mitchell_and_Webb_Look#Recurring_sketches"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let output; if (!input) { output = "Let's play Wangernumb!"; } else { const match = input.match(/(f0rty-s1x|shinty-six|filth-hundred and neeb|-?√?\d+(\.\d+)?i?([a-z]?)%?)/i); if (match) { if (match[3]) output = match[0] + "! That's AlphaNumericWang!"; else output = match[0] + "! That's Numberwang!"; } else { // That's a bad miss! output = "Sorry, that's not Numberwang. Let's rotate the board!"; } } const rand = Math.floor(Math.random() * didYouKnow.length); return output + "\n\nDid you know: " + didYouKnow[rand]; } } /** * Taken from http://numberwang.wikia.com/wiki/Numberwang_Wikia * * @constant */ const didYouKnow = [ "Numberwang, contrary to popular belief, is a fruit and not a vegetable.", "Robert Webb once got WordWang while presenting an episode of Numberwang.", "The 6705th digit of pi is Numberwang.", "Numberwang was invented on a Sevenday.", "Contrary to popular belief, Albert Einstein always got good grades in Numberwang at school. He once scored ^4$ on a test.", "680 asteroids have been named after Numberwang.", "Archimedes is most famous for proclaiming \"That's Numberwang!\" during an epiphany about water displacement he had while taking a bath.", "Numberwang Day is celebrated in Japan on every day of the year apart from June 6.", "Biologists recently discovered Numberwang within a strand of human DNA.", "Numbernot is a special type of non-Numberwang number. It is divisible by 3 and the letter \"y\".", "Julie once got 612.04 Numberwangs in a single episode of Emmerdale.", "In India, it is traditional to shout out \"Numberwang!\" instead of checkmate during games of chess.", "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.", "\"Numberwang\" was the third-most common baby name for a brief period in 1722.", "\"The Lion King\" was loosely based on Numberwang.", "\"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.", "The \"number lock\" button on a keyboard is based on the popular round of the same name in \"Numberwang\".", "Cambridge became the first university to offer a course in Numberwang in 1567.", "Schrödinger's Numberwang is a number that has been confusing dentists for centuries.", "\"Harry Potter and the Numberwang of Numberwang\" was rejected by publishers -41 times before it became a bestseller.", "\"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.", "The triple Numberwang bonus was discovered by archaeologist Thomas Jefferson in Somerset.", "Numberwang is illegal in parts of Czechoslovakia.", "Numberwang was discovered in India in the 12th century.", "Numberwang has the chemical formula Zn4SO2(HgEs)3.", "The first pack of cards ever created featured two \"Numberwang\" cards instead of jokers.", "Julius Caesar was killed by an overdose of Numberwang.", "The most Numberwang musical note is G#.", "In 1934, the forty-third Google Doodle promoted the upcoming television show \"Numberwang on Ice\".", "A recent psychology study found that toddlers were 17% faster at identifying numbers which were Numberwang.", "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.", "Astronomers suspect God is Numberwang.", "Numberwang is the official beverage of Canada.", "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.", "The first person to get three Numberwangs in a row was Madonna.", "\"Numberwang\" has the code U+46402 in Unicode.", "The musical note \"Numberwang\" is between D# and E♮.", "Numberwang was first played on the moon in 1834.", ]; export default Numberwang; ================================================ FILE: src/core/operations/OR.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { bitOp, or, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; /** * OR operation */ class OR extends Operation { /** * OR constructor */ constructor() { super(); this.name = "OR"; this.module = "Default"; this.description = "OR the input with the given key.
e.g. fe023da5"; this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#OR"; this.inputType = "ArrayBuffer"; this.outputType = "byteArray"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": BITWISE_OP_DELIMS } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string || "", args[0].option); input = new Uint8Array(input); return bitOp(input, key, or); } /** * Highlight OR * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight OR in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default OR; ================================================ FILE: src/core/operations/ObjectIdentifierToHex.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; /** * Object Identifier to Hex operation */ class ObjectIdentifierToHex extends Operation { /** * ObjectIdentifierToHex constructor */ constructor() { super(); this.name = "Object Identifier to Hex"; this.module = "PublicKey"; this.description = "Converts an object identifier (OID) into a hexadecimal string."; this.infoURL = "https://wikipedia.org/wiki/Object_identifier"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return r.KJUR.asn1.ASN1Util.oidIntToHex(input); } } export default ObjectIdentifierToHex; ================================================ FILE: src/core/operations/OffsetChecker.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Offset checker operation */ class OffsetChecker extends Operation { /** * OffsetChecker constructor */ constructor() { super(); this.name = "Offset checker"; this.module = "Default"; this.description = "Compares multiple inputs (separated by the specified delimiter) and highlights matching characters which appear at the same position in all samples."; this.inputType = "string"; this.outputType = "html"; this.args = [ { "name": "Sample delimiter", "type": "binaryString", "value": "\\n\\n" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const sampleDelim = args[0], samples = input.split(sampleDelim), outputs = new Array(samples.length); let i = 0, s = 0, match = false, inMatch = false, chr; if (!samples || samples.length < 2) { throw new OperationError("Not enough samples, perhaps you need to modify the sample delimiter or add more data?"); } // Initialise output strings outputs.fill("", 0, samples.length); // Loop through each character in the first sample for (i = 0; i < samples[0].length; i++) { chr = samples[0][i]; match = false; // Loop through each sample to see if the chars are the same for (s = 1; s < samples.length; s++) { if (samples[s][i] !== chr) { match = false; break; } match = true; } // Write output for each sample for (s = 0; s < samples.length; s++) { if (samples[s].length <= i) { if (inMatch) outputs[s] += ""; if (s === samples.length - 1) inMatch = false; continue; } if (match && !inMatch) { outputs[s] += "" + Utils.escapeHtml(samples[s][i]); if (samples[s].length === i + 1) outputs[s] += ""; if (s === samples.length - 1) inMatch = true; } else if (!match && inMatch) { outputs[s] += "" + Utils.escapeHtml(samples[s][i]); if (s === samples.length - 1) inMatch = false; } else { outputs[s] += Utils.escapeHtml(samples[s][i]); if (inMatch && samples[s].length === i + 1) { outputs[s] += ""; if (samples[s].length - 1 !== i) inMatch = false; } } if (samples[0].length - 1 === i) { if (inMatch) outputs[s] += ""; outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1)); } } } return outputs.join(sampleDelim); } } export default OffsetChecker; ================================================ FILE: src/core/operations/OpticalCharacterRecognition.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author mshwed [m@ttshwed.com] * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { createWorker } from "tesseract.js"; const OEM_MODES = ["Tesseract only", "LSTM only", "Tesseract/LSTM Combined"]; /** * Optical Character Recognition operation */ class OpticalCharacterRecognition extends Operation { /** * OpticalCharacterRecognition constructor */ constructor() { super(); this.name = "Optical Character Recognition"; this.module = "OCR"; 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.

Supported image formats: png, jpg, bmp, pbm."; this.infoURL = "https://wikipedia.org/wiki/Optical_character_recognition"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Show confidence", type: "boolean", value: true }, { name: "OCR Engine Mode", type: "option", value: OEM_MODES, defaultIndex: 1 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [showConfidence, oemChoice] = args; if (!isWorkerEnvironment()) throw new OperationError("This operation only works in a browser"); const type = isImage(input); if (!type) { throw new OperationError("Unsupported file type (supported: jpg,png,pbm,bmp) or no file provided"); } const assetDir = `${self.docURL}/assets/`; const oem = OEM_MODES.indexOf(oemChoice); try { self.sendStatusMessage("Spinning up Tesseract worker..."); const image = `data:${type};base64,${toBase64(input)}`; const worker = await createWorker("eng", oem, { workerPath: `${assetDir}tesseract/worker.min.js`, langPath: `${assetDir}tesseract/lang-data`, corePath: `${assetDir}tesseract/tesseract-core.wasm.js`, logger: progress => { if (isWorkerEnvironment()) { self.sendStatusMessage(`Status: ${progress.status}${progress.status === "recognizing text" ? ` - ${(parseFloat(progress.progress)*100).toFixed(2)}%`: "" }`); } } }); self.sendStatusMessage("Finding text..."); const result = await worker.recognize(image); if (showConfidence) { return `Confidence: ${result.data.confidence}%\n\n${result.data.text}`; } else { return result.data.text; } } catch (err) { throw new OperationError(`Error performing OCR on image. (${err})`); } } } export default OpticalCharacterRecognition; ================================================ FILE: src/core/operations/PEMToHex.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author cplussharp * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import { fromBase64 } from "../lib/Base64.mjs"; import { toHexFast } from "../lib/Hex.mjs"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * PEM to Hex operation */ class PEMToHex extends Operation { /** * PEMToHex constructor */ constructor() { super(); this.name = "PEM to Hex"; this.module = "Default"; this.description = "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string."; this.infoURL = "https://wikipedia.org/wiki/Privacy-Enhanced_Mail#Format"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = [ { "pattern": "----BEGIN ([A-Z][A-Z ]+[A-Z])-----", "args": [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const output = []; let match; const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g; while ((match = regex.exec(input)) !== null) { // find corresponding end tag const indexBase64 = match.index + match[0].length; const footer = `-----END ${match[1]}-----`; const indexFooter = input.indexOf(footer, indexBase64); if (indexFooter === -1) { throw new OperationError(`PEM footer '${footer}' not found`); } // decode base64 content const base64 = input.substring(indexBase64, indexFooter); const bytes = fromBase64(base64, "A-Za-z0-9+/=", "byteArray", true); const hex = toHexFast(bytes); output.push(hex); } return output.join("\n"); } } export default PEMToHex; ================================================ FILE: src/core/operations/PEMToJWK.mjs ================================================ /** * @author cplussharp * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * PEM to JWK operation */ class PEMToJWK extends Operation { /** * PEMToJWK constructor */ constructor() { super(); this.name = "PEM to JWK"; this.module = "PublicKey"; this.description = "Converts Keys in PEM format to a JSON Web Key format."; this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = [ { "pattern": "-----BEGIN ((RSA |EC )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-----", "args": [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let output = ""; let match; const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g; while ((match = regex.exec(input)) !== null) { // find corresponding end tag const indexBase64 = match.index + match[0].length; const header = input.substring(match.index, indexBase64); const footer = `-----END ${match[1]}-----`; const indexFooter = input.indexOf(footer, indexBase64); if (indexFooter === -1) { throw new OperationError(`PEM footer '${footer}' not found`); } const pem = input.substring(match.index, indexFooter + footer.length); if (match[1].indexOf("KEY") !== -1) { if (header === "-----BEGIN RSA PUBLIC KEY-----") { throw new OperationError("Unsupported RSA public key format. Only PKCS#8 is supported."); } const key = r.KEYUTIL.getKey(pem); if (key.type === "DSA") { throw new OperationError("DSA keys are not supported for JWK"); } const jwk = r.KEYUTIL.getJWKFromKey(key); if (output.length > 0) { output += "\n"; } output += JSON.stringify(jwk); } else if (match[1] === "CERTIFICATE") { const cert = new r.X509(); cert.readCertPEM(pem); const key = cert.getPublicKey(); const jwk = r.KEYUTIL.getJWKFromKey(key); if (output.length > 0) { output += "\n"; } output += JSON.stringify(jwk); } else { throw new OperationError(`Unsupported PEM type '${match[1]}'`); } } return output; } } export default PEMToJWK; ================================================ FILE: src/core/operations/PGPDecrypt.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import kbpgp from "kbpgp"; import { ASP, importPrivateKey } from "../lib/PGP.mjs"; import OperationError from "../errors/OperationError.mjs"; import * as es6promisify from "es6-promisify"; const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; /** * PGP Decrypt operation */ class PGPDecrypt extends Operation { /** * PGPDecrypt constructor */ constructor() { super(); this.name = "PGP Decrypt"; this.module = "PGP"; this.description = [ "Input: the ASCII-armoured PGP message you want to decrypt.", "

", "Arguments: the ASCII-armoured PGP private key of the recipient, ", "(and the private key password if necessary).", "

", "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", "

", "This function uses the Keybase implementation of PGP.", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Private key of recipient", "type": "text", "value": "" }, { "name": "Private key passphrase", "type": "string", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} * * @throws {OperationError} if invalid private key */ async run(input, args) { const encryptedMessage = input, [privateKey, passphrase] = args, keyring = new kbpgp.keyring.KeyRing(); let plaintextMessage; if (!privateKey) throw new OperationError("Enter the private key of the recipient."); const key = await importPrivateKey(privateKey, passphrase); keyring.add_key_manager(key); try { plaintextMessage = await promisify(kbpgp.unbox)({ armored: encryptedMessage, keyfetch: keyring, asp: ASP }); } catch (err) { throw new OperationError(`Couldn't decrypt message with provided private key: ${err}`); } return plaintextMessage.toString(); } } export default PGPDecrypt; ================================================ FILE: src/core/operations/PGPDecryptAndVerify.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import kbpgp from "kbpgp"; import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP.mjs"; import OperationError from "../errors/OperationError.mjs"; import * as es6promisify from "es6-promisify"; const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; /** * PGP Decrypt and Verify operation */ class PGPDecryptAndVerify extends Operation { /** * PGPDecryptAndVerify constructor */ constructor() { super(); this.name = "PGP Decrypt and Verify"; this.module = "PGP"; this.description = [ "Input: the ASCII-armoured encrypted PGP message you want to verify.", "

", "Arguments: the ASCII-armoured PGP public key of the signer, ", "the ASCII-armoured private key of the recipient (and the private key password if necessary).", "

", "This operation uses PGP to decrypt and verify an encrypted digital signature.", "

", "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", "

", "This function uses the Keybase implementation of PGP.", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Public key of signer", "type": "text", "value": "" }, { "name": "Private key of recipient", "type": "text", "value": "" }, { "name": "Private key password", "type": "string", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const signedMessage = input, [publicKey, privateKey, passphrase] = args, keyring = new kbpgp.keyring.KeyRing(); let unboxedLiterals; if (!publicKey) throw new OperationError("Enter the public key of the signer."); if (!privateKey) throw new OperationError("Enter the private key of the recipient."); const privKey = await importPrivateKey(privateKey, passphrase); const pubKey = await importPublicKey(publicKey); keyring.add_key_manager(privKey); keyring.add_key_manager(pubKey); try { unboxedLiterals = await promisify(kbpgp.unbox)({ armored: signedMessage, keyfetch: keyring, asp: ASP }); const ds = unboxedLiterals[0].get_data_signer(); if (ds) { const km = ds.get_key_manager(); if (km) { const signer = km.get_userids_mark_primary()[0].components; let text = "Signed by "; if (signer.email || signer.username || signer.comment) { if (signer.username) { text += `${signer.username} `; } if (signer.comment) { text += `(${signer.comment}) `; } if (signer.email) { text += `<${signer.email}>`; } text += "\n"; } text += [ `PGP key ID: ${km.get_pgp_short_key_id()}`, `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, `Signed on ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`, "----------------------------------\n" ].join("\n"); text += unboxedLiterals.toString(); return text.trim(); } else { throw new OperationError("Could not identify a key manager."); } } else { throw new OperationError("The data does not appear to be signed."); } } catch (err) { throw new OperationError(`Couldn't verify message: ${err}`); } } } export default PGPDecryptAndVerify; ================================================ FILE: src/core/operations/PGPEncrypt.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import kbpgp from "kbpgp"; import { ASP, importPublicKey } from "../lib/PGP.mjs"; import OperationError from "../errors/OperationError.mjs"; import * as es6promisify from "es6-promisify"; const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; /** * PGP Encrypt operation */ class PGPEncrypt extends Operation { /** * PGPEncrypt constructor */ constructor() { super(); this.name = "PGP Encrypt"; this.module = "PGP"; this.description = [ "Input: the message you want to encrypt.", "

", "Arguments: the ASCII-armoured PGP public key of the recipient.", "

", "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", "

", "This function uses the Keybase implementation of PGP.", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Public key of recipient", "type": "text", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} * * @throws {OperationError} if failed private key import or failed encryption */ async run(input, args) { const plaintextMessage = input, plainPubKey = args[0]; let encryptedMessage; if (!plainPubKey) throw new OperationError("Enter the public key of the recipient."); const key = await importPublicKey(plainPubKey); try { encryptedMessage = await promisify(kbpgp.box)({ "msg": plaintextMessage, "encrypt_for": key, "asp": ASP }); } catch (err) { throw new OperationError(`Couldn't encrypt message with provided public key: ${err}`); } return encryptedMessage.toString(); } } export default PGPEncrypt; ================================================ FILE: src/core/operations/PGPEncryptAndSign.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import kbpgp from "kbpgp"; import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP.mjs"; import OperationError from "../errors/OperationError.mjs"; import * as es6promisify from "es6-promisify"; const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; /** * PGP Encrypt and Sign operation */ class PGPEncryptAndSign extends Operation { /** * PGPEncryptAndSign constructor */ constructor() { super(); this.name = "PGP Encrypt and Sign"; this.module = "PGP"; this.description = [ "Input: the cleartext you want to sign.", "

", "Arguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)", "and the ASCII-armoured PGP public key of the recipient.", "

", "This operation uses PGP to produce an encrypted digital signature.", "

", "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", "

", "This function uses the Keybase implementation of PGP.", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Private key of signer", "type": "text", "value": "" }, { "name": "Private key passphrase", "type": "string", "value": "" }, { "name": "Public key of recipient", "type": "text", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} * * @throws {OperationError} if failure to sign message */ async run(input, args) { const message = input, [privateKey, passphrase, publicKey] = args; let signedMessage; if (!privateKey) throw new OperationError("Enter the private key of the signer."); if (!publicKey) throw new OperationError("Enter the public key of the recipient."); const privKey = await importPrivateKey(privateKey, passphrase); const pubKey = await importPublicKey(publicKey); try { signedMessage = await promisify(kbpgp.box)({ "msg": message, "encrypt_for": pubKey, "sign_with": privKey, "asp": ASP }); } catch (err) { throw new OperationError(`Couldn't sign message: ${err}`); } return signedMessage; } } export default PGPEncryptAndSign; ================================================ FILE: src/core/operations/PGPVerify.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import kbpgp from "kbpgp"; import { ASP, importPublicKey } from "../lib/PGP.mjs"; import * as es6promisify from "es6-promisify"; const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; /** * PGP Verify operation */ class PGPVerify extends Operation { /** * PGPVerify constructor */ constructor() { super(); this.name = "PGP Verify"; this.module = "PGP"; this.description = [ "Input: the ASCII-armoured encrypted PGP message you want to verify.", "

", "Argument: the ASCII-armoured PGP public key of the signer", "

", "This operation uses PGP to decrypt a clearsigned message.", "

", "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", "

", "This function uses the Keybase implementation of PGP.", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Public key of signer", "type": "text", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const signedMessage = input, [publicKey] = args, keyring = new kbpgp.keyring.KeyRing(); let unboxedLiterals; if (!publicKey) throw new OperationError("Enter the public key of the signer."); const pubKey = await importPublicKey(publicKey); keyring.add_key_manager(pubKey); try { unboxedLiterals = await promisify(kbpgp.unbox)({ armored: signedMessage, keyfetch: keyring, asp: ASP }); const ds = unboxedLiterals[0].get_data_signer(); if (ds) { const km = ds.get_key_manager(); if (km) { const signer = km.get_userids_mark_primary()[0].components; let text = "Signed by "; if (signer.email || signer.username || signer.comment) { if (signer.username) { text += `${signer.username} `; } if (signer.comment) { text += `(${signer.comment}) `; } if (signer.email) { text += `<${signer.email}>`; } text += "\n"; } text += [ `PGP key ID: ${km.get_pgp_short_key_id()}`, `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, `Signed on ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`, "----------------------------------\n" ].join("\n"); text += unboxedLiterals.toString(); return text.trim(); } else { throw new OperationError("Could not identify a key manager."); } } else { throw new OperationError("The data does not appear to be signed."); } } catch (err) { throw new OperationError(`Couldn't verify message: ${err}`); } } } export default PGPVerify; ================================================ FILE: src/core/operations/PHPDeserialize.mjs ================================================ /** * @author Jarmo van Lenthe [github.com/jarmovanlenthe] * @copyright Jarmo van Lenthe * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * PHP Deserialize operation */ class PHPDeserialize extends Operation { /** * PHPDeserialize constructor */ constructor() { super(); this.name = "PHP Deserialize"; this.module = "Default"; this.description = "Deserializes PHP serialized data, outputting keyed arrays as JSON.

This function does not support object tags.

Example:
a:2:{s:1:"a";i:10;i:0;a:1:{s:2:"ab";b:1;}}
becomes
{"a": 10,0: {"ab": true}}

Output valid JSON: JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes."; this.infoURL = "http://www.phpinternalsbook.com/classes_objects/serialization.html"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Output valid JSON", "type": "boolean", "value": true } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { /** * Recursive method for deserializing. * @returns {*} */ function handleInput() { /** * Read `length` characters from the input, shifting them out the input. * @param length * @returns {string} */ function read(length) { let result = ""; for (let idx = 0; idx < length; idx++) { const char = inputPart.shift(); if (char === undefined) { throw new OperationError("End of input reached before end of script"); } result += char; } return result; } /** * Read characters from the input until `until` is found. * @param until * @returns {string} */ function readUntil(until) { let result = ""; for (;;) { const char = read(1); if (char === until) { break; } else { result += char; } } return result; } /** * Read characters from the input that must be equal to `expect` * @param expect * @returns {string} */ function expect(expect) { const result = read(expect.length); if (result !== expect) { throw new OperationError("Unexpected input found"); } return result; } /** * Helper function to handle deserialized arrays. * @returns {Array} */ function handleArray() { const items = parseInt(readUntil(":"), 10) * 2; expect("{"); const result = []; let isKey = true; let lastItem = null; for (let idx = 0; idx < items; idx++) { const item = handleInput(); if (isKey) { lastItem = item; isKey = false; } else { const numberCheck = lastItem.match(/[0-9]+/); if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) { result.push('"' + lastItem + '": ' + item); } else { result.push(lastItem + ": " + item); } isKey = true; } } expect("}"); return result; } const kind = read(1).toLowerCase(); switch (kind) { case "n": expect(";"); return "null"; case "i": case "d": case "b": { expect(":"); const data = readUntil(";"); if (kind === "b") { return (parseInt(data, 10) !== 0); } return data; } case "a": expect(":"); return "{" + handleArray() + "}"; case "s": { expect(":"); const length = readUntil(":"); expect("\""); const value = read(length); expect('";'); if (args[0]) { return '"' + value.replace(/"/g, '\\"') + '"'; // lgtm [js/incomplete-sanitization] } else { return '"' + value + '"'; } } default: throw new OperationError("Unknown type: " + kind); } } const inputPart = input.split(""); return handleInput(); } } export default PHPDeserialize; ================================================ FILE: src/core/operations/PHPSerialize.mjs ================================================ /** * @author brun0ne [brunonblok@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * PHP Serialize operation */ class PHPSerialize extends Operation { /** * PHPSerialize constructor */ constructor() { super(); this.name = "PHP Serialize"; this.module = "Default"; this.description = "Performs PHP serialization on JSON data.

This function does not support object tags.

Since PHP doesn't distinguish dicts and arrays, this operation is not always symmetric to PHP Deserialize.

Example:
[5,"abc",true]
becomes
a:3:{i:0;i:5;i:1;s:3:"abc";i:2;b:1;}"; this.infoURL = "https://www.phpinternalsbook.com/php5/classes_objects/serialization.html"; this.inputType = "JSON"; this.outputType = "string"; this.args = []; } /** * @param {JSON} input * @param {Object[]} args * @returns {string} */ run(input, args) { /** * Determines if a number is an integer * @param {number} value * @returns {boolean} */ function isInteger(value) { return typeof value === "number" && parseInt(value.toString(), 10) === value; } /** * Serialize basic types * @param {string | number | boolean} content * @returns {string} */ function serializeBasicTypes(content) { const basicTypes = { "string": "s", "integer": "i", "float": "d", "boolean": "b" }; /** * Booleans * cast to 0 or 1 */ if (typeof content === "boolean") { return `${basicTypes.boolean}:${content ? 1 : 0}`; } /* Numbers */ if (typeof content === "number") { if (isInteger(content)) { return `${basicTypes.integer}:${content.toString()}`; } else { return `${basicTypes.float}:${content.toString()}`; } } /* Strings */ if (typeof content === "string") return `${basicTypes.string}:${content.length}:"${content}"`; /** This should be unreachable */ throw new OperationError(`Encountered a non-implemented type: ${typeof content}`); } /** * Recursively serialize * @param {*} object * @returns {string} */ function serialize(object) { /* Null */ if (object == null) { return `N;`; } if (typeof object !== "object") { /* Basic types */ return `${serializeBasicTypes(object)};`; } else if (object instanceof Array) { /* Arrays */ const serializedElements = []; for (let i = 0; i < object.length; i++) { serializedElements.push(`${serialize(i)}${serialize(object[i])}`); } return `a:${object.length}:{${serializedElements.join("")}}`; } else if (object instanceof Object) { /** * Objects * Note: the output cannot be guaranteed to be in the same order as the input */ const serializedElements = []; const keys = Object.keys(object); for (const key of keys) { serializedElements.push(`${serialize(key)}${serialize(object[key])}`); } return `a:${keys.length}:{${serializedElements.join("")}}`; } /** This should be unreachable */ throw new OperationError(`Encountered a non-implemented type: ${typeof object}`); } return serialize(input); } } export default PHPSerialize; ================================================ FILE: src/core/operations/PLISTViewer.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * P-list Viewer operation */ class PlistViewer extends Operation { /** * PlistViewer constructor */ constructor() { super(); this.name = "P-list Viewer"; this.module = "Default"; 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.

This operation displays plist files in a human readable format."; this.infoURL = "https://wikipedia.org/wiki/Property_list"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { // Regexes are designed to transform the xml format into a more readable string format. input = input.slice(input.indexOf("/g, "plist => ") .replace(//g, "{") .replace(/<\/dict>/g, "}") .replace(//g, "[") .replace(/<\/array>/g, "]") .replace(/.+<\/key>/g, m => `${m.slice(5, m.indexOf(/<\/key>/g)-5)}\t=> `) .replace(/.+<\/real>/g, m => `${m.slice(6, m.indexOf(/<\/real>/g)-6)}\n`) .replace(/.+<\/string>/g, m => `"${m.slice(8, m.indexOf(/<\/string>/g)-8)}"\n`) .replace(/.+<\/integer>/g, m => `${m.slice(9, m.indexOf(/<\/integer>/g)-9)}\n`) .replace(//g, m => "false") .replace(//g, m => "true") .replace(/<\/plist>/g, "/plist") .replace(/.+<\/date>/g, m => `${m.slice(6, m.indexOf(/<\/integer>/g)-6)}`) .replace(/[\s\S]+?<\/data>/g, m => `${m.slice(6, m.indexOf(/<\/data>/g)-6)}`) .replace(/[ \t\r\f\v]/g, ""); /** * Depending on the type of brace, it will increment the depth and amount of arrays accordingly. * * @param {string} elem * @param {array} vals * @param {number} offset */ function braces(elem, vals, offset) { const temp = vals.indexOf(elem); if (temp !== -1) { depthCount += offset; if (temp === 1) arrCount += offset; } } let result = ""; let arrCount = 0; let depthCount = 0; /** * Formats the input after the regex has replaced all of the relevant parts. * * @param {array} input * @param {number} index */ function printIt(input, index) { if (!(input.length)) return; let temp = ""; const origArr = arrCount; let currElem = input[0]; // If the current position points at a larger dynamic structure. if (currElem.indexOf("=>") !== -1) { // If the LHS also points at a larger structure (nested plists in a dictionary). if (input[1].indexOf("=>") !== -1) temp = currElem.slice(0, -2) + " => " + input[1].slice(0, -2) + " =>\n"; else temp = currElem.slice(0, -2) + " => " + input[1] + "\n"; input = input.slice(1); } else { // Controls the tab depth for how many closing braces there have been. braces(currElem, ["}", "]"], -1); // Has to be here since the formatting breaks otherwise. temp = currElem + "\n"; } currElem = input[0]; // Tab out to the correct distance. result += ("\t".repeat(depthCount)); // If it is enclosed in an array show index. if (arrCount > 0 && currElem !== "]") result += index.toString() + " => "; result += temp; // Controls the tab depth for how many opening braces there have been. braces(currElem, ["{", "["], 1); // If there has been a new array then reset index. if (arrCount > origArr) return printIt(input.slice(1), 0); return printIt(input.slice(1), ++index); } input = input.split("\n").filter(e => e !== ""); printIt(input, 0); return result; } } export default PlistViewer; ================================================ FILE: src/core/operations/PadLines.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Pad lines operation */ class PadLines extends Operation { /** * PadLines constructor */ constructor() { super(); this.name = "Pad lines"; this.module = "Default"; this.description = "Add the specified number of the specified character to the beginning or end of each line"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Position", "type": "option", "value": ["Start", "End"] }, { "name": "Length", "type": "number", "value": 5 }, { "name": "Character", "type": "binaryShortString", "value": " " } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [position, len, chr] = args, lines = input.split("\n"); let output = "", i = 0; if (position === "Start") { for (i = 0; i < lines.length; i++) { output += lines[i].padStart(lines[i].length+len, chr) + "\n"; } } else if (position === "End") { for (i = 0; i < lines.length; i++) { output += lines[i].padEnd(lines[i].length+len, chr) + "\n"; } } return output.slice(0, output.length-1); } } export default PadLines; ================================================ FILE: src/core/operations/ParseASN1HexString.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; /** * Parse ASN.1 hex string operation */ class ParseASN1HexString extends Operation { /** * ParseASN1HexString constructor */ constructor() { super(); this.name = "Parse ASN.1 hex string"; this.module = "PublicKey"; 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.

This operation parses arbitrary ASN.1 data (encoded as an hex string: use the 'To Hex' operation if necessary) and presents the resulting tree."; this.infoURL = "https://wikipedia.org/wiki/Abstract_Syntax_Notation_One"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Starting index", "type": "number", "value": 0 }, { "name": "Truncate octet strings longer than", "type": "number", "value": 32 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [index, truncateLen] = args; return r.ASN1HEX.dump(input.replace(/\s/g, "").toLowerCase(), { "ommit_long_octet": truncateLen }, index); } } export default ParseASN1HexString; ================================================ FILE: src/core/operations/ParseCSR.mjs ================================================ /** * @author jkataja * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; import { formatDnObj } from "../lib/PublicKey.mjs"; import Utils from "../Utils.mjs"; /** * Parse CSR operation */ class ParseCSR extends Operation { /** * ParseCSR constructor */ constructor() { super(); this.name = "Parse CSR"; this.module = "PublicKey"; this.description = "Parse Certificate Signing Request (CSR) for an X.509 certificate"; this.infoURL = "https://wikipedia.org/wiki/Certificate_signing_request"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Input format", "type": "option", "value": ["PEM"] } ]; this.checks = [ { "pattern": "^-+BEGIN CERTIFICATE REQUEST-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE REQUEST-+\\r?\\n?$", "flags": "i", "args": ["PEM"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} Human-readable description of a Certificate Signing Request (CSR). */ run(input, args) { if (!input.length) { return "No input"; } // Parse the CSR into JSON parameters const csrParam = new r.KJUR.asn1.csr.CSRUtil.getParam(input); return `Subject\n${formatDnObj(csrParam.subject, 2)} Public Key${formatSubjectPublicKey(csrParam.sbjpubkey)} Signature${formatSignature(csrParam.sigalg, csrParam.sighex)} Requested Extensions${formatRequestedExtensions(csrParam)}`; } } /** * Format signature of a CSR * @param {*} sigAlg string * @param {*} sigHex string * @returns Multi-line string describing CSR Signature */ function formatSignature(sigAlg, sigHex) { let out = `\n`; out += ` Algorithm: ${sigAlg}\n`; if (new RegExp("withdsa", "i").test(sigAlg)) { const d = new r.KJUR.crypto.DSA(); const sigParam = d.parseASN1Signature(sigHex); out += ` Signature: R: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[0]))} S: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[1]))}\n`; } else if (new RegExp("withrsa", "i").test(sigAlg)) { out += ` Signature: ${formatHexOntoMultiLine(sigHex)}\n`; } else { out += ` Signature: ${formatHexOntoMultiLine(ensureHexIsPositiveInTwosComplement(sigHex))}\n`; } return chop(out); } /** * Format Subject Public Key from PEM encoded public key string * @param {*} publicKeyPEM string * @returns Multi-line string describing Subject Public Key Info */ function formatSubjectPublicKey(publicKeyPEM) { let out = "\n"; const publicKey = r.KEYUTIL.getKey(publicKeyPEM); if (publicKey instanceof r.RSAKey) { out += ` Algorithm: RSA Length: ${publicKey.n.bitLength()} bits Modulus: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.n))} Exponent: ${publicKey.e} (0x${Utils.hex(publicKey.e)})\n`; } else if (publicKey instanceof r.KJUR.crypto.ECDSA) { out += ` Algorithm: ECDSA Length: ${publicKey.ecparams.keylen} bits Pub: ${formatHexOntoMultiLine(publicKey.pubKeyHex)} ASN1 OID: ${r.KJUR.crypto.ECDSA.getName(publicKey.getShortNISTPCurveName())} NIST CURVE: ${publicKey.getShortNISTPCurveName()}\n`; } else if (publicKey instanceof r.KJUR.crypto.DSA) { out += ` Algorithm: DSA Length: ${publicKey.p.toString(16).length * 4} bits Pub: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.y))} P: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.p))} Q: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.q))} G: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.g))}\n`; } else { out += `unsupported public key algorithm\n`; } return chop(out); } /** * Format known extensions of a CSR * @param {*} csrParam object * @returns Multi-line string describing CSR Requested Extensions */ function formatRequestedExtensions(csrParam) { const formattedExtensions = new Array(4).fill(""); if (Object.hasOwn(csrParam, "extreq")) { for (const extension of csrParam.extreq) { let parts = []; switch (extension.extname) { case "basicConstraints" : parts = describeBasicConstraints(extension); formattedExtensions[0] = ` Basic Constraints:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; break; case "keyUsage" : parts = describeKeyUsage(extension); formattedExtensions[1] = ` Key Usage:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; break; case "extKeyUsage" : parts = describeExtendedKeyUsage(extension); formattedExtensions[2] = ` Extended Key Usage:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; break; case "subjectAltName" : parts = describeSubjectAlternativeName(extension); formattedExtensions[3] = ` Subject Alternative Name:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; break; default : parts = ["(unsuported extension)"]; formattedExtensions.push(` ${extension.extname}:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`); } } } let out = "\n"; formattedExtensions.forEach((formattedExtension) => { if (formattedExtension !== undefined && formattedExtension !== null && formattedExtension.length !== 0) { out += formattedExtension; } }); return chop(out); } /** * Format extension critical tag * @param {*} extension Object * @returns String describing whether the extension is critical or not */ function formatExtensionCriticalTag(extension) { return Object.hasOwn(extension, "critical") && extension.critical ? " critical" : ""; } /** * Format string input as a comma separated hex string on multiple lines * @param {*} hex String * @returns Multi-line string describing the Hex input */ function formatHexOntoMultiLine(hex) { if (hex.length % 2 !== 0) { hex = "0" + hex; } return formatMultiLine(chop(hex.replace(/(..)/g, "$&:"))); } /** * Convert BigInt to abs value in Hex * @param {*} int BigInt * @returns String representing absolute value in Hex */ function absBigIntToHex(int) { int = int < 0n ? -int : int; return ensureHexIsPositiveInTwosComplement(int.toString(16)); } /** * Ensure Hex String remains positive in 2's complement * @param {*} hex String * @returns Hex String ensuring value remains positive in 2's complement */ function ensureHexIsPositiveInTwosComplement(hex) { if (hex.length % 2 !== 0) { return "0" + hex; } // prepend 00 if most significant bit is 1 (sign bit) if (hex.length >=2 && (parseInt(hex.substring(0, 2), 16) & 128)) { hex = "00" + hex; } return hex; } /** * Format string onto multiple lines * @param {*} longStr * @returns String as a multi-line string */ function formatMultiLine(longStr) { const lines = []; for (let remain = longStr ; remain !== "" ; remain = remain.substring(48)) { lines.push(remain.substring(0, 48)); } return lines.join("\n "); } /** * Describe Basic Constraints * @see RFC 5280 4.2.1.9. Basic Constraints https://www.ietf.org/rfc/rfc5280.txt * @param {*} extension CSR extension with the name `basicConstraints` * @returns Array of strings describing Basic Constraints */ function describeBasicConstraints(extension) { const constraints = []; constraints.push(`CA = ${Object.hasOwn(extension, "cA") && extension.cA ? "true" : "false"}`); if (Object.hasOwn(extension, "pathLen")) constraints.push(`PathLenConstraint = ${extension.pathLen}`); return constraints; } /** * Describe Key Usage extension permitted use cases * @see RFC 5280 4.2.1.3. Key Usage https://www.ietf.org/rfc/rfc5280.txt * @param {*} extension CSR extension with the name `keyUsage` * @returns Array of strings describing Key Usage extension permitted use cases */ function describeKeyUsage(extension) { const usage = []; const kuIdentifierToName = { digitalSignature: "Digital Signature", nonRepudiation: "Non-repudiation", keyEncipherment: "Key encipherment", dataEncipherment: "Data encipherment", keyAgreement: "Key agreement", keyCertSign: "Key certificate signing", cRLSign: "CRL signing", encipherOnly: "Encipher Only", decipherOnly: "Decipher Only", }; if (Object.hasOwn(extension, "names")) { extension.names.forEach((ku) => { if (Object.hasOwn(kuIdentifierToName, ku)) { usage.push(kuIdentifierToName[ku]); } else { usage.push(`unknown key usage (${ku})`); } }); } if (usage.length === 0) usage.push("(none)"); return usage; } /** * Describe Extended Key Usage extension permitted use cases * @see RFC 5280 4.2.1.12. Extended Key Usage https://www.ietf.org/rfc/rfc5280.txt * @param {*} extension CSR extension with the name `extendedKeyUsage` * @returns Array of strings describing Extended Key Usage extension permitted use cases */ function describeExtendedKeyUsage(extension) { const usage = []; const ekuIdentifierToName = { "serverAuth": "TLS Web Server Authentication", "clientAuth": "TLS Web Client Authentication", "codeSigning": "Code signing", "emailProtection": "E-mail Protection (S/MIME)", "timeStamping": "Trusted Timestamping", "1.3.6.1.4.1.311.2.1.21": "Microsoft Individual Code Signing", // msCodeInd "1.3.6.1.4.1.311.2.1.22": "Microsoft Commercial Code Signing", // msCodeCom "1.3.6.1.4.1.311.10.3.1": "Microsoft Trust List Signing", // msCTLSign "1.3.6.1.4.1.311.10.3.3": "Microsoft Server Gated Crypto", // msSGC "1.3.6.1.4.1.311.10.3.4": "Microsoft Encrypted File System", // msEFS "1.3.6.1.4.1.311.20.2.2": "Microsoft Smartcard Login", // msSmartcardLogin "2.16.840.1.113730.4.1": "Netscape Server Gated Crypto", // nsSGC }; if (Object.hasOwn(extension, "array")) { extension.array.forEach((eku) => { if (Object.hasOwn(ekuIdentifierToName, eku)) { usage.push(ekuIdentifierToName[eku]); } else { usage.push(eku); } }); } if (usage.length === 0) usage.push("(none)"); return usage; } /** * Format Subject Alternative Names from the name `subjectAltName` extension * @see RFC 5280 4.2.1.6. Subject Alternative Name https://www.ietf.org/rfc/rfc5280.txt * @param {*} extension object * @returns Array of strings describing Subject Alternative Name extension */ function describeSubjectAlternativeName(extension) { const names = []; if (Object.hasOwn(extension, "extname") && extension.extname === "subjectAltName") { if (Object.hasOwn(extension, "array")) { for (const altName of extension.array) { Object.keys(altName).forEach((key) => { switch (key) { case "rfc822": names.push(`EMAIL: ${altName[key]}`); break; case "dns": names.push(`DNS: ${altName[key]}`); break; case "uri": names.push(`URI: ${altName[key]}`); break; case "ip": names.push(`IP: ${altName[key]}`); break; case "dn": names.push(`DIR: ${altName[key].str}`); break; case "other" : names.push(`Other: ${altName[key].oid}::${altName[key].value.utf8str.str}`); break; default: names.push(`(unable to format SAN '${key}':${altName[key]})\n`); } }); } } } return names; } /** * Join an array of strings and add leading spaces to each line. * @param {*} n How many leading spaces * @param {*} parts Array of strings * @returns Joined and indented string. */ function indent(n, parts) { const fluff = " ".repeat(n); return fluff + parts.join("\n" + fluff) + "\n"; } /** * Remove last character from a string. * @param {*} s String * @returns Chopped string. */ function chop(s) { return s.substring(0, s.length - 1); } export default ParseCSR; ================================================ FILE: src/core/operations/ParseColourCode.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Parse colour code operation */ class ParseColourCode extends Operation { /** * ParseColourCode constructor */ constructor() { super(); this.name = "Parse colour code"; this.module = "Default"; this.description = "Converts a colour code in a standard format to other standard formats and displays the colour itself.

Example inputs
  • #d9edf7
  • rgba(217,237,247,1)
  • hsla(200,65%,91%,1)
  • cmyk(0.12, 0.04, 0.00, 0.03)
"; this.infoURL = "https://wikipedia.org/wiki/Web_colors"; this.inputType = "string"; this.outputType = "html"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { let m = null, r = 0, g = 0, b = 0, a = 1; // Read in the input if ((m = input.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i))) { // Hex - #d9edf7 r = parseInt(m[1], 16); g = parseInt(m[2], 16); b = parseInt(m[3], 16); } else if ((m = input.match(/rgba?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)(?:,\s?(\d(?:\.\d+)?))?\)/i))) { // RGB or RGBA - rgb(217,237,247) or rgba(217,237,247,1) r = parseFloat(m[1]); g = parseFloat(m[2]); b = parseFloat(m[3]); a = m[4] ? parseFloat(m[4]) : 1; } else if ((m = input.match(/hsla?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)%,\s?(\d{1,3}(?:\.\d+)?)%(?:,\s?(\d(?:\.\d+)?))?\)/i))) { // HSL or HSLA - hsl(200, 65%, 91%) or hsla(200, 65%, 91%, 1) const h_ = parseFloat(m[1]) / 360, s_ = parseFloat(m[2]) / 100, l_ = parseFloat(m[3]) / 100, rgb_ = ParseColourCode._hslToRgb(h_, s_, l_); r = rgb_[0]; g = rgb_[1]; b = rgb_[2]; a = m[4] ? parseFloat(m[4]) : 1; } else if ((m = input.match(/cmyk\((\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?)\)/i))) { // CMYK - cmyk(0.12, 0.04, 0.00, 0.03) const c_ = parseFloat(m[1]), m_ = parseFloat(m[2]), y_ = parseFloat(m[3]), k_ = parseFloat(m[4]); r = Math.round(255 * (1 - c_) * (1 - k_)); g = Math.round(255 * (1 - m_) * (1 - k_)); b = Math.round(255 * (1 - y_) * (1 - k_)); } const hsl_ = ParseColourCode._rgbToHsl(r, g, b), h = Math.round(hsl_[0] * 360), s = Math.round(hsl_[1] * 100), l = Math.round(hsl_[2] * 100); let k = 1 - Math.max(r/255, g/255, b/255), c = (1 - r/255 - k) / (1 - k), y = (1 - b/255 - k) / (1 - k); m = (1 - g/255 - k) / (1 - k); c = isNaN(c) ? "0" : c.toFixed(2); m = isNaN(m) ? "0" : m.toFixed(2); y = isNaN(y) ? "0" : y.toFixed(2); k = k.toFixed(2); const hex = "#" + Math.round(r).toString(16).padStart(2, "0") + Math.round(g).toString(16).padStart(2, "0") + Math.round(b).toString(16).padStart(2, "0"), rgb = "rgb(" + r + ", " + g + ", " + b + ")", rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")", hsl = "hsl(" + h + ", " + s + "%, " + l + "%)", hsla = "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")", cmyk = "cmyk(" + c + ", " + m + ", " + y + ", " + k + ")"; // Generate output return `
Hex: ${hex} RGB: ${rgb} RGBA: ${rgba} HSL: ${hsl} HSLA: ${hsla} CMYK: ${cmyk} `; } /** * Converts an HSL color value to RGB. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. * Assumes h, s, and l are contained in the set [0, 1] and * returns r, g, and b in the set [0, 255]. * * @author Mohsen (http://stackoverflow.com/a/9493060) * * @param {number} h - The hue * @param {number} s - The saturation * @param {number} l - The lightness * @return {Array} The RGB representation */ static _hslToRgb(h, s, l) { let r, g, b; if (s === 0) { r = g = b = l; // achromatic } else { const hue2rgb = function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; }; const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; } /** * Converts an RGB color value to HSL. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. * Assumes r, g, and b are contained in the set [0, 255] and * returns h, s, and l in the set [0, 1]. * * @author Mohsen (http://stackoverflow.com/a/9493060) * * @param {number} r - The red color value * @param {number} g - The green color value * @param {number} b - The blue color value * @return {Array} The HSL representation */ static _rgbToHsl(r, g, b) { r /= 255; g /= 255; b /= 255; const max = Math.max(r, g, b), min = Math.min(r, g, b), l = (max + min) / 2; let h, s; if (max === min) { h = s = 0; // achromatic } else { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h, s, l]; } } export default ParseColourCode; ================================================ FILE: src/core/operations/ParseDateTime.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import moment from "moment-timezone"; import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs"; /** * Parse DateTime operation */ class ParseDateTime extends Operation { /** * ParseDateTime constructor */ constructor() { super(); this.name = "Parse DateTime"; this.module = "Default"; this.description = "Parses a DateTime string in your specified format and displays it in whichever timezone you choose with the following information:
  • Date
  • Time
  • Period (AM/PM)
  • Timezone
  • UTC offset
  • Daylight Saving Time
  • Leap year
  • Days in this month
  • Day of year
  • Week number
  • Quarter
Run with no input to see format string examples if required."; this.infoURL = "https://momentjs.com/docs/#/parsing/string-format/"; this.inputType = "string"; this.outputType = "html"; this.args = [ { "name": "Built in formats", "type": "populateOption", "value": DATETIME_FORMATS, "target": 1 }, { "name": "Input format string", "type": "binaryString", "value": "DD/MM/YYYY HH:mm:ss" }, { "name": "Input timezone", "type": "option", "value": ["UTC"].concat(moment.tz.names()) } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const inputFormat = args[1], inputTimezone = args[2]; let date, output = ""; try { date = moment.tz(input, inputFormat, inputTimezone); if (!date || date.format() === "Invalid date") throw Error; } catch (err) { return `Invalid format.\n\n${FORMAT_EXAMPLES}`; } output += "Date: " + date.format("dddd Do MMMM YYYY") + "\nTime: " + date.format("HH:mm:ss") + "\nPeriod: " + date.format("A") + "\nTimezone: " + date.format("z") + "\nUTC offset: " + date.format("ZZ") + "\n\nDaylight Saving Time: " + date.isDST() + "\nLeap year: " + date.isLeapYear() + "\nDays in this month: " + date.daysInMonth() + "\n\nDay of year: " + date.dayOfYear() + "\nWeek number: " + date.week() + "\nQuarter: " + date.quarter(); return output; } } export default ParseDateTime; ================================================ FILE: src/core/operations/ParseIPRange.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author Klaxon [klaxon@veyr.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import {ipv4CidrRange, ipv4HyphenatedRange, ipv4ListedRange, ipv6CidrRange, ipv6HyphenatedRange, ipv6ListedRange} from "../lib/IP.mjs"; /** * Parse IP range operation */ class ParseIPRange extends Operation { /** * ParseIPRange constructor */ constructor() { super(); this.name = "Parse IP range"; this.module = "Default"; this.description = "Given a CIDR range (e.g. 10.0.0.0/24), hyphenated range (e.g. 10.0.0.0 - 10.0.1.0), 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.

IPv6 is supported but will not be enumerated."; this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Include network info", "type": "boolean", "value": true }, { "name": "Enumerate IP addresses", "type": "boolean", "value": true }, { "name": "Allow large queries", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [ includeNetworkInfo, enumerateAddresses, allowLargeList ] = args; // Check what type of input we are looking at const ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/, ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, ipv4ListRegex = /^\s*(((?:\d{1,3}\.){3}\d{1,3})(\/(\d\d?))?(\n|$)(\n*))+\s*$/, 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, 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, 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; let match; if ((match = ipv4CidrRegex.exec(input))) { return ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); } else if ((match = ipv4RangeRegex.exec(input))) { return ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); } else if ((match = ipv4ListRegex.exec(input))) { return ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); } else if ((match = ipv6CidrRegex.exec(input))) { return ipv6CidrRange(match, includeNetworkInfo); } else if ((match = ipv6RangeRegex.exec(input))) { return ipv6HyphenatedRange(match, includeNetworkInfo); } else if ((match = ipv6ListRegex.exec(input))) { return ipv6ListedRange(match, includeNetworkInfo); } else { 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."); } } } export default ParseIPRange; ================================================ FILE: src/core/operations/ParseIPv4Header.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import {fromHex, toHex} from "../lib/Hex.mjs"; import {ipv4ToStr, protocolLookup} from "../lib/IP.mjs"; import TCPIPChecksum from "./TCPIPChecksum.mjs"; /** * Parse IPv4 header operation */ class ParseIPv4Header extends Operation { /** * ParseIPv4Header constructor */ constructor() { super(); this.name = "Parse IPv4 header"; this.module = "Default"; this.description = "Given an IPv4 header, this operations parses and displays each field in an easily readable format."; this.infoURL = "https://wikipedia.org/wiki/IPv4#Header"; this.inputType = "string"; this.outputType = "html"; this.args = [ { "name": "Input format", "type": "option", "value": ["Hex", "Raw"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const format = args[0]; let output; if (format === "Hex") { input = fromHex(input); } else if (format === "Raw") { input = new Uint8Array(Utils.strToArrayBuffer(input)); } else { throw new OperationError("Unrecognised input format."); } let ihl = input[0] & 0x0f; const dscp = (input[1] >>> 2) & 0x3f, ecn = input[1] & 0x03, length = input[2] << 8 | input[3], identification = input[4] << 8 | input[5], flags = (input[6] >>> 5) & 0x07, fragOffset = (input[6] & 0x1f) << 8 | input[7], ttl = input[8], protocol = input[9], checksum = input[10] << 8 | input[11], srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15], dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19], checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20)); let version = (input[0] >>> 4) & 0x0f, options = []; // Version if (version !== 4) { version = version + " (Error: for IPv4 headers, this should always be set to 4)"; } // IHL if (ihl < 5) { ihl = ihl + " (Error: this should always be at least 5)"; } else if (ihl > 5) { // sort out options... const optionsLen = ihl * 4 - 20; options = input.slice(20, optionsLen + 20); } // Protocol const protocolInfo = protocolLookup[protocol] || {keyword: "", protocol: ""}; // Checksum const correctChecksum = (new TCPIPChecksum).run(checksumHeader), givenChecksum = Utils.hex(checksum); let checksumResult; if (correctChecksum === givenChecksum) { checksumResult = givenChecksum + " (correct)"; } else { checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")"; } output = ``; if (ihl > 5) { output += ``; } return output + "
FieldValue
Version${version}
Internet Header Length (IHL)${ihl} (${ihl * 4} bytes)
Differentiated Services Code Point (DSCP)${dscp}
Explicit Congestion Notification (ECN)${ecn}
Total length${length} bytes IP header: ${ihl * 4} bytes Data: ${length - ihl * 4} bytes
Identification0x${Utils.hex(identification)} (${identification})
Flags0x${Utils.hex(flags, 2)} Reserved bit:${flags >> 2} (must be 0) Don't fragment:${flags >> 1 & 1} More fragments:${flags & 1}
Fragment offset${fragOffset}
Time-To-Live${ttl}
Protocol${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})
Header checksum${checksumResult}
Source IP address${ipv4ToStr(srcIP)}
Destination IP address${ipv4ToStr(dstIP)}
Options${toHex(options)}
"; } } export default ParseIPv4Header; ================================================ FILE: src/core/operations/ParseIPv6Address.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import {strToIpv6, ipv6ToStr, ipv4ToStr, IPV6_REGEX} from "../lib/IP.mjs"; import BigNumber from "bignumber.js"; /** * Parse IPv6 address operation */ class ParseIPv6Address extends Operation { /** * ParseIPv6Address constructor */ constructor() { super(); this.name = "Parse IPv6 address"; this.module = "Default"; this.description = "Displays the longhand and shorthand versions of a valid IPv6 address.

Recognises all reserved ranges and parses encapsulated or tunnelled addresses including Teredo and 6to4."; this.infoURL = "https://wikipedia.org/wiki/IPv6_address"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let match, output = ""; if ((match = IPV6_REGEX.exec(input))) { const ipv6 = strToIpv6(match[1]), longhand = ipv6ToStr(ipv6), shorthand = ipv6ToStr(ipv6, true); output += "Longhand: " + longhand + "\nShorthand: " + shorthand + "\n"; // Detect reserved addresses if (shorthand === "::") { // Unspecified address output += "\nUnspecified address corresponding to 0.0.0.0/32 in IPv4."; output += "\nUnspecified address range: ::/128"; } else if (shorthand === "::1") { // Loopback address output += "\nLoopback address to the local host corresponding to 127.0.0.1/8 in IPv4."; output += "\nLoopback addresses range: ::1/128"; } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0xffff) { // IPv4-mapped IPv6 address 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."; output += "\nMapped IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); output += "\nIPv4-mapped IPv6 addresses range: ::ffff:0:0/96"; } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && ipv6[3] === 0 && ipv6[4] === 0xffff && ipv6[5] === 0) { // IPv4-translated address output += "\nIPv4-translated address detected. Used by Stateless IP/ICMP Translation (SIIT). See RFCs 6145 and 6052 for more details."; output += "\nTranslated IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); output += "\nIPv4-translated addresses range: ::ffff:0:0:0/96"; } else if (ipv6[0] === 0x100) { // Discard prefix per RFC 6666 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."; output += "\nDiscard range: 100::/64"; } else if (ipv6[0] === 0x64 && ipv6[1] === 0xff9b && ipv6[2] === 0 && ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0) { // IPv4/IPv6 translation per RFC 6052 output += "\n'Well-Known' prefix for IPv4/IPv6 translation detected. See RFC 6052 for more details."; output += "\nTranslated IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); output += "\n'Well-Known' prefix range: 64:ff9b::/96"; } else if (ipv6[0] === 0x2001 && ipv6[1] === 0) { // Teredo tunneling output += "\nTeredo tunneling IPv6 address detected\n"; const serverIpv4 = (ipv6[2] << 16) + ipv6[3], udpPort = (~ipv6[5]) & 0xffff, clientIpv4 = ~((ipv6[6] << 16) + ipv6[7]), flagCone = (ipv6[4] >>> 15) & 1, flagR = (ipv6[4] >>> 14) & 1, flagRandom1 = (ipv6[4] >>> 10) & 15, flagUg = (ipv6[4] >>> 8) & 3, flagRandom2 = ipv6[4] & 255; output += "\nServer IPv4 address: " + ipv4ToStr(serverIpv4) + "\nClient IPv4 address: " + ipv4ToStr(clientIpv4) + "\nClient UDP port: " + udpPort + "\nFlags:" + "\n\tCone: " + flagCone; if (flagCone) { output += " (Client is behind a cone NAT)"; } else { output += " (Client is not behind a cone NAT)"; } output += "\n\tR: " + flagR; if (flagR) { output += " Error: This flag should be set to 0. See RFC 5991 and RFC 4380."; } output += "\n\tRandom1: " + Utils.bin(flagRandom1, 4) + "\n\tUG: " + Utils.bin(flagUg, 2); if (flagUg) { output += " Error: This flag should be set to 00. See RFC 4380."; } output += "\n\tRandom2: " + Utils.bin(flagRandom2, 8); if (!flagR && !flagUg && flagRandom1 && flagRandom2) { output += "\n\nThis is a valid Teredo address which complies with RFC 4380 and RFC 5991."; } else if (!flagR && !flagUg) { 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."; } else { output += "\n\nThis is an invalid Teredo address."; } output += "\n\nTeredo prefix range: 2001::/32"; } else if (ipv6[0] === 0x2001 && ipv6[1] === 0x2 && ipv6[2] === 0) { // Benchmarking 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."; output += "\nBMWG range: 2001:2::/48"; } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x10 && ipv6[1] <= 0x1f) { // ORCHIDv1 output += "\nDeprecated, previously ORCHIDv1 (Overlay Routable Cryptographic Hash Identifiers).\nORCHIDv1 range: 2001:10::/28\nORCHIDv2 now uses 2001:20::/28."; } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x20 && ipv6[1] <= 0x2f) { // ORCHIDv2 output += "\nORCHIDv2 (Overlay Routable Cryptographic Hash Identifiers).\nThese are non-routed IPv6 addresses used for Cryptographic Hash Identifiers."; output += "\nORCHIDv2 range: 2001:20::/28"; } else if (ipv6[0] === 0x2001 && ipv6[1] === 0xdb8) { // Documentation 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."; output += "\nDocumentation range: 2001:db8::/32"; } else if (ipv6[0] === 0x2002) { // 6to4 output += "\n6to4 transition IPv6 address detected. See RFC 3056 for more details." + "\n6to4 prefix range: 2002::/16"; const v4Addr = ipv4ToStr((ipv6[1] << 16) + ipv6[2]), slaId = ipv6[3], interfaceIdStr = ipv6[4].toString(16) + ipv6[5].toString(16) + ipv6[6].toString(16) + ipv6[7].toString(16), interfaceId = new BigNumber(interfaceIdStr, 16); output += "\n\nEncapsulated IPv4 address: " + v4Addr + "\nSLA ID: " + slaId + "\nInterface ID (base 16): " + interfaceIdStr + "\nInterface ID (base 10): " + interfaceId.toString(); } else if (ipv6[0] >= 0xfc00 && ipv6[0] <= 0xfdff) { // Unique local address 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."; output += "\nUnique local addresses range: fc00::/7"; } else if (ipv6[0] >= 0xfe80 && ipv6[0] <= 0xfebf) { // Link-local address output += "\nThis is a link-local address comparable to the auto-configuration addresses 169.254.0.0/16 in IPv4."; output += "\nLink-local addresses range: fe80::/10"; } else if (ipv6[0] >= 0xff00) { // Multicast output += "\nThis is a reserved multicast address."; output += "\nMulticast addresses range: ff00::/8"; switch (ipv6[0]) { case 0xff01: output += "\n\nReserved Multicast Block for Interface Local Scope"; break; case 0xff02: output += "\n\nReserved Multicast Block for Link Local Scope"; break; case 0xff03: output += "\n\nReserved Multicast Block for Realm Local Scope"; break; case 0xff04: output += "\n\nReserved Multicast Block for Admin Local Scope"; break; case 0xff05: output += "\n\nReserved Multicast Block for Site Local Scope"; break; case 0xff08: output += "\n\nReserved Multicast Block for Organisation Local Scope"; break; case 0xff0e: output += "\n\nReserved Multicast Block for Global Scope"; break; } if (ipv6[6] === 1) { if (ipv6[7] === 2) { output += "\nReserved Multicast Address for 'All DHCP Servers and Relay Agents (defined in RFC3315)'"; } else if (ipv6[7] === 3) { output += "\nReserved Multicast Address for 'All LLMNR Hosts (defined in RFC4795)'"; } } else { switch (ipv6[7]) { case 1: output += "\nReserved Multicast Address for 'All nodes'"; break; case 2: output += "\nReserved Multicast Address for 'All routers'"; break; case 5: output += "\nReserved Multicast Address for 'OSPFv3 - All OSPF routers'"; break; case 6: output += "\nReserved Multicast Address for 'OSPFv3 - All Designated Routers'"; break; case 8: output += "\nReserved Multicast Address for 'IS-IS for IPv6 Routers'"; break; case 9: output += "\nReserved Multicast Address for 'RIP Routers'"; break; case 0xa: output += "\nReserved Multicast Address for 'EIGRP Routers'"; break; case 0xc: output += "\nReserved Multicast Address for 'Simple Service Discovery Protocol'"; break; case 0xd: output += "\nReserved Multicast Address for 'PIM Routers'"; break; case 0x16: output += "\nReserved Multicast Address for 'MLDv2 Reports (defined in RFC3810)'"; break; case 0x6b: output += "\nReserved Multicast Address for 'Precision Time Protocol v2 Peer Delay Measurement Messages'"; break; case 0xfb: output += "\nReserved Multicast Address for 'Multicast DNS'"; break; case 0x101: output += "\nReserved Multicast Address for 'Network Time Protocol'"; break; case 0x108: output += "\nReserved Multicast Address for 'Network Information Service'"; break; case 0x114: output += "\nReserved Multicast Address for 'Experiments'"; break; case 0x181: output += "\nReserved Multicast Address for 'Precision Time Protocol v2 Messages (exc. Peer Delay)'"; break; } } } // Detect possible EUI-64 addresses if (((ipv6[5] & 0xff) === 0xff) && (ipv6[6] >>> 8 === 0xfe)) { output += "\n\nThis IPv6 address contains a modified EUI-64 address, identified by the presence of FF:FE in the 12th and 13th octets."; const intIdent = Utils.hex(ipv6[4] >>> 8) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[5] & 0xff) + ":" + Utils.hex(ipv6[6] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff), mac = Utils.hex((ipv6[4] >>> 8) ^ 2) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff); output += "\nInterface identifier: " + intIdent + "\nMAC address: " + mac; } } else { throw new OperationError("Invalid IPv6 address"); } return output; } } export default ParseIPv6Address; ================================================ FILE: src/core/operations/ParseObjectIDTimestamp.mjs ================================================ /** * @author dmfj [dominic@dmfj.io] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import BSON from "bson"; /** * Parse ObjectID timestamp operation */ class ParseObjectIDTimestamp extends Operation { /** * ParseObjectIDTimestamp constructor */ constructor() { super(); this.name = "Parse ObjectID timestamp"; this.module = "Serialise"; this.description = "Parse timestamp from MongoDB/BSON ObjectID hex string."; this.infoURL = "https://docs.mongodb.com/manual/reference/method/ObjectId.getTimestamp/"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { try { const objectId = new BSON.ObjectID(input); return objectId.getTimestamp().toISOString(); } catch (err) { throw new OperationError(err); } } } export default ParseObjectIDTimestamp; ================================================ FILE: src/core/operations/ParseQRCode.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { parseQrCode } from "../lib/QRCode.mjs"; /** * Parse QR Code operation */ class ParseQRCode extends Operation { /** * ParseQRCode constructor */ constructor() { super(); this.name = "Parse QR Code"; this.module = "Image"; this.description = "Reads an image file and attempts to detect and read a Quick Response (QR) code from the image.

Normalise Image
Attempts to normalise the image before parsing it to improve detection of a QR code."; this.infoURL = "https://wikipedia.org/wiki/QR_code"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Normalise image", type: "boolean", value: false, }, ]; this.checks = [ { pattern: "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)", flags: "", args: [false], useful: true, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ async run(input, args) { const [normalise] = args; if (!isImage(input)) { throw new OperationError("Invalid file type."); } return parseQrCode(input, normalise); } } export default ParseQRCode; ================================================ FILE: src/core/operations/ParseSSHHostKey.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { fromBase64 } from "../lib/Base64.mjs"; import { fromHex, toHexFast } from "../lib/Hex.mjs"; /** * Parse SSH Host Key operation */ class ParseSSHHostKey extends Operation { /** * ParseSSHHostKey constructor */ constructor() { super(); this.name = "Parse SSH Host Key"; this.module = "Default"; this.description = "Parses a SSH host key and extracts fields from it.
The key type can be:
  • ssh-rsa
  • ssh-dss
  • ecdsa-sha2
  • ssh-ed25519
The key format can be either Hex or Base64."; this.infoURL = "https://wikipedia.org/wiki/Secure_Shell"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Input Format", type: "option", value: [ "Auto", "Base64", "Hex" ] } ]; this.checks = [ { pattern: "^\\s*([A-F\\d]{2}[,;:]){15,}[A-F\\d]{2}\\s*$", flags: "i", args: ["Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [inputFormat] = args, inputKey = this.convertKeyToBinary(input.trim(), inputFormat), fields = this.parseKey(inputKey), keyType = Utils.byteArrayToChars(fromHex(fields[0]), ""); let output = `Key type: ${keyType}`; if (keyType === "ssh-rsa") { output += `\nExponent: 0x${fields[1]}`; output += `\nModulus: 0x${fields[2]}`; } else if (keyType === "ssh-dss") { output += `\np: 0x${fields[1]}`; output += `\nq: 0x${fields[2]}`; output += `\ng: 0x${fields[3]}`; output += `\ny: 0x${fields[4]}`; } else if (keyType.startsWith("ecdsa-sha2")) { output += `\nCurve: ${Utils.byteArrayToChars(fromHex(fields[1]))}`; output += `\nPoint: 0x${fields.slice(2)}`; } else if (keyType === "ssh-ed25519") { output += `\nx: 0x${fields[1]}`; } else { output += "\nUnsupported key type."; output += `\nParameters: ${fields.slice(1)}`; } return output; } /** * Converts the key to binary format from either hex or base64 * * @param {string} inputKey * @param {string} inputFormat * @returns {byteArray} */ convertKeyToBinary(inputKey, inputFormat) { const keyPattern = new RegExp(/^(?:ssh|ecdsa-sha2)\S+\s+(\S*)/), keyMatch = inputKey.match(keyPattern); if (keyMatch) { inputKey = keyMatch[1]; } if (inputFormat === "Auto") { inputFormat = this.detectKeyFormat(inputKey); } if (inputFormat === "Hex") { return fromHex(inputKey); } else if (inputFormat === "Base64") { return fromBase64(inputKey, null, "byteArray"); } else { throw new OperationError("Invalid input format."); } } /** * Detects if the key is base64 or hex encoded * * @param {string} inputKey * @returns {string} */ detectKeyFormat(inputKey) { const hexPattern = new RegExp(/^(?:[\dA-Fa-f]{2}[ ,;:]?)+$/); const b64Pattern = new RegExp(/^\s*(?:[A-Za-z\d+/]{4})+(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?\s*$/); if (hexPattern.test(inputKey)) { return "Hex"; } else if (b64Pattern.test(inputKey)) { return "Base64"; } else { throw new OperationError("Unable to detect input key format."); } } /** * Parses fields from the key * * @param {byteArray} key */ parseKey(key) { const fields = []; while (key.length > 0) { const lengthField = key.slice(0, 4); let decodedLength = 0; for (let i = 0; i < lengthField.length; i++) { decodedLength += lengthField[i]; decodedLength = decodedLength << 8; } decodedLength = decodedLength >> 8; // Break if length wasn't decoded correctly if (decodedLength <= 0) break; fields.push(toHexFast(key.slice(4, 4 + decodedLength))); key = key.slice(4 + decodedLength); } return fields; } } export default ParseSSHHostKey; ================================================ FILE: src/core/operations/ParseTCP.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Stream from "../lib/Stream.mjs"; import {toHexFast, fromHex} from "../lib/Hex.mjs"; import {toBinary} from "../lib/Binary.mjs"; import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import BigNumber from "bignumber.js"; /** * Parse TCP operation */ class ParseTCP extends Operation { /** * ParseTCP constructor */ constructor() { super(); this.name = "Parse TCP"; this.module = "Default"; this.description = "Parses a TCP header and payload (if present)."; this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol"; this.inputType = "string"; this.outputType = "json"; this.presentType = "html"; this.args = [ { name: "Input format", type: "option", value: ["Hex", "Raw"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const format = args[0]; if (format === "Hex") { input = fromHex(input); } else if (format === "Raw") { input = Utils.strToArrayBuffer(input); } else { throw new OperationError("Unrecognised input format."); } const s = new Stream(new Uint8Array(input)); if (s.length < 20) { throw new OperationError("Need at least 20 bytes for a TCP Header"); } // Parse Header const TCPPacket = { "Source port": s.readInt(2), "Destination port": s.readInt(2), "Sequence number": bytesToLargeNumber(s.getBytes(4)), "Acknowledgement number": s.readInt(4), "Data offset": s.readBits(4), "Flags": { "Reserved": toBinary(s.readBits(3), "", 3), "NS": s.readBits(1), "CWR": s.readBits(1), "ECE": s.readBits(1), "URG": s.readBits(1), "ACK": s.readBits(1), "PSH": s.readBits(1), "RST": s.readBits(1), "SYN": s.readBits(1), "FIN": s.readBits(1), }, "Window size": s.readInt(2), "Checksum": "0x" + toHexFast(s.getBytes(2)), "Urgent pointer": "0x" + toHexFast(s.getBytes(2)) }; // Parse options if present let windowScaleShift = 0; if (TCPPacket["Data offset"] > 5) { let remainingLength = TCPPacket["Data offset"] * 4 - 20; const options = {}; while (remainingLength > 0) { const option = { "Kind": s.readInt(1) }; let opt = { name: "Reserved", length: true }; if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) { opt = TCP_OPTION_KIND_LOOKUP[option.Kind]; } // Add Length and Value fields if (opt.length) { option.Length = s.readInt(1); if (option.Length > 2) { if (Object.prototype.hasOwnProperty.call(opt, "parser")) { option.Value = opt.parser(s.getBytes(option.Length - 2)); } else { option.Value = option.Length <= 6 ? s.readInt(option.Length - 2): "0x" + toHexFast(s.getBytes(option.Length - 2)); } // Store Window Scale shift for later if (option.Kind === 3 && option.Value) { windowScaleShift = option.Value["Shift count"]; } } } options[opt.name] = option; const length = option.Length || 1; remainingLength -= length; } TCPPacket.Options = options; } if (s.hasMore()) { TCPPacket.Data = "0x" + toHexFast(s.getBytes()); } // Improve values TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`; const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift))); TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`; return TCPPacket; } /** * Displays the TCP Packet in a tabular style * @param {Object} data * @returns {html} */ present(data) { return objToTable(data); } } // Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml // on 2022-05-30 const TCP_OPTION_KIND_LOOKUP = { 0: { name: "End of Option List", length: false }, 1: { name: "No-Operation", length: false }, 2: { name: "Maximum Segment Size", length: true }, 3: { name: "Window Scale", length: true, parser: windowScaleParser }, 4: { name: "SACK Permitted", length: true }, 5: { name: "SACK", length: true }, 6: { name: "Echo (obsoleted by option 8)", length: true }, 7: { name: "Echo Reply (obsoleted by option 8)", length: true }, 8: { name: "Timestamps", length: true, parser: tcpTimestampParser }, 9: { name: "Partial Order Connection Permitted (obsolete)", length: true }, 10: { name: "Partial Order Service Profile (obsolete)", length: true }, 11: { name: "CC (obsolete)", length: true }, 12: { name: "CC.NEW (obsolete)", length: true }, 13: { name: "CC.ECHO (obsolete)", length: true }, 14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser }, 15: { name: "TCP Alternate Checksum Data (obsolete)", length: true }, 16: { name: "Skeeter", length: true }, 17: { name: "Bubba", length: true }, 18: { name: "Trailer Checksum Option", length: true }, 19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true }, 20: { name: "SCPS Capabilities", length: true }, 21: { name: "Selective Negative Acknowledgements", length: true }, 22: { name: "Record Boundaries", length: true }, 23: { name: "Corruption experienced", length: true }, 24: { name: "SNAP", length: true }, 25: { name: "Unassigned (released 2000-12-18)", length: true }, 26: { name: "TCP Compression Filter", length: true }, 27: { name: "Quick-Start Response", length: true }, 28: { name: "User Timeout Option (also, other known unauthorized use)", length: true }, 29: { name: "TCP Authentication Option (TCP-AO)", length: true }, 30: { name: "Multipath TCP (MPTCP)", length: true }, 69: { name: "Encryption Negotiation (TCP-ENO)", length: true }, 70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, 76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, 77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, 78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, 253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true }, 254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true } }; /** * Parses the TCP Alternate Checksum Request field * @param {Uint8Array} data */ function tcpAlternateChecksumParser(data) { const lookup = { 0: "TCP Checksum", 1: "8-bit Fletchers's algorithm", 2: "16-bit Fletchers's algorithm", 3: "Redundant Checksum Avoidance" }[data[0]]; return `${lookup} (0x${toHexFast(data)})`; } /** * Parses the TCP Timestamp field * @param {Uint8Array} data */ function tcpTimestampParser(data) { const s = new Stream(data); if (s.length !== 8) return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`; const tsval = bytesToLargeNumber(s.getBytes(4)), tsecr = bytesToLargeNumber(s.getBytes(4)); return { "Current Timestamp": tsval, "Echo Reply": tsecr }; } /** * Parses the Window Scale field * @param {Uint8Array} data */ function windowScaleParser(data) { if (data.length !== 1) return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`; return { "Shift count": data[0], "Multiplier": 1 << data[0] }; } export default ParseTCP; ================================================ FILE: src/core/operations/ParseTLSRecord.mjs ================================================ /** * @author c65722 [] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {toHexFast} from "../lib/Hex.mjs"; import {objToTable} from "../lib/Protocol.mjs"; import Stream from "../lib/Stream.mjs"; /** * Parse TLS record operation. */ class ParseTLSRecord extends Operation { /** * ParseTLSRecord constructor. */ constructor() { super(); this.name = "Parse TLS record"; this.module = "Default"; this.description = "Parses one or more TLS records"; this.infoURL = "https://wikipedia.org/wiki/Transport_Layer_Security"; this.inputType = "ArrayBuffer"; this.outputType = "json"; this.presentType = "html"; this.args = []; this._handshakeParser = new HandshakeParser(); this._contentTypes = new Map(); for (const key in ContentType) { this._contentTypes[ContentType[key]] = key.toString().toLocaleLowerCase(); } } /** * @param {ArrayBuffer} input - Stream, containing one or more raw TLS Records. * @param {Object[]} args * @returns {Object[]} Array of Object representations of TLS Records contained within input. */ run(input, args) { const s = new Stream(new Uint8Array(input)); const output = []; while (s.hasMore()) { const record = this._readRecord(s); if (record) { output.push(record); } } return output; } /** * Reads a TLS Record from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw TLS Record. * @returns {Object} Object representation of TLS Record. */ _readRecord(input) { const RECORD_HEADER_LEN = 5; if (input.position + RECORD_HEADER_LEN > input.length) { input.moveTo(input.length); return null; } const type = input.readInt(1); const typeString = this._contentTypes[type] ?? type.toString(); const version = "0x" + toHexFast(input.getBytes(2)); const length = input.readInt(2); const content = input.getBytes(length); const truncated = content.length < length; const recordHeader = new RecordHeader(typeString, version, length, truncated); if (!content.length) { return {...recordHeader}; } if (type === ContentType.HANDSHAKE) { return this._handshakeParser.parse(new Stream(content), recordHeader); } const record = {...recordHeader}; record.value = "0x" + toHexFast(content); return record; } /** * Displays the parsed TLS Records in a tabular style. * * @param {Object[]} data - Array of Object representations of the TLS Records. * @returns {html} HTML representation of TLS Records contained within data. */ present(data) { return data.map(r => objToTable(r)).join("\n\n"); } } export default ParseTLSRecord; /** * Repesents the known values of type field of a TLS Record header. */ const ContentType = Object.freeze({ CHANGE_CIPHER_SPEC: 20, ALERT: 21, HANDSHAKE: 22, APPLICATION_DATA: 23, }); /** * Represents a TLS Record header */ class RecordHeader { /** * RecordHeader cosntructor. * * @param {string} type - String representation of TLS Record type field. * @param {string} version - Hex representation of TLS Record version field. * @param {int} length - Length of TLS Record. * @param {bool} truncated - Is TLS Record truncated. */ constructor(type, version, length, truncated) { this.type = type; this.version = version; this.length = length; if (truncated) { this.truncated = true; } } } /** * Parses TLS Handshake messages. */ class HandshakeParser { /** * HandshakeParser constructor. */ constructor() { this._clientHelloParser = new ClientHelloParser(); this._serverHelloParser = new ServerHelloParser(); this._newSessionTicketParser = new NewSessionTicketParser(); this._certificateParser = new CertificateParser(); this._certificateRequestParser = new CertificateRequestParser(); this._certificateVerifyParser = new CertificateVerifyParser(); this._handshakeTypes = new Map(); for (const key in HandshakeType) { this._handshakeTypes[HandshakeType[key]] = key.toString().toLowerCase(); } } /** * Parses a single TLS handshake message. * * @param {Stream} input - Stream, containing a raw Handshake message. * @param {RecordHeader} recordHeader - TLS Record header. * @returns {Object} Object representation of Handshake. */ parse(input, recordHeader) { const output = {...recordHeader}; if (!input.hasMore()) { return output; } const handshakeType = input.readInt(1); output.handshakeType = this._handshakeTypes[handshakeType] ?? handshakeType.toString(); if (input.position + 3 > input.length) { input.moveTo(input.length); return output; } const handshakeLength = input.readInt(3); if (handshakeLength + 4 !== recordHeader.length) { input.moveTo(0); output.handshakeType = this._handshakeTypes[HandshakeType.FINISHED]; output.handshakeValue = "0x" + toHexFast(input.bytes); return output; } const content = input.getBytes(handshakeLength); if (!content.length) { return output; } switch (handshakeType) { case HandshakeType.CLIENT_HELLO: return {...output, ...this._clientHelloParser.parse(new Stream(content))}; case HandshakeType.SERVER_HELLO: return {...output, ...this._serverHelloParser.parse(new Stream(content))}; case HandshakeType.NEW_SESSION_TICKET: return {...output, ...this._newSessionTicketParser.parse(new Stream(content))}; case HandshakeType.CERTIFICATE: return {...output, ...this._certificateParser.parse(new Stream(content))}; case HandshakeType.CERTIFICATE_REQUEST: return {...output, ...this._certificateRequestParser.parse(new Stream(content))}; case HandshakeType.CERTIFICATE_VERIFY: return {...output, ...this._certificateVerifyParser.parse(new Stream(content))}; default: output.handshakeValue = "0x" + toHexFast(content); } return output; } } /** * Represents the known values of the msg_type field of a TLS Handshake message. */ const HandshakeType = Object.freeze({ HELLO_REQUEST: 0, CLIENT_HELLO: 1, SERVER_HELLO: 2, NEW_SESSION_TICKET: 4, CERTIFICATE: 11, SERVER_KEY_EXCHANGE: 12, CERTIFICATE_REQUEST: 13, SERVER_HELLO_DONE: 14, CERTIFICATE_VERIFY: 15, CLIENT_KEY_EXCHANGE: 16, FINISHED: 20, }); /** * Parses TLS Handshake ClientHello messages. */ class ClientHelloParser { /** * ClientHelloParser constructor. */ constructor() { this._extensionsParser = new ExtensionsParser(); } /** * Parses a single TLS Handshake ClientHello message. * * @param {Stream} input - Stream, containing a raw ClientHello message. * @returns {Object} Object representation of ClientHello. */ parse(input) { const output = {}; output.clientVersion = this._readClientVersion(input); output.random = this._readRandom(input); const sessionID = this._readSessionID(input); if (sessionID) { output.sessionID = sessionID; } output.cipherSuites = this._readCipherSuites(input); output.compressionMethods = this._readCompressionMethods(input); output.extensions = this._readExtensions(input); return output; } /** * Reads the client_version field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ClientHello message, with position before client_version field. * @returns {string} Hex representation of client_version. */ _readClientVersion(input) { return readBytesAsHex(input, 2); } /** * Reads the random field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ClientHello message, with position before random field. * @returns {string} Hex representation of random. */ _readRandom(input) { return readBytesAsHex(input, 32); } /** * Reads the session_id field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ClientHello message, with position before session_id length field. * @returns {string} Hex representation of session_id, or empty string if session_id not present. */ _readSessionID(input) { return readSizePrefixedBytesAsHex(input, 1); } /** * Reads the cipher_suites field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ClientHello message, with position before cipher_suites length field. * @returns {Object} Object represention of cipher_suites field. */ _readCipherSuites(input) { const output = {}; output.length = input.readInt(2); if (!output.length) { return {}; } const cipherSuites = new Stream(input.getBytes(output.length)); if (cipherSuites.length < output.length) { output.truncated = true; } output.values = []; while (cipherSuites.hasMore()) { const cipherSuite = readBytesAsHex(cipherSuites, 2); if (cipherSuite) { output.values.push(cipherSuite); } } return output; } /** * Reads the compression_methods field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ClientHello message, with position before compression_methods length field. * @returns {Object} Object representation of compression_methods field. */ _readCompressionMethods(input) { const output = {}; output.length = input.readInt(1); if (!output.length) { return {}; } const compressionMethods = new Stream(input.getBytes(output.length)); if (compressionMethods.length < output.length) { output.truncated = true; } output.values = []; while (compressionMethods.hasMore()) { const compressionMethod = readBytesAsHex(compressionMethods, 1); if (compressionMethod) { output.values.push(compressionMethod); } } return output; } /** * Reads the extensions field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ClientHello message, with position before extensions length field. * @returns {Object} Object representations of extensions field. */ _readExtensions(input) { const output = {}; output.length = input.readInt(2); if (!output.length) { return {}; } const extensions = new Stream(input.getBytes(output.length)); if (extensions.length < output.length) { output.truncated = true; } output.values = this._extensionsParser.parse(extensions); return output; } } /** * Parses TLS Handshake ServeHello messages. */ class ServerHelloParser { /** * ServerHelloParser constructor. */ constructor() { this._extensionsParser = new ExtensionsParser(); } /** * Parses a single TLS Handshake ServerHello message. * * @param {Stream} input - Stream, containing a raw ServerHello message. * @return {Object} Object representation of ServerHello. */ parse(input) { const output = {}; output.serverVersion = this._readServerVersion(input); output.random = this._readRandom(input); const sessionID = this._readSessionID(input); if (sessionID) { output.sessionID = sessionID; } output.cipherSuite = this._readCipherSuite(input); output.compressionMethod = this._readCompressionMethod(input); output.extensions = this._readExtensions(input); return output; } /** * Reads the server_version field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ServerHello message, with position before server_version field. * @returns {string} Hex representation of server_version. */ _readServerVersion(input) { return readBytesAsHex(input, 2); } /** * Reads the random field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ServerHello message, with position before random field. * @returns {string} Hex representation of random. */ _readRandom(input) { return readBytesAsHex(input, 32); } /** * Reads the session_id field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ServertHello message, with position before session_id length field. * @returns {string} Hex representation of session_id, or empty string if session_id not present. */ _readSessionID(input) { return readSizePrefixedBytesAsHex(input, 1); } /** * Reads the cipher_suite field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ServerHello message, with position before cipher_suite field. * @returns {string} Hex represention of cipher_suite. */ _readCipherSuite(input) { return readBytesAsHex(input, 2); } /** * Reads the compression_method field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ServerHello message, with position before compression_method field. * @returns {string} Hex represention of compression_method. */ _readCompressionMethod(input) { return readBytesAsHex(input, 1); } /** * Reads the extensions field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw ServerHello message, with position before extensions length field. * @returns {Object} Object representation of extensions field. */ _readExtensions(input) { const output = {}; output.length = input.readInt(2); if (!output.length) { return {}; } const extensions = new Stream(input.getBytes(output.length)); if (extensions.length < output.length) { output.truncated = true; } output.values = this._extensionsParser.parse(extensions); return output; } } /** * Parses TLS Handshake Hello Extensions. */ class ExtensionsParser { /** * Parses a stream of TLS Handshake Hello Extensions. * * @param {Stream} input - Stream, containing multiple raw Extensions, with position before first extension length field. * @returns {Object[]} Array of Object representations of Extensions contained within input. */ parse(input) { const output = []; while (input.hasMore()) { const extension = this._readExtension(input); if (extension) { output.push(extension); } } return output; } /** * Reads a single Extension from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a list of Extensions, with position before the length field of the next Extension. * @returns {Object} Object representation of Extension. */ _readExtension(input) { const output = {}; if (input.position + 4 > input.length) { input.moveTo(input.length); return null; } output.type = "0x" + toHexFast(input.getBytes(2)); output.length = input.readInt(2); if (!output.length) { return output; } const value = input.getBytes(output.length); if (!value || value.length !== output.length) { output.truncated = true; } if (value && value.length) { output.value = "0x" + toHexFast(value); } return output; } } /** * Parses TLS Handshake NewSessionTicket messages. */ class NewSessionTicketParser { /** * Parses a single TLS Handshake NewSessionTicket message. * * @param {Stream} input - Stream, containing a raw NewSessionTicket message. * @returns {Object} Object representation of NewSessionTicket. */ parse(input) { return { ticketLifetimeHint: this._readTicketLifetimeHint(input), ticket: this._readTicket(input), }; } /** * Reads the ticket_lifetime_hint field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket_lifetime_hint field. * @returns {string} Lifetime hint, in seconds. */ _readTicketLifetimeHint(input) { if (input.position + 4 > input.length) { input.moveTo(input.length); return ""; } return input.readInt(4) + "s"; } /** * Reads the ticket field fromt the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket length field. * @returns {string} Hex representation of ticket. */ _readTicket(input) { return readSizePrefixedBytesAsHex(input, 2); } } /** * Parses TLS Handshake Certificate messages. */ class CertificateParser { /** * Parses a single TLS Handshake Certificate message. * * @param {Stream} input - Stream, containing a raw Certificate message. * @returns {Object} Object representation of Certificate. */ parse(input) { const output = {}; output.certificateList = this._readCertificateList(input); return output; } /** * Reads the certificate_list field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw Certificate message, with position before certificate_list length field. * @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_list field. */ _readCertificateList(input) { const output = {}; if (input.position + 3 > input.length) { input.moveTo(input.length); return output; } output.length = input.readInt(3); if (!output.length) { return output; } const certificates = new Stream(input.getBytes(output.length)); if (certificates.length < output.length) { output.truncated = true; } output.values = []; while (certificates.hasMore()) { const certificate = this._readCertificate(certificates); if (certificate) { output.values.push(certificate); } } return output; } /** * Reads a single certificate from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a list of certificicates, with position before the length field of the next certificate. * @returns {string} Hex representation of certificate. */ _readCertificate(input) { return readSizePrefixedBytesAsHex(input, 3); } } /** * Parses TLS Handshake CertificateRequest messages. */ class CertificateRequestParser { /** * Parses a single TLS Handshake CertificateRequest message. * * @param {Stream} input - Stream, containing a raw CertificateRequest message. * @return {Object} Object representation of CertificateRequest. */ parse(input) { const output = {}; output.certificateTypes = this._readCertificateTypes(input); output.supportedSignatureAlgorithms = this._readSupportedSignatureAlgorithms(input); const certificateAuthorities = this._readCertificateAuthorities(input); if (certificateAuthorities.length) { output.certificateAuthorities = certificateAuthorities; } return output; } /** * Reads the certificate_types field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_types length field. * @return {string[]} Array of strings, each containing a hex representation of a value within the certificate_types field. */ _readCertificateTypes(input) { const output = {}; output.length = input.readInt(1); if (!output.length) { return {}; } const certificateTypes = new Stream(input.getBytes(output.length)); if (certificateTypes.length < output.length) { output.truncated = true; } output.values = []; while (certificateTypes.hasMore()) { const certificateType = readBytesAsHex(certificateTypes, 1); if (certificateType) { output.values.push(certificateType); } } return output; } /** * Reads the supported_signature_algorithms field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before supported_signature_algorithms length field. * @returns {string[]} Array of strings, each containing a hex representation of a value within the supported_signature_algorithms field. */ _readSupportedSignatureAlgorithms(input) { const output = {}; output.length = input.readInt(2); if (!output.length) { return {}; } const signatureAlgorithms = new Stream(input.getBytes(output.length)); if (signatureAlgorithms.length < output.length) { output.truncated = true; } output.values = []; while (signatureAlgorithms.hasMore()) { const signatureAlgorithm = readBytesAsHex(signatureAlgorithms, 2); if (signatureAlgorithm) { output.values.push(signatureAlgorithm); } } return output; } /** * Reads the certificate_authorities field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_authorities length field. * @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_authorities field. */ _readCertificateAuthorities(input) { const output = {}; output.length = input.readInt(2); if (!output.length) { return {}; } const certificateAuthorities = new Stream(input.getBytes(output.length)); if (certificateAuthorities.length < output.length) { output.truncated = true; } output.values = []; while (certificateAuthorities.hasMore()) { const certificateAuthority = this._readCertificateAuthority(certificateAuthorities); if (certificateAuthority) { output.values.push(certificateAuthority); } } return output; } /** * Reads a single certificate authority from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a list of raw certificate authorities, with position before the length field of the next certificate authority. * @returns {string} Hex representation of certificate authority. */ _readCertificateAuthority(input) { return readSizePrefixedBytesAsHex(input, 2); } } /** * Parses TLS Handshake CertificateVerify messages. */ class CertificateVerifyParser { /** * Parses a single CertificateVerify Message. * * @param {Stream} input - Stream, containing a raw CertificateVerify message. * @returns {Object} Object representation of CertificateVerify. */ parse(input) { return { algorithmHash: this._readAlgorithmHash(input), algorithmSignature: this._readAlgorithmSignature(input), signature: this._readSignature(input), }; } /** * Reads the algorithm.hash field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.hash field. * @return {string} Hex representation of hash algorithm. */ _readAlgorithmHash(input) { return readBytesAsHex(input, 1); } /** * Reads the algorithm.signature field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.signature field. * @return {string} Hex representation of signature algorithm. */ _readAlgorithmSignature(input) { return readBytesAsHex(input, 1); } /** * Reads the signature field from the following bytes in the provided Stream. * * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before signature field. * @return {string} Hex representation of signature. */ _readSignature(input) { return readSizePrefixedBytesAsHex(input, 2); } } /** * Read the following size prefixed bytes from the provided Stream, and reuturn as a hex string. * * @param {Stream} input - Stream to read from. * @param {int} sizePrefixLength - Length of the size prefix field. * @returns {string} Hex representation of bytes read from Stream, empty string is returned if * field cannot be read in full. */ function readSizePrefixedBytesAsHex(input, sizePrefixLength) { const length = input.readInt(sizePrefixLength); if (!length) { return ""; } return readBytesAsHex(input, length); } /** * Read n bytes from the provided Stream, and return as a hex string. * * @param {Stream} input - Stream to read from. * @param {int} n - Number of bytes to read. * @returns {string} Hex representation of bytes read from Stream, or empty string if field cannot * be read in full. */ function readBytesAsHex(input, n) { const bytes = input.getBytes(n); if (!bytes || bytes.length !== n) { return ""; } return "0x" + toHexFast(bytes); } ================================================ FILE: src/core/operations/ParseTLV.mjs ================================================ /** * @author gchq77703 [] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import TLVParser from "../lib/TLVParser.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Parse TLV operation */ class ParseTLV extends Operation { /** * ParseTLV constructor */ constructor() { super(); this.name = "Parse TLV"; this.module = "Default"; this.description = "Converts a Type-Length-Value (TLV) encoded string into a JSON object. Can optionally include a Key / Type entry.

Tags: Key-Length-Value, KLV, Length-Value, LV"; this.infoURL = "https://wikipedia.org/wiki/Type-length-value"; this.inputType = "ArrayBuffer"; this.outputType = "JSON"; this.args = [ { name: "Type/Key size", type: "number", value: 1 }, { name: "Length size", type: "number", value: 1 }, { name: "Use BER", type: "boolean", value: false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [bytesInKey, bytesInLength, basicEncodingRules] = args; input = new Uint8Array(input); if (bytesInKey <= 0 && bytesInLength <= 0) throw new OperationError("Type or Length size must be greater than 0"); const tlv = new TLVParser(input, { bytesInLength, basicEncodingRules }); const data = []; while (!tlv.atEnd()) { const key = bytesInKey ? tlv.getValue(bytesInKey) : undefined; const length = tlv.getLength(); const value = tlv.getValue(length); data.push({ key, length, value }); } return data; } } export default ParseTLV; ================================================ FILE: src/core/operations/ParseUDP.mjs ================================================ /** * @author h345983745 [] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Stream from "../lib/Stream.mjs"; import {toHexFast, fromHex} from "../lib/Hex.mjs"; import {objToTable} from "../lib/Protocol.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Parse UDP operation */ class ParseUDP extends Operation { /** * ParseUDP constructor */ constructor() { super(); this.name = "Parse UDP"; this.module = "Default"; this.description = "Parses a UDP header and payload (if present)."; this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol"; this.inputType = "string"; this.outputType = "json"; this.presentType = "html"; this.args = [ { name: "Input format", type: "option", value: ["Hex", "Raw"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {Object} */ run(input, args) { const format = args[0]; if (format === "Hex") { input = fromHex(input); } else if (format === "Raw") { input = Utils.strToArrayBuffer(input); } else { throw new OperationError("Unrecognised input format."); } const s = new Stream(new Uint8Array(input)); if (s.length < 8) { throw new OperationError("Need 8 bytes for a UDP Header"); } // Parse Header const UDPPacket = { "Source port": s.readInt(2), "Destination port": s.readInt(2), "Length": s.readInt(2), "Checksum": "0x" + toHexFast(s.getBytes(2)) }; // Parse data if present if (s.hasMore()) { UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8)); } return UDPPacket; } /** * Displays the UDP Packet in a tabular style * @param {Object} data * @returns {html} */ present(data) { return objToTable(data); } } export default ParseUDP; ================================================ FILE: src/core/operations/ParseUNIXFilePermissions.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Parse UNIX file permissions operation */ class ParseUNIXFilePermissions extends Operation { /** * ParseUNIXFilePermissions constructor */ constructor() { super(); this.name = "Parse UNIX file permissions"; this.module = "Default"; 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.

Input should be in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."; this.infoURL = "https://wikipedia.org/wiki/File_system_permissions#Traditional_Unix_permissions"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = [ { pattern: "^\\s*d[rxw-]{9}\\s*$", flags: "", args: [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const perms = { d: false, // directory sl: false, // symbolic link np: false, // named pipe s: false, // socket cd: false, // character device bd: false, // block device dr: false, // door sb: false, // sticky bit su: false, // setuid sg: false, // setgid ru: false, // read user wu: false, // write user eu: false, // execute user rg: false, // read group wg: false, // write group eg: false, // execute group ro: false, // read other wo: false, // write other eo: false // execute other }; let d = 0, u = 0, g = 0, o = 0, output = "", octal = null, textual = null; if (input.search(/\s*[0-7]{1,4}\s*/i) === 0) { // Input is octal octal = input.match(/\s*([0-7]{1,4})\s*/i)[1]; if (octal.length === 4) { d = parseInt(octal[0], 8); u = parseInt(octal[1], 8); g = parseInt(octal[2], 8); o = parseInt(octal[3], 8); } else { if (octal.length > 0) u = parseInt(octal[0], 8); if (octal.length > 1) g = parseInt(octal[1], 8); if (octal.length > 2) o = parseInt(octal[2], 8); } perms.su = d >> 2 & 0x1; perms.sg = d >> 1 & 0x1; perms.sb = d & 0x1; perms.ru = u >> 2 & 0x1; perms.wu = u >> 1 & 0x1; perms.eu = u & 0x1; perms.rg = g >> 2 & 0x1; perms.wg = g >> 1 & 0x1; perms.eg = g & 0x1; perms.ro = o >> 2 & 0x1; perms.wo = o >> 1 & 0x1; perms.eo = o & 0x1; } else if (input.search(/\s*[dlpcbDrwxsStT-]{1,10}\s*/) === 0) { // Input is textual textual = input.match(/\s*([dlpcbDrwxsStT-]{1,10})\s*/)[1]; switch (textual[0]) { case "d": perms.d = true; break; case "l": perms.sl = true; break; case "p": perms.np = true; break; case "s": perms.s = true; break; case "c": perms.cd = true; break; case "b": perms.bd = true; break; case "D": perms.dr = true; break; } if (textual.length > 1) perms.ru = textual[1] === "r"; if (textual.length > 2) perms.wu = textual[2] === "w"; if (textual.length > 3) { switch (textual[3]) { case "x": perms.eu = true; break; case "s": perms.eu = true; perms.su = true; break; case "S": perms.su = true; break; } } if (textual.length > 4) perms.rg = textual[4] === "r"; if (textual.length > 5) perms.wg = textual[5] === "w"; if (textual.length > 6) { switch (textual[6]) { case "x": perms.eg = true; break; case "s": perms.eg = true; perms.sg = true; break; case "S": perms.sg = true; break; } } if (textual.length > 7) perms.ro = textual[7] === "r"; if (textual.length > 8) perms.wo = textual[8] === "w"; if (textual.length > 9) { switch (textual[9]) { case "x": perms.eo = true; break; case "t": perms.eo = true; perms.sb = true; break; case "T": perms.sb = true; break; } } } else { throw new OperationError("Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."); } output += "Textual representation: " + permsToStr(perms); output += "\nOctal representation: " + permsToOctal(perms); // File type if (textual) { output += "\nFile type: " + ftFromPerms(perms); } // setuid, setgid if (perms.su) { output += "\nThe setuid flag is set"; } if (perms.sg) { output += "\nThe setgid flag is set"; } // sticky bit if (perms.sb) { output += "\nThe sticky bit is set"; } // Permission matrix output += ` +---------+-------+-------+-------+ | | User | Group | Other | +---------+-------+-------+-------+ | Read | ${perms.ru ? "X" : " "} | ${perms.rg ? "X" : " "} | ${perms.ro ? "X" : " "} | +---------+-------+-------+-------+ | Write | ${perms.wu ? "X" : " "} | ${perms.wg ? "X" : " "} | ${perms.wo ? "X" : " "} | +---------+-------+-------+-------+ | Execute | ${perms.eu ? "X" : " "} | ${perms.eg ? "X" : " "} | ${perms.eo ? "X" : " "} | +---------+-------+-------+-------+`; return output; } } /** * Given a permissions object dictionary, generates a textual permissions string. * * @param {Object} perms * @returns {string} */ function permsToStr(perms) { let str = "", type = "-"; if (perms.d) type = "d"; if (perms.sl) type = "l"; if (perms.np) type = "p"; if (perms.s) type = "s"; if (perms.cd) type = "c"; if (perms.bd) type = "b"; if (perms.dr) type = "D"; str = type; str += perms.ru ? "r" : "-"; str += perms.wu ? "w" : "-"; if (perms.eu && perms.su) { str += "s"; } else if (perms.su) { str += "S"; } else if (perms.eu) { str += "x"; } else { str += "-"; } str += perms.rg ? "r" : "-"; str += perms.wg ? "w" : "-"; if (perms.eg && perms.sg) { str += "s"; } else if (perms.sg) { str += "S"; } else if (perms.eg) { str += "x"; } else { str += "-"; } str += perms.ro ? "r" : "-"; str += perms.wo ? "w" : "-"; if (perms.eo && perms.sb) { str += "t"; } else if (perms.sb) { str += "T"; } else if (perms.eo) { str += "x"; } else { str += "-"; } return str; } /** * Given a permissions object dictionary, generates an octal permissions string. * * @param {Object} perms * @returns {string} */ function permsToOctal(perms) { let d = 0, u = 0, g = 0, o = 0; if (perms.su) d += 4; if (perms.sg) d += 2; if (perms.sb) d += 1; if (perms.ru) u += 4; if (perms.wu) u += 2; if (perms.eu) u += 1; if (perms.rg) g += 4; if (perms.wg) g += 2; if (perms.eg) g += 1; if (perms.ro) o += 4; if (perms.wo) o += 2; if (perms.eo) o += 1; return d.toString() + u.toString() + g.toString() + o.toString(); } /** * Given a permissions object dictionary, returns the file type. * * @param {Object} perms * @returns {string} */ function ftFromPerms(perms) { if (perms.d) return "Directory"; if (perms.sl) return "Symbolic link"; if (perms.np) return "Named pipe"; if (perms.s) return "Socket"; if (perms.cd) return "Character device"; if (perms.bd) return "Block device"; if (perms.dr) return "Door"; return "Regular file"; } export default ParseUNIXFilePermissions; ================================================ FILE: src/core/operations/ParseURI.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import url from "url"; /** * Parse URI operation */ class ParseURI extends Operation { /** * ParseURI constructor */ constructor() { super(); this.name = "Parse URI"; this.module = "URL"; 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."; this.infoURL = "https://wikipedia.org/wiki/Uniform_Resource_Identifier"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const uri = url.parse(input, true); let output = ""; if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n"; if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n"; if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n"; if (uri.port) output += "Port:\t\t" + uri.port + "\n"; if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n"; if (uri.query) { const keys = Object.keys(uri.query); let padding = 0; keys.forEach(k => { padding = (k.length > padding) ? k.length : padding; }); output += "Arguments:\n"; for (const key in uri.query) { output += "\t" + key.padEnd(padding, " "); if (uri.query[key].length) { output += " = " + uri.query[key] + "\n"; } else { output += "\n"; } } } if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n"; return output; } } export default ParseURI; ================================================ FILE: src/core/operations/ParseUserAgent.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import UAParser from "ua-parser-js"; /** * Parse User Agent operation */ class ParseUserAgent extends Operation { /** * ParseUserAgent constructor */ constructor() { super(); this.name = "Parse User Agent"; this.module = "UserAgent"; this.description = "Attempts to identify and categorise information contained in a user-agent string."; this.infoURL = "https://wikipedia.org/wiki/User_agent"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = [ { pattern: "^(User-Agent:|Mozilla\\/)[^\\n\\r]+\\s*$", flags: "i", args: [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const ua = UAParser(input); return `Browser Name: ${ua.browser.name || "unknown"} Version: ${ua.browser.version || "unknown"} Device Model: ${ua.device.model || "unknown"} Type: ${ua.device.type || "unknown"} Vendor: ${ua.device.vendor || "unknown"} Engine Name: ${ua.engine.name || "unknown"} Version: ${ua.engine.version || "unknown"} OS Name: ${ua.os.name || "unknown"} Version: ${ua.os.version || "unknown"} CPU Architecture: ${ua.cpu.architecture || "unknown"}`; } } export default ParseUserAgent; ================================================ FILE: src/core/operations/ParseX509CRL.mjs ================================================ /** * @author robinsandhu * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; import { fromBase64 } from "../lib/Base64.mjs"; import { toHex } from "../lib/Hex.mjs"; import { formatDnObj } from "../lib/PublicKey.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; /** * Parse X.509 CRL operation */ class ParseX509CRL extends Operation { /** * ParseX509CRL constructor */ constructor() { super(); this.name = "Parse X.509 CRL"; this.module = "PublicKey"; this.description = "Parse Certificate Revocation List (CRL)"; this.infoURL = "https://wikipedia.org/wiki/Certificate_revocation_list"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Input format", "type": "option", "value": ["PEM", "DER Hex", "Base64", "Raw"] } ]; this.checks = [ { "pattern": "^-+BEGIN X509 CRL-+\\r?\\n[\\da-z+/\\n\\r]+-+END X509 CRL-+\\r?\\n?$", "flags": "i", "args": ["PEM"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} Human-readable description of a Certificate Revocation List (CRL). */ run(input, args) { if (!input.length) { return "No input"; } const inputFormat = args[0]; let undefinedInputFormat = false; try { switch (inputFormat) { case "DER Hex": input = input.replace(/\s/g, "").toLowerCase(); break; case "PEM": break; case "Base64": input = toHex(fromBase64(input, null, "byteArray"), ""); break; case "Raw": input = toHex(Utils.strToArrayBuffer(input), ""); break; default: undefinedInputFormat = true; } } catch (e) { throw "Certificate load error (non-certificate input?)"; } if (undefinedInputFormat) throw "Undefined input format"; const crl = new r.X509CRL(input); let out = `Certificate Revocation List (CRL): Version: ${crl.getVersion() === null ? "1 (0x0)" : "2 (0x1)"} Signature Algorithm: ${crl.getSignatureAlgorithmField()} Issuer:\n${formatDnObj(crl.getIssuer(), 8)} Last Update: ${generalizedDateTimeToUTC(crl.getThisUpdate())} Next Update: ${generalizedDateTimeToUTC(crl.getNextUpdate())}\n`; if (crl.getParam().ext !== undefined) { out += `\tCRL extensions:\n${formatCRLExtensions(crl.getParam().ext, 8)}\n`; } out += `Revoked Certificates:\n${formatRevokedCertificates(crl.getRevCertArray(), 4)} Signature Value:\n${formatCRLSignature(crl.getSignatureValueHex(), 8)}`; return out; } } /** * Generalized date time string to UTC. * @param {string} datetime * @returns UTC datetime string. */ function generalizedDateTimeToUTC(datetime) { // Ensure the string is in the correct format if (!/^\d{12,14}Z$/.test(datetime)) { throw new OperationError(`failed to format datetime string ${datetime}`); } // Extract components let centuary = "20"; if (datetime.length === 15) { centuary = datetime.substring(0, 2); datetime = datetime.slice(2); } const year = centuary + datetime.substring(0, 2); const month = datetime.substring(2, 4); const day = datetime.substring(4, 6); const hour = datetime.substring(6, 8); const minute = datetime.substring(8, 10); const second = datetime.substring(10, 12); // Construct ISO 8601 format string const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`; // Parse using standard Date object const isoDateTime = new Date(isoString); return isoDateTime.toUTCString(); } /** * Format CRL extensions. * @param {r.ExtParam[] | undefined} extensions * @param {Number} indent * @returns Formatted string detailing CRL extensions. */ function formatCRLExtensions(extensions, indent) { if (Array.isArray(extensions) === false || extensions.length === 0) { return indentString(`No CRL extensions.`, indent); } let out = ``; extensions.sort((a, b) => { if (!Object.hasOwn(a, "extname") || !Object.hasOwn(b, "extname")) { return 0; } if (a.extname < b.extname) { return -1; } else if (a.extname === b.extname) { return 0; } else { return 1; } }); extensions.forEach((ext) => { if (!Object.hasOwn(ext, "extname")) { throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`); } switch (ext.extname) { case "authorityKeyIdentifier": out += `X509v3 Authority Key Identifier:\n`; if (Object.hasOwn(ext, "kid")) { out += `\tkeyid:${colonDelimitedHexFormatString(ext.kid.hex.toUpperCase())}\n`; } if (Object.hasOwn(ext, "issuer")) { out += `\tDirName:${ext.issuer.str}\n`; } if (Object.hasOwn(ext, "sn")) { out += `\tserial:${colonDelimitedHexFormatString(ext.sn.hex.toUpperCase())}\n`; } break; case "cRLDistributionPoints": out += `X509v3 CRL Distribution Points:\n`; ext.array.forEach((distPoint) => { const fullName = `Full Name:\n${formatGeneralNames(distPoint.dpname.full, 4)}`; out += indentString(fullName, 4) + "\n"; }); break; case "cRLNumber": if (!Object.hasOwn(ext, "num")) { throw new OperationError(`'cRLNumber' CRL entry extension missing 'num' key: ${ext}`); } out += `X509v3 CRL Number:\n\t${ext.num.hex.toUpperCase()}\n`; break; case "issuerAltName": out += `X509v3 Issuer Alternative Name:\n${formatGeneralNames(ext.array, 4)}\n`; break; default: out += `${ext.extname}:\n`; out += `\tUnsupported CRL extension. Try openssl CLI.\n`; break; } }); return indentString(chop(out), indent); } /** * Format general names array. * @param {Object[]} names * @returns Multi-line formatted string describing all supported general name types. */ function formatGeneralNames(names, indent) { let out = ``; names.forEach((name) => { const key = Object.keys(name)[0]; switch (key) { case "ip": out += `IP:${name.ip}\n`; break; case "dns": out += `DNS:${name.dns}\n`; break; case "uri": out += `URI:${name.uri}\n`; break; case "rfc822": out += `EMAIL:${name.rfc822}\n`; break; case "dn": out += `DIR:${name.dn.str}\n`; break; case "other": out += `OtherName:${name.other.oid}::${Object.values(name.other.value)[0].str}\n`; break; default: out += `${key}: unsupported general name type`; break; } }); return indentString(chop(out), indent); } /** * Colon-delimited hex formatted output. * @param {string} hexString Hex String * @returns String representing input hex string with colon delimiter. */ function colonDelimitedHexFormatString(hexString) { if (hexString.length % 2 !== 0) { hexString = "0" + hexString; } return chop(hexString.replace(/(..)/g, "$&:")); } /** * Format revoked certificates array * @param {r.RevokedCertificate[] | null} revokedCertificates * @param {Number} indent * @returns Multi-line formatted string output of revoked certificates array */ function formatRevokedCertificates(revokedCertificates, indent) { if (Array.isArray(revokedCertificates) === false || revokedCertificates.length === 0) { return indentString("No Revoked Certificates.", indent); } let out=``; revokedCertificates.forEach((revCert) => { if (!Object.hasOwn(revCert, "sn") || !Object.hasOwn(revCert, "date")) { throw new OperationError("invalid revoked certificate object, missing either serial number or date"); } out += `Serial Number: ${revCert.sn.hex.toUpperCase()} Revocation Date: ${generalizedDateTimeToUTC(revCert.date)}\n`; if (Object.hasOwn(revCert, "ext") && Array.isArray(revCert.ext) && revCert.ext.length !== 0) { out += `\tCRL entry extensions:\n${indentString(formatCRLEntryExtensions(revCert.ext), 2*indent)}\n`; } }); return indentString(chop(out), indent); } /** * Format CRL entry extensions. * @param {Object[]} exts * @returns Formatted multi-line string describing CRL entry extensions. */ function formatCRLEntryExtensions(exts) { let out = ``; const crlReasonCodeToReasonMessage = { 0: "Unspecified", 1: "Key Compromise", 2: "CA Compromise", 3: "Affiliation Changed", 4: "Superseded", 5: "Cessation Of Operation", 6: "Certificate Hold", 8: "Remove From CRL", 9: "Privilege Withdrawn", 10: "AA Compromise", }; const holdInstructionOIDToName = { "1.2.840.10040.2.1": "Hold Instruction None", "1.2.840.10040.2.2": "Hold Instruction Call Issuer", "1.2.840.10040.2.3": "Hold Instruction Reject", }; exts.forEach((ext) => { if (!Object.hasOwn(ext, "extname")) { throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`); } switch (ext.extname) { case "cRLReason": if (!Object.hasOwn(ext, "code")) { throw new OperationError(`'cRLReason' CRL entry extension missing 'code' key: ${ext}`); } out += `X509v3 CRL Reason Code: ${Object.hasOwn(crlReasonCodeToReasonMessage, ext.code) ? crlReasonCodeToReasonMessage[ext.code] : `invalid reason code: ${ext.code}`}\n`; break; case "2.5.29.23": // Hold instruction out += `Hold Instruction Code:\n\t${Object.hasOwn(holdInstructionOIDToName, ext.extn.oid) ? holdInstructionOIDToName[ext.extn.oid] : `${ext.extn.oid}: unknown hold instruction OID`}\n`; break; case "2.5.29.24": // Invalidity Date out += `Invalidity Date:\n\t${generalizedDateTimeToUTC(ext.extn.gentime.str)}\n`; break; default: out += `${ext.extname}:\n`; out += `\tUnsupported CRL entry extension. Try openssl CLI.\n`; break; } }); return chop(out); } /** * Format CRL signature. * @param {String} sigHex * @param {Number} indent * @returns String representing hex signature value formatted on multiple lines. */ function formatCRLSignature(sigHex, indent) { if (sigHex.length % 2 !== 0) { sigHex = "0" + sigHex; } return indentString(formatMultiLine(chop(sigHex.replace(/(..)/g, "$&:"))), indent); } /** * Format string onto multiple lines. * @param {string} longStr * @returns String as a multi-line string. */ function formatMultiLine(longStr) { const lines = []; for (let remain = longStr ; remain !== "" ; remain = remain.substring(54)) { lines.push(remain.substring(0, 54)); } return lines.join("\n"); } /** * Indent a multi-line string by n spaces. * @param {string} input String * @param {number} spaces How many leading spaces * @returns Indented string. */ function indentString(input, spaces) { const indent = " ".repeat(spaces); return input.replace(/^/gm, indent); } /** * Remove last character from a string. * @param {string} s String * @returns Chopped string. */ function chop(s) { if (s.length < 1) { return s; } return s.substring(0, s.length - 1); } export default ParseX509CRL; ================================================ FILE: src/core/operations/ParseX509Certificate.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import r from "jsrsasign"; import { fromBase64 } from "../lib/Base64.mjs"; import { runHash } from "../lib/Hash.mjs"; import { fromHex, toHex } from "../lib/Hex.mjs"; import { formatByteStr, formatDnObj } from "../lib/PublicKey.mjs"; import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Parse X.509 certificate operation */ class ParseX509Certificate extends Operation { /** * ParseX509Certificate constructor */ constructor() { super(); this.name = "Parse X.509 certificate"; this.module = "PublicKey"; 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.

This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.

Tags: X509, server hello, handshake"; this.infoURL = "https://wikipedia.org/wiki/X.509"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Input format", "type": "option", "value": ["PEM", "DER Hex", "Base64", "Raw"] } ]; this.checks = [ { "pattern": "^-+BEGIN CERTIFICATE-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE-+\\r?\\n?$", "flags": "i", "args": ["PEM"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!input.length) { return "No input"; } const cert = new r.X509(), inputFormat = args[0]; let undefinedInputFormat = false; try { switch (inputFormat) { case "DER Hex": input = input.replace(/\s/g, "").toLowerCase(); cert.readCertHex(input); break; case "PEM": cert.readCertPEM(input); break; case "Base64": cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), "")); break; case "Raw": cert.readCertHex(toHex(Utils.strToArrayBuffer(input), "")); break; default: undefinedInputFormat = true; } } catch (e) { throw "Certificate load error (non-certificate input?)"; } if (undefinedInputFormat) throw "Undefined input format"; const hex = Utils.strToArrayBuffer(Utils.byteArrayToChars(fromHex(cert.hex))), sn = cert.getSerialNumberHex(), issuer = cert.getIssuer(), subject = cert.getSubject(), pk = cert.getPublicKey(), pkFields = [], sig = cert.getSignatureValueHex(); let pkStr = "", sigStr = "", extensions = ""; // Public Key fields pkFields.push({ key: "Algorithm", value: pk.type }); if (pk.type === "EC") { // ECDSA pkFields.push({ key: "Curve Name", value: pk.curveName }); pkFields.push({ key: "Length", value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + " bits" }); pkFields.push({ key: "pub", value: formatByteStr(pk.pubKeyHex, 16, 18) }); } else if (pk.type === "DSA") { // DSA pkFields.push({ key: "pub", value: formatByteStr(pk.y.toString(16), 16, 18) }); pkFields.push({ key: "P", value: formatByteStr(pk.p.toString(16), 16, 18) }); pkFields.push({ key: "Q", value: formatByteStr(pk.q.toString(16), 16, 18) }); pkFields.push({ key: "G", value: formatByteStr(pk.g.toString(16), 16, 18) }); } else if (pk.e) { // RSA pkFields.push({ key: "Length", value: pk.n.bitLength() + " bits" }); pkFields.push({ key: "Modulus", value: formatByteStr(pk.n.toString(16), 16, 18) }); pkFields.push({ key: "Exponent", value: pk.e + " (0x" + pk.e.toString(16) + ")" }); } else { pkFields.push({ key: "Error", value: "Unknown Public Key type" }); } // Format Public Key fields for (let i = 0; i < pkFields.length; i++) { pkStr += ` ${pkFields[i].key}:${(pkFields[i].value + "\n").padStart( 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, " " )}`; } // Signature fields let breakoutSig = false; try { breakoutSig = r.ASN1HEX.dump(sig).indexOf("SEQUENCE") === 0; } catch (err) { // Error processing signature, output without further breakout } if (breakoutSig) { // DSA or ECDSA sigStr = ` r: ${formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18)} s: ${formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18)}`; } else { // RSA or unknown sigStr = ` Signature: ${formatByteStr(sig, 16, 18)}`; } // Extensions try { extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; } catch (err) {} const issuerStr = formatDnObj(issuer, 2), nbDate = formatDate(cert.getNotBefore()), naDate = formatDate(cert.getNotAfter()), subjectStr = formatDnObj(subject, 2); return `Version: ${cert.version} (0x${Utils.hex(cert.version - 1)}) Serial number: ${new r.BigInteger(sn, 16).toString()} (0x${sn}) Algorithm ID: ${cert.getSignatureAlgorithmField()} Validity Not Before: ${nbDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotBefore()}) Not After: ${naDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotAfter()}) Issuer ${issuerStr} Subject ${subjectStr} Fingerprints MD5: ${runHash("md5", hex)} SHA1: ${runHash("sha1", hex)} SHA256: ${runHash("sha256", hex)} Public Key ${pkStr.slice(0, -1)} Certificate Signature Algorithm: ${cert.getSignatureAlgorithmName()} ${sigStr} Extensions ${extensions}`; } } /** * Formats dates. * * @param {string} dateStr * @returns {string} */ function formatDate (dateStr) { if (dateStr.length === 13) { // UTC Time dateStr = (dateStr[0] < "5" ? "20" : "19") + dateStr; } return dateStr[6] + dateStr[7] + "/" + dateStr[4] + dateStr[5] + "/" + dateStr[0] + dateStr[1] + dateStr[2] + dateStr[3] + " " + dateStr[8] + dateStr[9] + ":" + dateStr[10] + dateStr[11] + ":" + dateStr[12] + dateStr[13]; } export default ParseX509Certificate; ================================================ FILE: src/core/operations/PlayMedia.mjs ================================================ /** * @author anthony-arnold [anthony.arnold@uqconnect.edu.au] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import { fromBase64, toBase64 } from "../lib/Base64.mjs"; import { fromHex } from "../lib/Hex.mjs"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { isType, detectFileType } from "../lib/FileType.mjs"; /** * PlayMedia operation */ class PlayMedia extends Operation { /** * PlayMedia constructor */ constructor() { super(); this.name = "Play Media"; this.module = "Default"; this.description = "Plays the input as audio or video depending on the type.

Tags: sound, movie, mp3, mp4, mov, webm, wav, ogg"; this.infoURL = ""; this.inputType = "string"; this.outputType = "byteArray"; this.presentType = "html"; this.args = [ { "name": "Input format", "type": "option", "value": ["Raw", "Base64", "Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} The multimedia data as bytes. */ run(input, args) { const [inputFormat] = args; if (!input.length) return []; // Convert input to raw bytes switch (inputFormat) { case "Hex": input = fromHex(input); break; case "Base64": // Don't trust the Base64 entered by the user. // Unwrap it first, then re-encode later. input = fromBase64(input, undefined, "byteArray"); break; case "Raw": default: input = Utils.strToByteArray(input); break; } // Determine file type if (!isType(/^(audio|video)/, input)) { throw new OperationError("Invalid or unrecognised file type"); } return input; } /** * Displays an audio or video element that may be able to play the media * file. * * @param {byteArray} data Data containing an audio or video file. * @returns {string} Markup to display a media player. */ async present(data) { if (!data.length) return ""; const types = detectFileType(data); const matches = /^audio|video/.exec(types[0].mime); if (!matches) { throw new OperationError("Invalid file type"); } const dataURI = `data:${types[0].mime};base64,${toBase64(data)}`; const element = matches[0]; let html = `<${element} src='${dataURI}' type='${types[0].mime}' controls>`; html += "

Unsupported media type.

"; html += ``; return html; } } export default PlayMedia; ================================================ FILE: src/core/operations/PowerSet.mjs ================================================ /** * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Power Set operation */ class PowerSet extends Operation { /** * Power set constructor */ constructor() { super(); this.name = "Power Set"; this.module = "Default"; this.description = "Calculates all the subsets of a set."; this.infoURL = "https://wikipedia.org/wiki/Power_set"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Item delimiter", type: "binaryString", value: "," }, ]; } /** * Generate the power set * * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { [this.itemDelimiter] = args; // Split and filter empty strings const inputArray = input.split(this.itemDelimiter).filter(a => a); if (inputArray.length) { return this.runPowerSet(inputArray); } return ""; } /** * Return the power set of the inputted set. * * @param {Object[]} a * @returns {Object[]} */ runPowerSet(a) { // empty array items getting picked up a = a.filter(i => i.length); if (!a.length) { return []; } /** * Decimal to binary function * @param {*} dec */ const toBinary = (dec) => (dec >>> 0).toString(2); const result = new Set(); // Get the decimal number to make a binary as long as the input const maxBinaryValue = parseInt(Number(a.map(i => "1").reduce((p, c) => p + c)), 2); // Make an array of each binary number from 0 to maximum const binaries = [...Array(maxBinaryValue + 1).keys()] .map(toBinary) .map(i => i.padStart(toBinary(maxBinaryValue).length, "0")); // XOR the input with each binary to get each unique permutation binaries.forEach((binary) => { const split = binary.split(""); result.add(a.filter((item, index) => split[index] === "1")); }); // map for formatting & put in length order. return [...result] .map(r => r.join(this.itemDelimiter)).sort((a, b) => a.length - b.length) .map(i => `${i}\n`).join(""); } } export default PowerSet; ================================================ FILE: src/core/operations/ProtobufDecode.mjs ================================================ /** * @author GCHQ Contributor [3] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Protobuf from "../lib/Protobuf.mjs"; /** * Protobuf Decode operation */ class ProtobufDecode extends Operation { /** * ProtobufDecode constructor */ constructor() { super(); this.name = "Protobuf Decode"; this.module = "Protobuf"; this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.

If a .proto schema is defined, the encoded data will be decoded with reference to the schema. Only one message instance will be decoded.

Show Unknown Fields
When a schema is used, this option shows fields that are present in the input data but not defined in the schema.

Show Types
Show the type of a field next to its name. For undefined fields, the wiretype and example types are shown instead."; this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers"; this.inputType = "ArrayBuffer"; this.outputType = "JSON"; this.args = [ { name: "Schema (.proto text)", type: "text", value: "", rows: 8, hint: "Drag and drop is enabled on this ingredient" }, { name: "Show Unknown Fields", type: "boolean", value: false }, { name: "Show Types", type: "boolean", value: false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {JSON} */ run(input, args) { input = new Uint8Array(input); try { return Protobuf.decode(input, args); } catch (err) { throw new OperationError(err); } } } export default ProtobufDecode; ================================================ FILE: src/core/operations/ProtobufEncode.mjs ================================================ /** * @author GCHQ Contributor [3] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Protobuf from "../lib/Protobuf.mjs"; /** * Protobuf Encode operation */ class ProtobufEncode extends Operation { /** * ProtobufEncode constructor */ constructor() { super(); this.name = "Protobuf Encode"; this.module = "Protobuf"; this.description = "Encodes a valid JSON object into a protobuf byte array using the input .proto schema."; this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding"; this.inputType = "JSON"; this.outputType = "ArrayBuffer"; this.args = [ { name: "Schema (.proto text)", type: "text", value: "", rows: 8, hint: "Drag and drop is enabled on this ingredient" } ]; } /** * @param {Object} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { try { return Protobuf.encode(input, args); } catch (error) { throw new OperationError(error); } } } export default ProtobufEncode; ================================================ FILE: src/core/operations/PseudoRandomIntegerGenerator.mjs ================================================ /** * @author cktgh [chankaitung@gmail.com] * @copyright Crown Copyright 2026 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import forge from "node-forge"; import Utils, { isWorkerEnvironment } from "../Utils.mjs"; import { DELIM_OPTIONS } from "../lib/Delim.mjs"; /** * Pseudo-Random Integer Generator operation */ class PseudoRandomIntegerGenerator extends Operation { // in theory 2**53 is the max range, but we use Number.MAX_SAFE_INTEGER (2**53 - 1) as it is more consistent. static MAX_RANGE = Number.MAX_SAFE_INTEGER; // arbitrary choice static BUFFER_SIZE = 1024; /** * PseudoRandomIntegerGenerator constructor */ constructor() { super(); this.name = "Pseudo-Random Integer Generator"; this.module = "Ciphers"; this.description = "A cryptographically-secure pseudo-random number generator (PRNG).

Generates random integers within a specified range using the browser's built-in crypto.getRandomValues() method if available.

The supported range of integers is from -(2^53 - 1) to (2^53 - 1)."; this.infoURL = "https://wikipedia.org/wiki/Pseudorandom_number_generator"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Number of Integers", "type": "number", "value": 1, "min": 1 }, { "name": "Min Value", "type": "number", "value": 0, "min": Number.MIN_SAFE_INTEGER, "max": Number.MAX_SAFE_INTEGER }, { "name": "Max Value", "type": "number", "value": 99, "min": Number.MIN_SAFE_INTEGER, "max": Number.MAX_SAFE_INTEGER }, { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS }, { "name": "Output", "type": "option", "value": ["Raw", "Hex", "Decimal"] } ]; // not using BigUint64Array to avoid BigInt handling overhead this.randomBuffer = new Uint32Array(PseudoRandomIntegerGenerator.BUFFER_SIZE); this.randomBufferOffset = PseudoRandomIntegerGenerator.BUFFER_SIZE; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [numInts, minInt, maxInt, delimiter, outputType] = args; if (minInt === null || maxInt === null) return ""; const min = Math.ceil(minInt); const max = Math.floor(maxInt); const delim = Utils.charRep(delimiter || "Space"); if (!Number.isSafeInteger(min) || !Number.isSafeInteger(max)) { throw new OperationError("Min and Max must be between `-(2^53 - 1)` and `2^53 - 1`."); } if (min > max) { throw new OperationError("Min cannot be larger than Max."); } const range = max - min + 1; // inclusive range if (range > PseudoRandomIntegerGenerator.MAX_RANGE) { throw new OperationError("Range between Min and Max cannot be larger than `2^53`"); } // as large as possible while divisible by range const rejectionThreshold = PseudoRandomIntegerGenerator.MAX_RANGE - (PseudoRandomIntegerGenerator.MAX_RANGE % range); const output = []; for (let i = 0; i < numInts; i++) { const result = this._generateRandomValue(rejectionThreshold); const intValue = min + (result % range); switch (outputType) { case "Hex": output.push(intValue.toString(16)); break; case "Decimal": output.push(intValue.toString(10)); break; case "Raw": default: output.push(Utils.chr(intValue)); } } if (outputType === "Raw") { return output.join(""); } return output.join(delim); } /** * Generate a random value, result will be less than the rejection threshold (exclusive). * * @param {number} rejectionThreshold * @returns {number} */ _generateRandomValue(rejectionThreshold) { let result; do { if (this.randomBufferOffset + 2 > this.randomBuffer.length) { this._resetRandomBuffer(); } // stitching a 53 bit number; not using BigUint64Array to avoid BigInt handling overhead result = (this.randomBuffer[this.randomBufferOffset++] & 0x1f_ffff) * 0x1_0000_0000 + this.randomBuffer[this.randomBufferOffset++]; } while (result >= rejectionThreshold); return result; } /** * Fill random buffer with new random values and rseet the offset. */ _resetRandomBuffer() { if (isWorkerEnvironment() && self.crypto) { self.crypto.getRandomValues(this.randomBuffer); } else { const bytes = forge.random.getBytesSync(this.randomBuffer.length * 4); for (let j = 0; j < this.randomBuffer.length; j++) { this.randomBuffer[j] = (bytes.charCodeAt(j * 4) << 24) | (bytes.charCodeAt(j * 4 + 1) << 16) | (bytes.charCodeAt(j * 4 + 2) << 8) | bytes.charCodeAt(j * 4 + 3); } } this.randomBufferOffset = 0; } } export default PseudoRandomIntegerGenerator; ================================================ FILE: src/core/operations/PseudoRandomNumberGenerator.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import forge from "node-forge"; import BigNumber from "bignumber.js"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * Pseudo-Random Number Generator operation */ class PseudoRandomNumberGenerator extends Operation { /** * PseudoRandomNumberGenerator constructor */ constructor() { super(); this.name = "Pseudo-Random Number Generator"; this.module = "Ciphers"; this.description = "A cryptographically-secure pseudo-random number generator (PRNG).

This operation uses the browser's built-in crypto.getRandomValues() method if available. If this cannot be found, it falls back to a Fortuna-based PRNG algorithm."; this.infoURL = "https://wikipedia.org/wiki/Pseudorandom_number_generator"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Number of bytes", "type": "number", "value": 32 }, { "name": "Output as", "type": "option", "value": ["Hex", "Integer", "Byte array", "Raw"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [numBytes, outputAs] = args; let bytes; if (isWorkerEnvironment() && self.crypto) { bytes = new ArrayBuffer(numBytes); const CHUNK_SIZE = 65536; for (let i = 0; i < numBytes; i += CHUNK_SIZE) { self.crypto.getRandomValues(new Uint8Array(bytes, i, Math.min(numBytes - i, CHUNK_SIZE))); } bytes = Utils.arrayBufferToStr(bytes); } else { bytes = forge.random.getBytesSync(numBytes); } let value = new BigNumber(0), i; switch (outputAs) { case "Hex": return forge.util.bytesToHex(bytes); case "Integer": for (i = bytes.length - 1; i >= 0; i--) { value = value.times(256).plus(bytes.charCodeAt(i)); } return value.toFixed(); case "Byte array": return JSON.stringify(Utils.strToCharcode(bytes)); case "Raw": default: return bytes; } } } export default PseudoRandomNumberGenerator; ================================================ FILE: src/core/operations/PubKeyFromCert.mjs ================================================ /** * @author cplussharp * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Public Key from Certificate operation */ class PubKeyFromCert extends Operation { /** * PubKeyFromCert constructor */ constructor() { super(); this.name = "Public Key from Certificate"; this.module = "PublicKey"; this.description = "Extracts the Public Key from a Certificate."; this.infoURL = "https://en.wikipedia.org/wiki/X.509"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let output = ""; let match; const regex = /-----BEGIN CERTIFICATE-----/g; while ((match = regex.exec(input)) !== null) { // find corresponding end tag const indexBase64 = match.index + match[0].length; const footer = "-----END CERTIFICATE-----"; const indexFooter = input.indexOf(footer, indexBase64); if (indexFooter === -1) { throw new OperationError(`PEM footer '${footer}' not found`); } const certPem = input.substring(match.index, indexFooter + footer.length); const cert = new r.X509(); cert.readCertPEM(certPem); let pubKey; try { pubKey = cert.getPublicKey(); } catch { throw new OperationError("Unsupported public key type"); } const pubKeyPem = r.KEYUTIL.getPEM(pubKey); // PEM ends with '\n', so a new key always starts on a new line output += pubKeyPem; } return output; } } export default PubKeyFromCert; ================================================ FILE: src/core/operations/PubKeyFromPrivKey.mjs ================================================ /** * @author cplussharp * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import r from "jsrsasign"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Public Key from Private Key operation */ class PubKeyFromPrivKey extends Operation { /** * PubKeyFromPrivKey constructor */ constructor() { super(); this.name = "Public Key from Private Key"; this.module = "PublicKey"; this.description = "Extracts the Public Key from a Private Key."; this.infoURL = "https://en.wikipedia.org/wiki/PKCS_8"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let output = ""; let match; const regex = /-----BEGIN ((RSA |EC |DSA )?PRIVATE KEY)-----/g; while ((match = regex.exec(input)) !== null) { // find corresponding end tag const indexBase64 = match.index + match[0].length; const footer = `-----END ${match[1]}-----`; const indexFooter = input.indexOf(footer, indexBase64); if (indexFooter === -1) { throw new OperationError(`PEM footer '${footer}' not found`); } const privKeyPem = input.substring(match.index, indexFooter + footer.length); let privKey; try { privKey = r.KEYUTIL.getKey(privKeyPem); } catch (err) { throw new OperationError(`Unsupported key type: ${err}`); } let pubKey; if (privKey.type && privKey.type === "EC") { pubKey = new r.KJUR.crypto.ECDSA({ curve: privKey.curve }); pubKey.setPublicKeyHex(privKey.generatePublicKeyHex()); } else if (privKey.type && privKey.type === "DSA") { if (!privKey.y) { throw new OperationError(`DSA Private Key in PKCS#8 is not supported`); } pubKey = new r.KJUR.crypto.DSA(); pubKey.setPublic(privKey.p, privKey.q, privKey.g, privKey.y); } else if (privKey.n && privKey.e) { pubKey = new r.RSAKey(); pubKey.setPublic(privKey.n, privKey.e); } else { throw new OperationError(`Unsupported key type`); } const pubKeyPem = r.KEYUTIL.getPEM(pubKey); // PEM ends with '\n', so a new key always starts on a new line output += pubKeyPem; } return output; } } export default PubKeyFromPrivKey; ================================================ FILE: src/core/operations/RAKE.mjs ================================================ /** * @author sw5678 * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * RAKE operation */ class RAKE extends Operation { /** * RAKE constructor */ constructor() { super(); this.name = "RAKE"; this.module = "Default"; this.description = [ "Rapid Keyword Extraction (RAKE)", "

", "RAKE is a domain-independent keyword extraction algorithm in Natural Language Processing.", "

", "The list of stop words are from the NLTK python package", ].join("\n"); this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Word Delimiter (Regex)", type: "text", value: "\\s" }, { name: "Sentence Delimiter (Regex)", type: "text", value: "\\.\\s|\\n" }, { name: "Stop Words", type: "text", 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" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { // Get delimiter regexs const wordDelim = new RegExp(args[0], "g"); const sentDelim = new RegExp(args[1], "g"); // Deduplicate the stop words and add the empty string const stopWords = args[2].toLowerCase().replace(/ /g, "").split(",").unique(); stopWords.push(""); // Lower case input and remove start and ending whitespace input = input.toLowerCase().trim(); // Get tokens, token count, and phrases const tokens = []; const wordFrequencies = []; let phrases = []; // Build up list of phrases and token counts const sentences = input.split(sentDelim); for (const sent of sentences) { // Split sentence into words const splitSent = sent.split(wordDelim); let startIndex = 0; for (let i = 0; i < splitSent.length; i++) { const token = splitSent[i]; if (stopWords.includes(token)) { // If token is stop word then split to create phrase phrases.push(splitSent.slice(startIndex, i)); startIndex = i + 1; } else { // If token is not a stop word add to the count of the list of words if (tokens.includes(token)) { wordFrequencies[tokens.indexOf(token)]+=1; } else { tokens.push(token); wordFrequencies.push(1); } } } phrases.push(splitSent.slice(startIndex)); } // remove empty phrases phrases = phrases.filter(subArray => subArray.length > 0); // Remove duplicate phrases phrases = phrases.unique(); // Generate word_degree_matrix and populate const wordDegreeMatrix = Array(tokens.length).fill().map(() => Array(tokens.length).fill(0)); for (const phrase of phrases) { for (const word1 of phrase) { for (const word2 of phrase) { wordDegreeMatrix[tokens.indexOf(word1)][tokens.indexOf(word2)]++; } } } // Calculate degree score for each token const degreeScores = Array(tokens.length).fill(0); for (let i=0; i b[0] - a[0]); scores.unshift(new Array("Scores: ", "Keywords: ")); // Output works with the 'To Table' functionality already built into CC return scores.map(function (score) { return score.join(", "); }).join("\n"); } } export default RAKE; ================================================ FILE: src/core/operations/RC2Decrypt.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import forge from "node-forge"; /** * RC2 Decrypt operation */ class RC2Decrypt extends Operation { /** * RC2Decrypt constructor */ constructor() { super(); this.name = "RC2 Decrypt"; this.module = "Ciphers"; this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.

Key: RC2 uses a variable size key.

IV: 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.

Padding: In both CBC and ECB mode, PKCS#7 padding will be used."; this.infoURL = "https://wikipedia.org/wiki/RC2"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Input", "type": "option", "value": ["Hex", "Raw"] }, { "name": "Output", "type": "option", "value": ["Raw", "Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteString(args[0].string, args[0].option), iv = Utils.convertToByteString(args[1].string, args[1].option), [,, inputType, outputType] = args, decipher = forge.rc2.createDecryptionCipher(key); input = Utils.convertToByteString(input, inputType); decipher.start(iv || null); decipher.update(forge.util.createBuffer(input)); decipher.finish(); return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); } } export default RC2Decrypt; ================================================ FILE: src/core/operations/RC2Encrypt.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import forge from "node-forge"; /** * RC2 Encrypt operation */ class RC2Encrypt extends Operation { /** * RC2Encrypt constructor */ constructor() { super(); this.name = "RC2 Encrypt"; this.module = "Ciphers"; this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.

Key: RC2 uses a variable size key.

You can generate a password-based key using one of the KDF operations.

IV: 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.

Padding: In both CBC and ECB mode, PKCS#7 padding will be used."; this.infoURL = "https://wikipedia.org/wiki/RC2"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Input", "type": "option", "value": ["Raw", "Hex"] }, { "name": "Output", "type": "option", "value": ["Hex", "Raw"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteString(args[0].string, args[0].option), iv = Utils.convertToByteString(args[1].string, args[1].option), [,, inputType, outputType] = args, cipher = forge.rc2.createEncryptionCipher(key); input = Utils.convertToByteString(input, inputType); cipher.start(iv || null); cipher.update(forge.util.createBuffer(input)); cipher.finish(); return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); } } export default RC2Encrypt; ================================================ FILE: src/core/operations/RC4.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import CryptoJS from "crypto-js"; import { format } from "../lib/Ciphers.mjs"; /** * RC4 operation */ class RC4 extends Operation { /** * RC4 constructor */ constructor() { super(); this.name = "RC4"; this.module = "Ciphers"; 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."; this.infoURL = "https://wikipedia.org/wiki/RC4"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Passphrase", "type": "toggleString", "value": "", "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"] }, { "name": "Input format", "type": "option", "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] }, { "name": "Output format", "type": "option", "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const message = format[args[1]].parse(input), passphrase = format[args[0].option].parse(args[0].string), encrypted = CryptoJS.RC4.encrypt(message, passphrase); return encrypted.ciphertext.toString(format[args[2]]); } /** * Highlight RC4 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight RC4 in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default RC4; ================================================ FILE: src/core/operations/RC4Drop.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { format } from "../lib/Ciphers.mjs"; import CryptoJS from "crypto-js"; /** * RC4 Drop operation */ class RC4Drop extends Operation { /** * RC4Drop constructor */ constructor() { super(); this.name = "RC4 Drop"; this.module = "Ciphers"; 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."; this.infoURL = "https://wikipedia.org/wiki/RC4#Fluhrer,_Mantin_and_Shamir_attack"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Passphrase", "type": "toggleString", "value": "", "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"] }, { "name": "Input format", "type": "option", "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] }, { "name": "Output format", "type": "option", "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] }, { "name": "Number of dwords to drop", "type": "number", "value": 192 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const message = format[args[1]].parse(input), passphrase = format[args[0].option].parse(args[0].string), drop = args[3], encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop }); return encrypted.ciphertext.toString(format[args[2]]); } /** * Highlight RC4 Drop * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight RC4 Drop in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default RC4Drop; ================================================ FILE: src/core/operations/RC6Decrypt.mjs ================================================ /** * @author Medjedtxm * @copyright Crown Copyright 2026 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import { toHex } from "../lib/Hex.mjs"; import { decryptRC6, getBlockSize, getDefaultRounds } from "../lib/RC6.mjs"; /** * RC6 Decrypt operation */ class RC6Decrypt extends Operation { /** * RC6Decrypt constructor */ constructor() { super(); this.name = "RC6 Decrypt"; this.module = "Ciphers"; 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.

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.

IV: 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.

Padding: In CBC and ECB mode, the PKCS#7 padding scheme is used."; this.infoURL = "https://wikipedia.org/wiki/RC6"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Mode", "type": "option", "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] }, { "name": "Input", "type": "option", "value": ["Hex", "Raw"] }, { "name": "Output", "type": "option", "value": ["Raw", "Hex"] }, { "name": "Padding", "type": "option", "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] }, { "name": "Word Size", "type": "number", "value": 32, "min": 8, "max": 256, "step": 8 }, { "name": "Rounds", "type": "number", "value": 20, "min": 1, "max": 255 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), iv = Utils.convertToByteArray(args[1].string, args[1].option), [,, mode, inputType, outputType, padding, wordSize, rounds] = args; // Validate word size if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0) throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`); const blockSize = getBlockSize(wordSize); const defaultRounds = getDefaultRounds(wordSize); if (iv.length !== blockSize && iv.length !== 0 && mode !== "ECB") throw new OperationError(`Invalid IV length: ${iv.length} bytes RC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits). Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255) throw new OperationError(`Invalid number of rounds: ${rounds} Rounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`); // Default IV to null bytes if empty (like AES) const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv; input = Utils.convertToByteArray(input, inputType); const output = decryptRC6(input, key, actualIv, mode, padding, rounds, wordSize); return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); } } export default RC6Decrypt; ================================================ FILE: src/core/operations/RC6Encrypt.mjs ================================================ /** * @author Medjedtxm * @copyright Crown Copyright 2026 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import { toHex } from "../lib/Hex.mjs"; import { encryptRC6, getBlockSize, getDefaultRounds } from "../lib/RC6.mjs"; /** * RC6 Encrypt operation */ class RC6Encrypt extends Operation { /** * RC6Encrypt constructor */ constructor() { super(); this.name = "RC6 Encrypt"; this.module = "Ciphers"; 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.

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.

IV: 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.

Padding: In CBC and ECB mode, the PKCS#7 padding scheme is used."; this.infoURL = "https://wikipedia.org/wiki/RC6"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Mode", "type": "option", "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] }, { "name": "Input", "type": "option", "value": ["Raw", "Hex"] }, { "name": "Output", "type": "option", "value": ["Hex", "Raw"] }, { "name": "Padding", "type": "option", "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] }, { "name": "Word Size", "type": "number", "value": 32, "min": 8, "max": 256, "step": 8 }, { "name": "Rounds", "type": "number", "value": 20, "min": 1, "max": 255 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), iv = Utils.convertToByteArray(args[1].string, args[1].option), [,, mode, inputType, outputType, padding, wordSize, rounds] = args; // Validate word size if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0) throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`); const blockSize = getBlockSize(wordSize); const defaultRounds = getDefaultRounds(wordSize); if (iv.length !== blockSize && iv.length !== 0 && mode !== "ECB") throw new OperationError(`Invalid IV length: ${iv.length} bytes RC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits). Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255) throw new OperationError(`Invalid number of rounds: ${rounds} Rounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`); // Default IV to null bytes if empty (like AES) const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv; input = Utils.convertToByteArray(input, inputType); const output = encryptRC6(input, key, actualIv, mode, padding, rounds, wordSize); return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); } } export default RC6Encrypt; ================================================ FILE: src/core/operations/RIPEMD.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * RIPEMD operation */ class RIPEMD extends Operation { /** * RIPEMD constructor */ constructor() { super(); this.name = "RIPEMD"; this.module = "Crypto"; 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.

RIPEMD was based upon the design principles used in MD4, and is similar in performance to the more popular SHA-1.

"; this.infoURL = "https://wikipedia.org/wiki/RIPEMD"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Size", "type": "option", "value": ["320", "256", "160", "128"] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const size = args[0]; return runHash("ripemd" + size, input); } } export default RIPEMD; ================================================ FILE: src/core/operations/ROT13.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * ROT13 operation. */ class ROT13 extends Operation { /** * ROT13 constructor */ constructor() { super(); this.name = "ROT13"; this.module = "Default"; this.description = "A simple caesar substitution cipher which rotates alphabet characters by the specified amount (default 13)."; this.infoURL = "https://wikipedia.org/wiki/ROT13"; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = [ { name: "Rotate lower case chars", type: "boolean", value: true }, { name: "Rotate upper case chars", type: "boolean", value: true }, { name: "Rotate numbers", type: "boolean", value: false }, { name: "Amount", type: "number", value: 13 }, ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const output = input, rot13Lowercase = args[0], rot13Upperacse = args[1], rotNumbers = args[2]; let amount = args[3], amountNumbers = args[3]; if (amount) { if (amount < 0) { amount = 26 - (Math.abs(amount) % 26); amountNumbers = 10 - (Math.abs(amountNumbers) % 10); } for (let i = 0; i < input.length; i++) { let chr = input[i]; if (rot13Upperacse && chr >= 65 && chr <= 90) { // Upper case chr = (chr - 65 + amount) % 26; output[i] = chr + 65; } else if (rot13Lowercase && chr >= 97 && chr <= 122) { // Lower case chr = (chr - 97 + amount) % 26; output[i] = chr + 97; } else if (rotNumbers && chr >= 48 && chr <= 57) { // Numbers chr = (chr - 48 + amountNumbers) % 10; output[i] = chr + 48; } } } return output; } /** * Highlight ROT13 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight ROT13 in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default ROT13; ================================================ FILE: src/core/operations/ROT13BruteForce.mjs ================================================ /** * @author MikeCAT * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * ROT13 Brute Force operation. */ class ROT13BruteForce extends Operation { /** * ROT13BruteForce constructor */ constructor() { super(); this.name = "ROT13 Brute Force"; this.module = "Default"; this.description = "Try all meaningful amounts for ROT13.

Optionally you can enter your known plaintext (crib) to filter the result."; this.infoURL = "https://wikipedia.org/wiki/ROT13"; this.inputType = "byteArray"; this.outputType = "string"; this.args = [ { name: "Rotate lower case chars", type: "boolean", value: true }, { name: "Rotate upper case chars", type: "boolean", value: true }, { name: "Rotate numbers", type: "boolean", value: false }, { name: "Sample length", type: "number", value: 100 }, { name: "Sample offset", type: "number", value: 0 }, { name: "Print amount", type: "boolean", value: true }, { name: "Crib (known plaintext string)", type: "string", value: "" } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [rotateLower, rotateUpper, rotateNum, sampleLength, sampleOffset, printAmount, crib] = args; const sample = input.slice(sampleOffset, sampleOffset + sampleLength); const cribLower = crib.toLowerCase(); const lowerStart = "a".charCodeAt(0), upperStart = "A".charCodeAt(0), numStart = "0".charCodeAt(0); const result = []; for (let amount = 1; amount < 26; amount++) { const rotated = sample.slice(); for (let i = 0; i < rotated.length; i++) { if (rotateLower && lowerStart <= rotated[i] && rotated[i] < lowerStart + 26) { rotated[i] = (rotated[i] - lowerStart + amount) % 26 + lowerStart; } else if (rotateUpper && upperStart <= rotated[i] && rotated[i] < upperStart + 26) { rotated[i] = (rotated[i] - upperStart + amount) % 26 + upperStart; } else if (rotateNum && numStart <= rotated[i] && rotated[i] < numStart + 10) { rotated[i] = (rotated[i] - numStart + amount) % 10 + numStart; } } const rotatedString = Utils.byteArrayToUtf8(rotated); if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); if (printAmount) { const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; result.push(amountStr + rotatedStringEscaped); } else { result.push(rotatedStringEscaped); } } } return result.join("\n"); } } export default ROT13BruteForce; ================================================ FILE: src/core/operations/ROT47.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * ROT47 operation. */ class ROT47 extends Operation { /** * ROT47 constructor */ constructor() { super(); this.name = "ROT47"; this.module = "Default"; this.description = "A slightly more complex variation of a caesar cipher, which includes ASCII characters from 33 '!' to 126 '~'. Default rotation: 47."; this.infoURL = "https://wikipedia.org/wiki/ROT13#Variants"; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = [ { name: "Amount", type: "number", value: 47 }, ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const output = input; let amount = args[0], chr; if (amount) { if (amount < 0) { amount = 94 - (Math.abs(amount) % 94); } for (let i = 0; i < input.length; i++) { chr = input[i]; if (chr >= 33 && chr <= 126) { chr = (chr - 33 + amount) % 94; output[i] = chr + 33; } } } return output; } /** * Highlight ROT47 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight ROT47 in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default ROT47; ================================================ FILE: src/core/operations/ROT47BruteForce.mjs ================================================ /** * @author MikeCAT * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * ROT47 Brute Force operation. */ class ROT47BruteForce extends Operation { /** * ROT47BruteForce constructor */ constructor() { super(); this.name = "ROT47 Brute Force"; this.module = "Default"; this.description = "Try all meaningful amounts for ROT47.

Optionally you can enter your known plaintext (crib) to filter the result."; this.infoURL = "https://wikipedia.org/wiki/ROT13#Variants"; this.inputType = "byteArray"; this.outputType = "string"; this.args = [ { name: "Sample length", type: "number", value: 100 }, { name: "Sample offset", type: "number", value: 0 }, { name: "Print amount", type: "boolean", value: true }, { name: "Crib (known plaintext string)", type: "string", value: "" } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [sampleLength, sampleOffset, printAmount, crib] = args; const sample = input.slice(sampleOffset, sampleOffset + sampleLength); const cribLower = crib.toLowerCase(); const result = []; for (let amount = 1; amount < 94; amount++) { const rotated = sample.slice(); for (let i = 0; i < rotated.length; i++) { if (33 <= rotated[i] && rotated[i] <= 126) { rotated[i] = (rotated[i] - 33 + amount) % 94 + 33; } } const rotatedString = Utils.byteArrayToUtf8(rotated); if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); if (printAmount) { const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; result.push(amountStr + rotatedStringEscaped); } else { result.push(rotatedStringEscaped); } } } return result.join("\n"); } } export default ROT47BruteForce; ================================================ FILE: src/core/operations/ROT8000.mjs ================================================ /** * @author Daniel Temkin [http://danieltemkin.com] * @author Thomas Leplus [https://www.leplus.org] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * ROT8000 operation. */ class ROT8000 extends Operation { /** * ROT8000 constructor */ constructor() { super(); this.name = "ROT8000"; this.module = "Default"; this.description = "The simple Caesar-cypher encryption that replaces each Unicode character with the one 0x8000 places forward or back along the alphabet."; this.infoURL = "https://rot8000.com/info"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { // Inspired from https://github.com/rottytooth/rot8000/blob/main/rot8000.js // these come from the valid-code-point-transitions.json file generated from the c# proj // this is done bc: 1) don't trust JS's understanging of surrogate pairs and 2) consistency with original rot8000 const validCodePoints = { "33": true, "127": false, "161": true, "5760": false, "5761": true, "8192": false, "8203": true, "8232": false, "8234": true, "8239": false, "8240": true, "8287": false, "8288": true, "12288": false, "12289": true, "55296": false, "57344": true }; const bmpSize = 0x10000; const rotList = {}; // the mapping of char to rotated char const hiddenBlocks = []; let startBlock = 0; for (const key in validCodePoints) { if (Object.prototype.hasOwnProperty.call(validCodePoints, key)) { if (validCodePoints[key] === true) hiddenBlocks.push({ start: startBlock, end: parseInt(key, 10) - 1 }); else startBlock = parseInt(key, 10); } } const validIntList = []; // list of all valid chars let currValid = false; for (let i = 0; i < bmpSize; i++) { if (validCodePoints[i] !== undefined) { currValid = validCodePoints[i]; } if (currValid) validIntList.push(i); } const rotateNum = Object.keys(validIntList).length / 2; // go through every valid char and find its match for (let i = 0; i < validIntList.length; i++) { rotList[String.fromCharCode(validIntList[i])] = String.fromCharCode(validIntList[(i + rotateNum) % (rotateNum * 2)]); } let output = ""; for (let count = 0; count < input.length; count++) { // if it is not in the mappings list, just add it directly (no rotation) if (rotList[input[count]] === undefined) { output += input[count]; continue; } // otherwise, rotate it and add it to the string output += rotList[input[count]]; } return output; } /** * Highlight ROT8000 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight ROT8000 in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default ROT8000; ================================================ FILE: src/core/operations/RSADecrypt.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import forge from "node-forge"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** * RSA Decrypt operation */ class RSADecrypt extends Operation { /** * RSADecrypt constructor */ constructor() { super(); this.name = "RSA Decrypt"; this.module = "Ciphers"; this.description = "Decrypt an RSA encrypted message with a PEM encoded private key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "RSA Private Key (PEM)", type: "text", value: "-----BEGIN RSA PRIVATE KEY-----" }, { name: "Key Password", type: "text", value: "" }, { name: "Encryption Scheme", type: "argSelector", value: [ { name: "RSA-OAEP", on: [3] }, { name: "RSAES-PKCS1-V1_5", off: [3] }, { name: "RAW", off: [3] }] }, { name: "Message Digest Algorithm", type: "option", value: Object.keys(MD_ALGORITHMS) } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [pemKey, password, scheme, md] = args; if (pemKey.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) { throw new OperationError("Please enter a private key."); } try { const privKey = forge.pki.decryptRsaPrivateKey(pemKey, password); const dMsg = privKey.decrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); return forge.util.decodeUtf8(dMsg); } catch (err) { throw new OperationError(err); } } } export default RSADecrypt; ================================================ FILE: src/core/operations/RSAEncrypt.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import forge from "node-forge"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** * RSA Encrypt operation */ class RSAEncrypt extends Operation { /** * RSAEncrypt constructor */ constructor() { super(); this.name = "RSA Encrypt"; this.module = "Ciphers"; this.description = "Encrypt a message with a PEM encoded RSA public key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "RSA Public Key (PEM)", type: "text", value: "-----BEGIN RSA PUBLIC KEY-----" }, { name: "Encryption Scheme", type: "argSelector", value: [ { name: "RSA-OAEP", on: [2] }, { name: "RSAES-PKCS1-V1_5", off: [2] }, { name: "RAW", off: [2] }] }, { name: "Message Digest Algorithm", type: "option", value: Object.keys(MD_ALGORITHMS) } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [pemKey, scheme, md] = args; if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) { throw new OperationError("Please enter a public key."); } try { // Load public key const pubKey = forge.pki.publicKeyFromPem(pemKey); // https://github.com/digitalbazaar/forge/issues/465#issuecomment-271097600 const plaintextBytes = forge.util.encodeUtf8(input); // Encrypt message const eMsg = pubKey.encrypt(plaintextBytes, scheme, {md: MD_ALGORITHMS[md].create()}); return eMsg; } catch (err) { if (err.message === "RSAES-OAEP input message length is too long.") { throw new OperationError(`RSAES-OAEP input message length (${err.length}) is longer than the maximum allowed length (${err.maxLength}).`); } throw new OperationError(err); } } } export default RSAEncrypt; ================================================ FILE: src/core/operations/RSASign.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @author gchq77703 [] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import forge from "node-forge"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** * RSA Sign operation */ class RSASign extends Operation { /** * RSASign constructor */ constructor() { super(); this.name = "RSA Sign"; this.module = "Ciphers"; this.description = "Sign a plaintext message with a PEM encoded RSA key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "RSA Private Key (PEM)", type: "text", value: "-----BEGIN RSA PRIVATE KEY-----" }, { name: "Key Password", type: "text", value: "" }, { name: "Message Digest Algorithm", type: "option", value: Object.keys(MD_ALGORITHMS) } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [key, password, mdAlgo] = args; if (key.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) { throw new OperationError("Please enter a private key."); } try { const privateKey = forge.pki.decryptRsaPrivateKey(key, password); // Generate message hash const md = MD_ALGORITHMS[mdAlgo].create(); md.update(input, "raw"); // Sign message hash const sig = privateKey.sign(md); return sig; } catch (err) { throw new OperationError(err); } } } export default RSASign; ================================================ FILE: src/core/operations/RSAVerify.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import forge from "node-forge"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; import Utils from "../Utils.mjs"; /** * RSA Verify operation */ class RSAVerify extends Operation { /** * RSAVerify constructor */ constructor() { super(); this.name = "RSA Verify"; this.module = "Ciphers"; this.description = "Verify a message against a signature and a public PEM encoded RSA key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "RSA Public Key (PEM)", type: "text", value: "-----BEGIN RSA PUBLIC KEY-----" }, { name: "Message", type: "text", value: "" }, { name: "Message format", type: "option", value: ["Raw", "Hex", "Base64"] }, { name: "Message Digest Algorithm", type: "option", value: Object.keys(MD_ALGORITHMS) } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [pemKey, message, format, mdAlgo] = args; if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) { throw new OperationError("Please enter a public key."); } try { // Load public key const pubKey = forge.pki.publicKeyFromPem(pemKey); // Generate message digest const md = MD_ALGORITHMS[mdAlgo].create(); const messageStr = Utils.convertToByteString(message, format); md.update(messageStr, "raw"); // Compare signed message digest and generated message digest const result = pubKey.verify(md.digest().bytes(), input); return result ? "Verified OK" : "Verification Failure"; } catch (err) { if (err.message === "Encrypted message length is invalid.") { throw new OperationError(`Signature length (${err.length}) does not match expected length based on key (${err.expected}).`); } throw new OperationError(err); } } } export default RSAVerify; ================================================ FILE: src/core/operations/Rabbit.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { toHexFast } from "../lib/Hex.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Rabbit operation */ class Rabbit extends Operation { /** * Rabbit constructor */ constructor() { super(); this.name = "Rabbit"; this.module = "Ciphers"; this.description = "Rabbit is a high-speed stream cipher introduced in 2003 and defined in RFC 4503.

The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).

big-endian: based on RFC4503 and RFC3447
little-endian: compatible with Crypto++"; this.infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Endianness", "type": "option", "value": ["Big", "Little"] }, { "name": "Input", "type": "option", "value": ["Raw", "Hex"] }, { "name": "Output", "type": "option", "value": ["Raw", "Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), iv = Utils.convertToByteArray(args[1].string, args[1].option), endianness = args[2], inputType = args[3], outputType = args[4]; const littleEndian = endianness === "Little"; if (key.length !== 16) { throw new OperationError(`Invalid key length: ${key.length} bytes (expected: 16)`); } if (iv.length !== 0 && iv.length !== 8) { throw new OperationError(`Invalid IV length: ${iv.length} bytes (expected: 0 or 8)`); } // Inner State const X = new Uint32Array(8), C = new Uint32Array(8); let b = 0; // Counter System const A = [ 0x4d34d34d, 0xd34d34d3, 0x34d34d34, 0x4d34d34d, 0xd34d34d3, 0x34d34d34, 0x4d34d34d, 0xd34d34d3 ]; const counterUpdate = function() { for (let j = 0; j < 8; j++) { const temp = C[j] + A[j] + b; b = (temp / ((1 << 30) * 4)) >>> 0; C[j] = temp; } }; // Next-State Function const g = function(u, v) { const uv = (u + v) >>> 0; const upper = uv >>> 16, lower = uv & 0xffff; const upperUpper = upper * upper; const upperLower2 = 2 * upper * lower; const lowerLower = lower * lower; const mswTemp = upperUpper + ((upperLower2 / (1 << 16)) >>> 0); const lswTemp = lowerLower + (upperLower2 & 0xffff) * (1 << 16); const msw = mswTemp + ((lswTemp / ((1 << 30) * 4)) >>> 0); const lsw = lswTemp >>> 0; return lsw ^ msw; }; const leftRotate = function(value, width) { return (value << width) | (value >>> (32 - width)); }; const nextStateHelper1 = function(v0, v1, v2) { return v0 + leftRotate(v1, 16) + leftRotate(v2, 16); }; const nextStateHelper2 = function(v0, v1, v2) { return v0 + leftRotate(v1, 8) + v2; }; const G = new Uint32Array(8); const nextState = function() { for (let j = 0; j < 8; j++) { G[j] = g(X[j], C[j]); } X[0] = nextStateHelper1(G[0], G[7], G[6]); X[1] = nextStateHelper2(G[1], G[0], G[7]); X[2] = nextStateHelper1(G[2], G[1], G[0]); X[3] = nextStateHelper2(G[3], G[2], G[1]); X[4] = nextStateHelper1(G[4], G[3], G[2]); X[5] = nextStateHelper2(G[5], G[4], G[3]); X[6] = nextStateHelper1(G[6], G[5], G[4]); X[7] = nextStateHelper2(G[7], G[6], G[5]); }; // Key Setup Scheme const K = new Uint16Array(8); if (littleEndian) { for (let i = 0; i < 8; i++) { K[i] = (key[1 + 2 * i] << 8) | key[2 * i]; } } else { for (let i = 0; i < 8; i++) { K[i] = (key[14 - 2 * i] << 8) | key[15 - 2 * i]; } } for (let j = 0; j < 8; j++) { if (j % 2 === 0) { X[j] = (K[(j + 1) % 8] << 16) | K[j]; C[j] = (K[(j + 4) % 8] << 16) | K[(j + 5) % 8]; } else { X[j] = (K[(j + 5) % 8] << 16) | K[(j + 4) % 8]; C[j] = (K[j] << 16) | K[(j + 1) % 8]; } } for (let i = 0; i < 4; i++) { counterUpdate(); nextState(); } for (let j = 0; j < 8; j++) { C[j] = C[j] ^ X[(j + 4) % 8]; } // IV Setup Scheme if (iv.length === 8) { const getIVValue = function(a, b, c, d) { if (littleEndian) { return (iv[a] << 24) | (iv[b] << 16) | (iv[c] << 8) | iv[d]; } else { return (iv[7 - a] << 24) | (iv[7 - b] << 16) | (iv[7 - c] << 8) | iv[7 - d]; } }; C[0] = C[0] ^ getIVValue(3, 2, 1, 0); C[1] = C[1] ^ getIVValue(7, 6, 3, 2); C[2] = C[2] ^ getIVValue(7, 6, 5, 4); C[3] = C[3] ^ getIVValue(5, 4, 1, 0); C[4] = C[4] ^ getIVValue(3, 2, 1, 0); C[5] = C[5] ^ getIVValue(7, 6, 3, 2); C[6] = C[6] ^ getIVValue(7, 6, 5, 4); C[7] = C[7] ^ getIVValue(5, 4, 1, 0); for (let i = 0; i < 4; i++) { counterUpdate(); nextState(); } } // Extraction Scheme const S = new Uint8Array(16); const extract = function() { let pos = 0; const addPart = function(value) { S[pos++] = value >>> 8; S[pos++] = value & 0xff; }; counterUpdate(); nextState(); addPart((X[6] >>> 16) ^ (X[1] & 0xffff)); addPart((X[6] & 0xffff) ^ (X[3] >>> 16)); addPart((X[4] >>> 16) ^ (X[7] & 0xffff)); addPart((X[4] & 0xffff) ^ (X[1] >>> 16)); addPart((X[2] >>> 16) ^ (X[5] & 0xffff)); addPart((X[2] & 0xffff) ^ (X[7] >>> 16)); addPart((X[0] >>> 16) ^ (X[3] & 0xffff)); addPart((X[0] & 0xffff) ^ (X[5] >>> 16)); if (littleEndian) { for (let i = 0, j = S.length - 1; i < j;) { const temp = S[i]; S[i] = S[j]; S[j] = temp; i++; j--; } } }; const data = Utils.convertToByteString(input, inputType); const result = new Uint8Array(data.length); for (let i = 0; i <= data.length - 16; i += 16) { extract(); for (let j = 0; j < 16; j++) { result[i + j] = data.charCodeAt(i + j) ^ S[j]; } } if (data.length % 16 !== 0) { const offset = data.length - data.length % 16; const length = data.length - offset; extract(); if (littleEndian) { for (let j = 0; j < length; j++) { result[offset + j] = data.charCodeAt(offset + j) ^ S[j]; } } else { for (let j = 0; j < length; j++) { result[offset + j] = data.charCodeAt(offset + j) ^ S[16 - length + j]; } } } if (outputType === "Hex") { return toHexFast(result); } return Utils.byteArrayToChars(result); } } export default Rabbit; ================================================ FILE: src/core/operations/RailFenceCipherDecode.mjs ================================================ /** * @author Flavio Diez [flaviofdiez+cyberchef@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Rail Fence Cipher Decode operation */ class RailFenceCipherDecode extends Operation { /** * RailFenceCipherDecode constructor */ constructor() { super(); this.name = "Rail Fence Cipher Decode"; this.module = "Ciphers"; this.description = "Decodes Strings that were created using the Rail fence Cipher provided a key and an offset"; this.infoURL = "https://wikipedia.org/wiki/Rail_fence_cipher"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Key", type: "number", value: 2 }, { name: "Offset", type: "number", value: 0 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [key, offset] = args; const cipher = input; if (key < 2) { throw new OperationError("Key has to be bigger than 2"); } else if (key > cipher.length) { throw new OperationError("Key should be smaller than the cipher's length"); } if (offset < 0) { throw new OperationError("Offset has to be a positive integer"); } const cycle = (key - 1) * 2; const plaintext = new Array(cipher.length); let j = 0; let x, y; for (y = 0; y < key; y++) { for (x = 0; x < cipher.length; x++) { if ((y + x + offset) % cycle === 0 || (y - x - offset) % cycle === 0) { plaintext[x] = cipher[j++]; } } } return plaintext.join(""); } } export default RailFenceCipherDecode; ================================================ FILE: src/core/operations/RailFenceCipherEncode.mjs ================================================ /** * @author Flavio Diez [flaviofdiez+cyberchef@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Rail Fence Cipher Encode operation */ class RailFenceCipherEncode extends Operation { /** * RailFenceCipherEncode constructor */ constructor() { super(); this.name = "Rail Fence Cipher Encode"; this.module = "Ciphers"; this.description = "Encodes Strings using the Rail fence Cipher provided a key and an offset"; this.infoURL = "https://wikipedia.org/wiki/Rail_fence_cipher"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Key", type: "number", value: 2 }, { name: "Offset", type: "number", value: 0 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [key, offset] = args; const plaintext = input; if (key < 2) { throw new OperationError("Key has to be bigger than 2"); } else if (key > plaintext.length) { throw new OperationError("Key should be smaller than the plain text's length"); } if (offset < 0) { throw new OperationError("Offset has to be a positive integer"); } const cycle = (key - 1) * 2; const rows = new Array(key).fill(""); for (let pos = 0; pos < plaintext.length; pos++) { const rowIdx = key - 1 - Math.abs(cycle / 2 - (pos + offset) % cycle); rows[rowIdx] += plaintext[pos]; } return rows.join(""); } } export default RailFenceCipherEncode; ================================================ FILE: src/core/operations/RandomizeColourPalette.mjs ================================================ /** * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { isImage } from "../lib/FileType.mjs"; import { runHash } from "../lib/Hash.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { Jimp } from "jimp"; /** * Randomize Colour Palette operation */ class RandomizeColourPalette extends Operation { /** * RandomizeColourPalette constructor */ constructor() { super(); this.name = "Randomize Colour Palette"; this.module = "Image"; this.description = "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."; this.infoURL = "https://wikipedia.org/wiki/Indexed_color"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Seed", type: "string", value: "", }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ async run(input, args) { if (!isImage(input)) throw new OperationError("Please enter a valid image file."); const seed = args[0] || Math.random().toString().substr(2), parsedImage = await Jimp.read(input), width = parsedImage.bitmap.width, height = parsedImage.bitmap.height; let rgbString, rgbHash, rgbHex; parsedImage.scan(0, 0, width, height, function (x, y, idx) { rgbString = this.bitmap.data.slice(idx, idx + 3).join("."); rgbHash = runHash("md5", Utils.strToArrayBuffer(seed + rgbString)); rgbHex = rgbHash.substr(0, 6) + "ff"; parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y); }); const imageBuffer = await parsedImage.getBuffer(parsedImage.mime); return new Uint8Array(imageBuffer).buffer; } /** * Displays the extracted data as an image for web apps. * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const type = isImage(data); return ``; } } export default RandomizeColourPalette; ================================================ FILE: src/core/operations/RawDeflate.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {COMPRESSION_TYPE} from "../lib/Zlib.mjs"; import rawdeflate from "zlibjs/bin/rawdeflate.min.js"; const Zlib = rawdeflate.Zlib; const RAW_COMPRESSION_TYPE_LOOKUP = { "Fixed Huffman Coding": Zlib.RawDeflate.CompressionType.FIXED, "Dynamic Huffman Coding": Zlib.RawDeflate.CompressionType.DYNAMIC, "None (Store)": Zlib.RawDeflate.CompressionType.NONE, }; /** * Raw Deflate operation */ class RawDeflate extends Operation { /** * RawDeflate constructor */ constructor() { super(); this.name = "Raw Deflate"; this.module = "Compression"; this.description = "Compresses data using the deflate algorithm with no headers."; this.infoURL = "https://wikipedia.org/wiki/DEFLATE"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { name: "Compression type", type: "option", value: COMPRESSION_TYPE } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const deflate = new Zlib.RawDeflate(new Uint8Array(input), { compressionType: RAW_COMPRESSION_TYPE_LOOKUP[args[0]] }); return new Uint8Array(deflate.compress()).buffer; } } export default RawDeflate; ================================================ FILE: src/core/operations/RawInflate.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {INFLATE_BUFFER_TYPE} from "../lib/Zlib.mjs"; import rawinflate from "zlibjs/bin/rawinflate.min.js"; const Zlib = rawinflate.Zlib; const RAW_BUFFER_TYPE_LOOKUP = { "Adaptive": Zlib.RawInflate.BufferType.ADAPTIVE, "Block": Zlib.RawInflate.BufferType.BLOCK, }; /** * Raw Inflate operation */ class RawInflate extends Operation { /** * RawInflate constructor */ constructor() { super(); this.name = "Raw Inflate"; this.module = "Compression"; this.description = "Decompresses data which has been compressed using the deflate algorithm with no headers."; this.infoURL = "https://wikipedia.org/wiki/DEFLATE"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { name: "Start index", type: "number", value: 0 }, { name: "Initial output buffer size", type: "number", value: 0 }, { name: "Buffer expansion type", type: "option", value: INFLATE_BUFFER_TYPE }, { name: "Resize buffer after decompression", type: "boolean", value: false }, { name: "Verify result", type: "boolean", value: false } ]; this.checks = [ { entropyRange: [7.5, 8], args: [0, 0, INFLATE_BUFFER_TYPE, false, false] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const inflate = new Zlib.RawInflate(new Uint8Array(input), { index: args[0], bufferSize: args[1], bufferType: RAW_BUFFER_TYPE_LOOKUP[args[2]], resize: args[3], verify: args[4] }), result = new Uint8Array(inflate.decompress()); return result.buffer; } } export default RawInflate; ================================================ FILE: src/core/operations/Register.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Dish from "../Dish.mjs"; import XRegExp from "xregexp"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * Register operation */ class Register extends Operation { /** * Register constructor */ constructor() { super(); this.name = "Register"; this.flowControl = true; this.module = "Regex"; 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.

To use registers in arguments, refer to them using the notation $Rn where n is the register number, starting at 0.

For example:
Input: Test
Extractor: (.*)
Argument: $R0 becomes Test

Registers can be escaped in arguments using a backslash. e.g. \\$R0 would become $R0 rather than Test."; this.infoURL = "https://wikipedia.org/wiki/Regular_expression#Syntax"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Extractor", "type": "binaryString", "value": "([\\s\\S]*)" }, { "name": "Case insensitive", "type": "boolean", "value": true }, { "name": "Multiline matching", "type": "boolean", "value": false }, { "name": "Dot matches all", "type": "boolean", "value": false } ]; } /** * @param {Object} state - The current state of the recipe. * @param {number} state.progress - The current position in the recipe. * @param {Dish} state.dish - The Dish being operated on. * @param {Operation[]} state.opList - The list of operations in the recipe. * @returns {Object} The updated state of the recipe. */ async run(state) { const ings = state.opList[state.progress].ingValues; const [extractorStr, i, m, s] = ings; let modifiers = ""; if (i) modifiers += "i"; if (m) modifiers += "m"; if (s) modifiers += "s"; const extractor = new XRegExp(extractorStr, modifiers), input = await state.dish.get(Dish.STRING), registers = input.match(extractor); if (!registers) return state; if (isWorkerEnvironment()) { self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1)); } /** * Replaces references to registers (e.g. $R0) with the contents of those registers. * * @param {string} str * @returns {string} */ function replaceRegister(str) { // Replace references to registers ($Rn) with contents of registers return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => { const index = parseInt(regNum, 10) + 1; if (index <= state.numRegisters || index >= state.numRegisters + registers.length) return match; if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape return slashes + registers[index - state.numRegisters]; }); } // Step through all subsequent ops and replace registers in args with extracted content for (let i = state.progress + 1; i < state.opList.length; i++) { if (state.opList[i].disabled) continue; let args = state.opList[i].ingValues; args = args.map(arg => { if (typeof arg !== "string" && typeof arg !== "object") return arg; if (typeof arg === "object" && Object.prototype.hasOwnProperty.call(arg, "string")) { arg.string = replaceRegister(arg.string); return arg; } return replaceRegister(arg); }); state.opList[i].ingValues = args; } state.numRegisters += registers.length - 1; return state; } } export default Register; ================================================ FILE: src/core/operations/RegularExpression.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import XRegExp from "xregexp"; import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Regular expression operation */ class RegularExpression extends Operation { /** * RegularExpression constructor */ constructor() { super(); this.name = "Regular expression"; this.module = "Regex"; this.description = "Define your own regular expression (regex) to search the input data with, optionally choosing from a list of pre-defined patterns.

Supports extended regex syntax including the 'dot matches all' flag, named capture groups, full unicode coverage (including \\p{} categories and scripts as well as astral codes) and recursive matching."; this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; this.inputType = "string"; this.outputType = "html"; this.args = [ { "name": "Built in regexes", "type": "populateOption", "value": [ { name: "User defined", value: "" }, { name: "IPv4 address", 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})?" }, { name: "IPv6 address", 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})" }, { name: "Email address", 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}\\])" }, { name: "URL", value: "([A-Za-z]+://)([-\\w]+(?:\\.\\w[-\\w]*)+)(:\\d+)?(/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*)?" }, { name: "Domain", value: "\\b((?=[a-z0-9-]{1,63}\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}\\b" }, { name: "Windows file path", 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})?" }, { name: "UNIX file path", value: "(?:/[A-Za-z\\d.][A-Za-z\\d\\-.]{0,61})+" }, { name: "MAC address", value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}" }, { name: "UUID", 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}" }, { name: "Date (yyyy-mm-dd)", value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" }, { name: "Date (dd/mm/yyyy)", value: "(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\d\\d)" }, { name: "Date (mm/dd/yyyy)", value: "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\d\\d)" }, { name: "Strings", value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}" }, ], "target": 1 }, { "name": "Regex", "type": "text", "value": "" }, { "name": "Case insensitive", "type": "boolean", "value": true }, { "name": "^ and $ match at newlines", "type": "boolean", "value": true }, { "name": "Dot matches all", "type": "boolean", "value": false }, { "name": "Unicode support", "type": "boolean", "value": false }, { "name": "Astral support", "type": "boolean", "value": false }, { "name": "Display total", "type": "boolean", "value": false }, { "name": "Output format", "type": "option", "value": ["Highlight matches", "List matches", "List capture groups", "List matches with capture groups"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const [, userRegex, i, m, s, u, a, displayTotal, outputFormat ] = args; let modifiers = "g"; if (i) modifiers += "i"; if (m) modifiers += "m"; if (s) modifiers += "s"; if (u) modifiers += "u"; if (a) modifiers += "A"; if (userRegex && userRegex !== "^" && userRegex !== "$") { try { const regex = new XRegExp(userRegex, modifiers); switch (outputFormat) { case "Highlight matches": return regexHighlight(input, regex, displayTotal); case "List matches": return Utils.escapeHtml(regexList(input, regex, displayTotal, true, false)); case "List capture groups": return Utils.escapeHtml(regexList(input, regex, displayTotal, false, true)); case "List matches with capture groups": return Utils.escapeHtml(regexList(input, regex, displayTotal, true, true)); default: throw new OperationError("Error: Invalid output format"); } } catch (err) { throw new OperationError("Invalid regex. Details: " + err.message); } } else { return Utils.escapeHtml(input); } } } /** * Creates a string listing the matches within a string. * * @param {string} input * @param {RegExp} regex * @param {boolean} displayTotal * @param {boolean} matches - Display full match * @param {boolean} captureGroups - Display each of the capture groups separately * @returns {string} */ function regexList(input, regex, displayTotal, matches, captureGroups) { let output = "", total = 0, match; while ((match = regex.exec(input))) { // Moves pointer when an empty string is matched (prevents infinite loop) if (match.index === regex.lastIndex) { regex.lastIndex++; } total++; if (matches) { output += match[0] + "\n"; } if (captureGroups) { for (let i = 1; i < match.length; i++) { if (matches) { output += " Group " + i + ": "; } output += match[i] + "\n"; } } } if (displayTotal) output = "Total found: " + total + "\n\n" + output; return output.slice(0, -1); } /** * Adds HTML highlights to matches within a string. * * @private * @param {string} input * @param {RegExp} regex * @param {boolean} displayTotal * @returns {string} */ function regexHighlight(input, regex, displayTotal) { let output = "", title = "", hl = 1, total = 0; const captureGroups = []; output = input.replace(regex, (match, ...args) => { args.pop(); // Throw away full string const offset = args.pop(), groups = args; title = `Offset: ${offset}\n`; if (groups.length) { title += "Groups:\n"; for (let i = 0; i < groups.length; i++) { title += `\t${i+1}: ${Utils.escapeHtml(groups[i] || "")}\n`; } } // Switch highlight hl = hl === 1 ? 2 : 1; // Store highlighted match and replace with a placeholder captureGroups.push(`${Utils.escapeHtml(match)}`); return `[cc_capture_group_${total++}]`; }); // Safely escape all remaining text, then replace placeholders output = Utils.escapeHtml(output); output = output.replace(/\[cc_capture_group_(\d+)\]/g, (_, i) => { return captureGroups[i]; }); if (displayTotal) output = "Total found: " + total + "\n\n" + output; return output; } export default RegularExpression; ================================================ FILE: src/core/operations/RemoveDiacritics.mjs ================================================ /** * @author Klaxon [klaxon@veyr.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Remove Diacritics operation */ class RemoveDiacritics extends Operation { /** * RemoveDiacritics constructor */ constructor() { super(); this.name = "Remove Diacritics"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Diacritic"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { // reference: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463 return input.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } } export default RemoveDiacritics; ================================================ FILE: src/core/operations/RemoveEXIF.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import { removeEXIF } from "../vendor/remove-exif.mjs"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Remove EXIF operation */ class RemoveEXIF extends Operation { /** * RemoveEXIF constructor */ constructor() { super(); this.name = "Remove EXIF"; this.module = "Image"; this.description = [ "Removes EXIF data from a JPEG image.", "

", "EXIF data embedded in photos usually contains information about the image file itself as well as the device used to create it.", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Exif"; this.inputType = "ArrayBuffer"; this.outputType = "byteArray"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { input = new Uint8Array(input); // Do nothing if input is empty if (input.length === 0) return input; try { return removeEXIF(input); } catch (err) { // Simply return input if no EXIF data is found if (err === "Exif not found.") return input; throw new OperationError(`Could not remove EXIF data from image: ${err}`); } } } export default RemoveEXIF; ================================================ FILE: src/core/operations/RemoveLineNumbers.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Remove line numbers operation */ class RemoveLineNumbers extends Operation { /** * RemoveLineNumbers constructor */ constructor() { super(); this.name = "Remove line numbers"; this.module = "Default"; this.description = "Removes line numbers from the output if they can be trivially detected."; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return input.replace(/^[ \t]{0,5}\d+[\s:|\-,.)\]]/gm, ""); } } export default RemoveLineNumbers; ================================================ FILE: src/core/operations/RemoveNullBytes.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Remove null bytes operation */ class RemoveNullBytes extends Operation { /** * RemoveNullBytes constructor */ constructor() { super(); this.name = "Remove null bytes"; this.module = "Default"; this.description = "Removes all null bytes (0x00) from the input."; this.inputType = "ArrayBuffer"; this.outputType = "byteArray"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { input = new Uint8Array(input); const output = []; for (let i = 0; i < input.length; i++) { if (input[i] !== 0) output.push(input[i]); } return output; } } export default RemoveNullBytes; ================================================ FILE: src/core/operations/RemoveWhitespace.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Remove whitespace operation */ class RemoveWhitespace extends Operation { /** * RemoveWhitespace constructor */ constructor() { super(); this.name = "Remove whitespace"; this.module = "Default"; this.description = "Optionally removes all spaces, carriage returns, line feeds, tabs and form feeds from the input data.

This operation also supports the removal of full stops which are sometimes used to represent non-printable bytes in ASCII output."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Spaces", "type": "boolean", "value": true }, { "name": "Carriage returns (\\r)", "type": "boolean", "value": true }, { "name": "Line feeds (\\n)", "type": "boolean", "value": true }, { "name": "Tabs", "type": "boolean", "value": true }, { "name": "Form feeds (\\f)", "type": "boolean", "value": true }, { "name": "Full stops", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [ removeSpaces, removeCarriageReturns, removeLineFeeds, removeTabs, removeFormFeeds, removeFullStops ] = args; let data = input; if (removeSpaces) data = data.replace(/ /g, ""); if (removeCarriageReturns) data = data.replace(/\r/g, ""); if (removeLineFeeds) data = data.replace(/\n/g, ""); if (removeTabs) data = data.replace(/\t/g, ""); if (removeFormFeeds) data = data.replace(/\f/g, ""); if (removeFullStops) data = data.replace(/\./g, ""); return data; } } export default RemoveWhitespace; ================================================ FILE: src/core/operations/RenderImage.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import { fromBase64, toBase64 } from "../lib/Base64.mjs"; import { fromHex } from "../lib/Hex.mjs"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import {isImage} from "../lib/FileType.mjs"; /** * Render Image operation */ class RenderImage extends Operation { /** * RenderImage constructor */ constructor() { super(); this.name = "Render Image"; this.module = "Image"; this.description = "Displays the input as an image. Supports the following formats:

  • jpg/jpeg
  • png
  • gif
  • webp
  • bmp
  • ico
"; this.inputType = "string"; this.outputType = "byteArray"; this.presentType = "html"; this.args = [ { "name": "Input format", "type": "option", "value": ["Raw", "Base64", "Hex"] } ]; this.checks = [ { pattern: "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)", flags: "", args: ["Raw"], useful: true, output: { mime: "image" } } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const inputFormat = args[0]; if (!input.length) return []; // Convert input to raw bytes switch (inputFormat) { case "Hex": input = fromHex(input); break; case "Base64": // Don't trust the Base64 entered by the user. // Unwrap it first, then re-encode later. input = fromBase64(input, undefined, "byteArray"); break; case "Raw": default: input = Utils.strToByteArray(input); break; } // Determine file type if (!isImage(input)) { throw new OperationError("Invalid file type"); } return input; } /** * Displays the image using HTML for web apps. * * @param {byteArray} data * @returns {html} */ async present(data) { if (!data.length) return ""; let dataURI = "data:"; // Determine file type const mime = isImage(data); if (mime) { dataURI += mime + ";"; } else { throw new OperationError("Invalid file type"); } // Add image data to URI dataURI += "base64," + toBase64(data); return ""; } } export default RenderImage; ================================================ FILE: src/core/operations/RenderMarkdown.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import MarkdownIt from "markdown-it"; import hljs from "highlight.js"; /** * Render Markdown operation */ class RenderMarkdown extends Operation { /** * RenderMarkdown constructor */ constructor() { super(); this.name = "Render Markdown"; this.module = "Code"; this.description = "Renders input Markdown as HTML. HTML rendering is disabled to avoid XSS."; this.infoURL = "https://wikipedia.org/wiki/Markdown"; this.inputType = "string"; this.outputType = "html"; this.args = [ { name: "Autoconvert URLs to links", type: "boolean", value: false }, { name: "Enable syntax highlighting", type: "boolean", value: true } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const [convertLinks, enableHighlighting] = args, md = new MarkdownIt({ linkify: convertLinks, html: false, // Explicitly disable HTML rendering highlight: function(str, lang) { if (lang && hljs.getLanguage(lang) && enableHighlighting) { try { return hljs.highlight(lang, str).value; } catch (__) {} } return ""; } }), rendered = md.render(input); return `
${rendered}
`; } } export default RenderMarkdown; ================================================ FILE: src/core/operations/ResizeImage.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime, ResizeStrategy } from "jimp"; /** * Resize Image operation */ class ResizeImage extends Operation { /** * ResizeImage constructor */ constructor() { super(); this.name = "Resize Image"; this.module = "Image"; this.description = "Resizes an image to the specified width and height values."; this.infoURL = "https://wikipedia.org/wiki/Image_scaling"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Width", type: "number", value: 100, min: 1, }, { name: "Height", type: "number", value: 100, min: 1, }, { name: "Unit type", type: "option", value: ["Pixels", "Percent"], }, { name: "Maintain aspect ratio", type: "boolean", value: false, }, { name: "Resizing algorithm", type: "option", value: [ "Nearest Neighbour", "Bilinear", "Bicubic", "Hermite", "Bezier", ], defaultIndex: 1, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { let width = args[0], height = args[1]; const unit = args[2], aspect = args[3], resizeAlg = args[4]; const resizeMap = { "Nearest Neighbour": ResizeStrategy.NEAREST_NEIGHBOR, Bilinear: ResizeStrategy.BILINEAR, Bicubic: ResizeStrategy.BICUBIC, Hermite: ResizeStrategy.HERMITE, Bezier: ResizeStrategy.BEZIER, }; if (!isImage(input)) { throw new OperationError("Invalid file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (unit === "Percent") { width = image.width * (width / 100); height = image.height * (height / 100); } if (isWorkerEnvironment()) self.sendStatusMessage("Resizing image..."); if (aspect) { image.scaleToFit({ w: width, h: height, mode: resizeMap[resizeAlg], }); } else { image.resize({ w: width, h: height, mode: resizeMap[resizeAlg], }); } let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error resizing image. (${err})`); } } /** * Displays the resized image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default ResizeImage; ================================================ FILE: src/core/operations/Return.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Return operation */ class Return extends Operation { /** * Return constructor */ constructor() { super(); this.name = "Return"; this.flowControl = true; this.module = "Default"; this.description = "End execution of operations at this point in the recipe."; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {Object} state - The current state of the recipe. * @param {number} state.progress - The current position in the recipe. * @param {Dish} state.dish - The Dish being operated on. * @param {Operation[]} state.opList - The list of operations in the recipe. * @returns {Object} The updated state of the recipe. */ run(state) { state.progress = state.opList.length; return state; } } export default Return; ================================================ FILE: src/core/operations/Reverse.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Reverse operation */ class Reverse extends Operation { /** * Reverse constructor */ constructor() { super(); this.name = "Reverse"; this.module = "Default"; this.description = "Reverses the input string."; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = [ { "name": "By", "type": "option", "value": ["Byte", "Character", "Line"], "defaultIndex": 1 } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { let i; if (args[0] === "Line") { const lines = []; let line = [], result = []; for (i = 0; i < input.length; i++) { if (input[i] === 0x0a) { lines.push(line); line = []; } else { line.push(input[i]); } } lines.push(line); lines.reverse(); for (i = 0; i < lines.length; i++) { result = result.concat(lines[i]); result.push(0x0a); } return result.slice(0, input.length); } else if (args[0] === "Character") { const inputString = Utils.byteArrayToUtf8(input); let result = ""; for (let i = inputString.length - 1; i >= 0; i--) { const c = inputString.charCodeAt(i); if (i > 0 && 0xdc00 <= c && c <= 0xdfff) { const c2 = inputString.charCodeAt(i - 1); if (0xd800 <= c2 && c2 <= 0xdbff) { // surrogates result += inputString.charAt(i - 1); result += inputString.charAt(i); i--; continue; } } result += inputString.charAt(i); } return Utils.strToUtf8ByteArray(result); } else { return input.reverse(); } } } export default Reverse; ================================================ FILE: src/core/operations/RisonDecode.mjs ================================================ /** * @author sg5506844 [sg5506844@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import rison from "rison"; /** * Rison Decode operation */ class RisonDecode extends Operation { /** * RisonDecode constructor */ constructor() { super(); this.name = "Rison Decode"; this.module = "Encodings"; 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."; this.infoURL = "https://github.com/Nanonid/rison"; this.inputType = "string"; this.outputType = "Object"; this.args = [ { name: "Decode Option", type: "editableOption", value: ["Decode", "Decode Object", "Decode Array"] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {Object} */ run(input, args) { const [decodeOption] = args; switch (decodeOption) { case "Decode": return rison.decode(input); case "Decode Object": return rison.decode_object(input); case "Decode Array": return rison.decode_array(input); default: throw new OperationError("Invalid Decode option"); } } } export default RisonDecode; ================================================ FILE: src/core/operations/RisonEncode.mjs ================================================ /** * @author sg5506844 [sg5506844@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import rison from "rison"; /** * Rison Encode operation */ class RisonEncode extends Operation { /** * RisonEncode constructor */ constructor() { super(); this.name = "Rison Encode"; this.module = "Encodings"; 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."; this.infoURL = "https://github.com/Nanonid/rison"; this.inputType = "Object"; this.outputType = "string"; this.args = [ { name: "Encode Option", type: "option", value: ["Encode", "Encode Object", "Encode Array", "Encode URI"] }, ]; } /** * @param {Object} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [encodeOption] = args; switch (encodeOption) { case "Encode": return rison.encode(input); case "Encode Object": return rison.encode_object(input); case "Encode Array": return rison.encode_array(input); case "Encode URI": return rison.encode_uri(input); default: throw new OperationError("Invalid encode option"); } } } export default RisonEncode; ================================================ FILE: src/core/operations/RotateImage.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Rotate Image operation */ class RotateImage extends Operation { /** * RotateImage constructor */ constructor() { super(); this.name = "Rotate Image"; this.module = "Image"; this.description = "Rotates an image by the specified number of degrees."; this.infoURL = ""; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Rotation amount (degrees)", type: "number", value: 90, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { const [degrees] = args; if (!isImage(input)) { throw new OperationError("Invalid file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (isWorkerEnvironment()) self.sendStatusMessage("Rotating image..."); image.rotate(degrees); let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error rotating image. (${err})`); } } /** * Displays the rotated image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default RotateImage; ================================================ FILE: src/core/operations/RotateLeft.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {rot, rotl, rotlCarry} from "../lib/Rotate.mjs"; /** * Rotate left operation. */ class RotateLeft extends Operation { /** * RotateLeft constructor */ constructor() { super(); this.name = "Rotate left"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts"; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = [ { name: "Amount", type: "number", value: 1 }, { name: "Carry through", type: "boolean", value: false } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { if (args[1]) { return rotlCarry(input, args[0]); } else { return rot(input, args[0], rotl); } } /** * Highlight rotate left * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight rotate left in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default RotateLeft; ================================================ FILE: src/core/operations/RotateRight.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {rot, rotr, rotrCarry} from "../lib/Rotate.mjs"; /** * Rotate right operation. */ class RotateRight extends Operation { /** * RotateRight constructor */ constructor() { super(); this.name = "Rotate right"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts"; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = [ { name: "Amount", type: "number", value: 1 }, { name: "Carry through", type: "boolean", value: false } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { if (args[1]) { return rotrCarry(input, args[0]); } else { return rot(input, args[0], rotr); } } /** * Highlight rotate right * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight rotate right in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default RotateRight; ================================================ FILE: src/core/operations/SHA0.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * SHA0 operation */ class SHA0 extends Operation { /** * SHA0 constructor */ constructor() { super(); this.name = "SHA0"; this.module = "Crypto"; 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."; this.infoURL = "https://wikipedia.org/wiki/SHA-1#SHA-0"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Rounds", type: "number", value: 80, min: 16 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { return runHash("sha0", input, {rounds: args[0]}); } } export default SHA0; ================================================ FILE: src/core/operations/SHA1.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * SHA1 operation */ class SHA1 extends Operation { /** * SHA1 constructor */ constructor() { super(); this.name = "SHA1"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/SHA-1"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Rounds", type: "number", value: 80, min: 16 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { return runHash("sha1", input, {rounds: args[0]}); } } export default SHA1; ================================================ FILE: src/core/operations/SHA2.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * SHA2 operation */ class SHA2 extends Operation { /** * SHA2 constructor */ constructor() { super(); this.name = "SHA2"; this.module = "Crypto"; 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.

  • SHA-512 operates on 64-bit words.
  • SHA-256 operates on 32-bit words.
  • SHA-384 is largely identical to SHA-512 but is truncated to 384 bytes.
  • SHA-224 is largely identical to SHA-256 but is truncated to 224 bytes.
  • 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.
The message digest algorithm for SHA256 variants consists, by default, of 64 rounds, and for SHA512 variants, it is, by default, 160."; this.infoURL = "https://wikipedia.org/wiki/SHA-2"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Size", type: "argSelector", value: [ { name: "512", on: [2], off: [1] }, { name: "384", on: [2], off: [1] }, { name: "256", on: [1], off: [2] }, { name: "224", on: [1], off: [2] }, { name: "512/256", on: [2], off: [1] }, { name: "512/224", on: [2], off: [1] } ] }, { name: "Rounds", // For SHA256 variants type: "number", value: 64, min: 16 }, { name: "Rounds", // For SHA512 variants type: "number", value: 160, min: 32 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const size = args[0]; const rounds = (size === "256" || size === "224") ? args[1] : args[2]; return runHash("sha" + size, input, {rounds: rounds}); } } export default SHA2; ================================================ FILE: src/core/operations/SHA3.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import JSSHA3 from "js-sha3"; import OperationError from "../errors/OperationError.mjs"; /** * SHA3 operation */ class SHA3 extends Operation { /** * SHA3 constructor */ constructor() { super(); this.name = "SHA3"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/SHA-3"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Size", "type": "option", "value": ["512", "384", "256", "224"] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const size = parseInt(args[0], 10); let algo; switch (size) { case 224: algo = JSSHA3.sha3_224; break; case 384: algo = JSSHA3.sha3_384; break; case 256: algo = JSSHA3.sha3_256; break; case 512: algo = JSSHA3.sha3_512; break; default: throw new OperationError("Invalid size"); } return algo(input); } } export default SHA3; ================================================ FILE: src/core/operations/SIGABA.mjs ================================================ /** * Emulation of the SIGABA machine. * * @author hettysymes * @copyright hettysymes 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {LETTERS} from "../lib/Enigma.mjs"; import {NUMBERS, CR_ROTORS, I_ROTORS, SigabaMachine, CRRotor, IRotor} from "../lib/SIGABA.mjs"; /** * Sigaba operation */ class Sigaba extends Operation { /** * Sigaba constructor */ constructor() { super(); this.name = "SIGABA"; this.module = "Bletchley"; this.description = "Encipher/decipher with the WW2 SIGABA machine.

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.

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.

Note: Whilst this has been tested against other software emulators, it has not been tested against hardware."; this.infoURL = "https://wikipedia.org/wiki/SIGABA"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "1st (left-hand) cipher rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "1st cipher rotor reversed", type: "boolean", value: false }, { name: "1st cipher rotor initial value", type: "option", value: LETTERS }, { name: "2nd cipher rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "2nd cipher rotor reversed", type: "boolean", value: false }, { name: "2nd cipher rotor initial value", type: "option", value: LETTERS }, { name: "3rd (middle) cipher rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "3rd cipher rotor reversed", type: "boolean", value: false }, { name: "3rd cipher rotor initial value", type: "option", value: LETTERS }, { name: "4th cipher rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "4th cipher rotor reversed", type: "boolean", value: false }, { name: "4th cipher rotor initial value", type: "option", value: LETTERS }, { name: "5th (right-hand) cipher rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "5th cipher rotor reversed", type: "boolean", value: false }, { name: "5th cipher rotor initial value", type: "option", value: LETTERS }, { name: "1st (left-hand) control rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "1st control rotor reversed", type: "boolean", value: false }, { name: "1st control rotor initial value", type: "option", value: LETTERS }, { name: "2nd control rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "2nd control rotor reversed", type: "boolean", value: false }, { name: "2nd control rotor initial value", type: "option", value: LETTERS }, { name: "3rd (middle) control rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "3rd control rotor reversed", type: "boolean", value: false }, { name: "3rd control rotor initial value", type: "option", value: LETTERS }, { name: "4th control rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "4th control rotor reversed", type: "boolean", value: false }, { name: "4th control rotor initial value", type: "option", value: LETTERS }, { name: "5th (right-hand) control rotor", type: "editableOption", value: CR_ROTORS, defaultIndex: 0 }, { name: "5th control rotor reversed", type: "boolean", value: false }, { name: "5th control rotor initial value", type: "option", value: LETTERS }, { name: "1st (left-hand) index rotor", type: "editableOption", value: I_ROTORS, defaultIndex: 0 }, { name: "1st index rotor initial value", type: "option", value: NUMBERS }, { name: "2nd index rotor", type: "editableOption", value: I_ROTORS, defaultIndex: 0 }, { name: "2nd index rotor initial value", type: "option", value: NUMBERS }, { name: "3rd (middle) index rotor", type: "editableOption", value: I_ROTORS, defaultIndex: 0 }, { name: "3rd index rotor initial value", type: "option", value: NUMBERS }, { name: "4th index rotor", type: "editableOption", value: I_ROTORS, defaultIndex: 0 }, { name: "4th index rotor initial value", type: "option", value: NUMBERS }, { name: "5th (right-hand) index rotor", type: "editableOption", value: I_ROTORS, defaultIndex: 0 }, { name: "5th index rotor initial value", type: "option", value: NUMBERS }, { name: "SIGABA mode", type: "option", value: ["Encrypt", "Decrypt"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const sigabaSwitch = args[40]; const cipherRotors = []; const controlRotors = []; const indexRotors = []; for (let i=0; i<5; i++) { const rotorWiring = args[i*3]; cipherRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1])); } for (let i=5; i<10; i++) { const rotorWiring = args[i*3]; controlRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1])); } for (let i=15; i<20; i++) { const rotorWiring = args[i*2]; indexRotors.push(new IRotor(rotorWiring, args[i*2+1])); } const sigaba = new SigabaMachine(cipherRotors, controlRotors, indexRotors); let result; if (sigabaSwitch === "Encrypt") { result = sigaba.encrypt(input); } else if (sigabaSwitch === "Decrypt") { result = sigaba.decrypt(input); } return result; } } export default Sigaba; ================================================ FILE: src/core/operations/SM2Decrypt.mjs ================================================ /** * @author flakjacket95 [dflack95@gmail.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import OperationError from "../errors/OperationError.mjs"; import Operation from "../Operation.mjs"; import { SM2 } from "../lib/SM2.mjs"; /** * SM2Decrypt operation */ class SM2Decrypt extends Operation { /** * SM2Decrypt constructor */ constructor() { super(); this.name = "SM2 Decrypt"; this.module = "Crypto"; this.description = "Decrypts a message utilizing the SM2 standard"; this.infoURL = ""; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) this.inputType = "string"; this.outputType = "ArrayBuffer"; this.args = [ { name: "Private Key", type: "string", value: "DEADBEEF" }, { "name": "Input Format", "type": "option", "value": ["C1C3C2", "C1C2C3"], "defaultIndex": 0 }, { name: "Curve", type: "option", "value": ["sm2p256v1"], "defaultIndex": 0 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const [privateKey, inputFormat, curveName] = args; if (privateKey.length !== 64) { throw new OperationError("Input private key must be in hex; and should be 32 bytes"); } const sm2 = new SM2(curveName, inputFormat); sm2.setPrivateKey(privateKey); const result = sm2.decrypt(input); return result; } } export default SM2Decrypt; ================================================ FILE: src/core/operations/SM2Encrypt.mjs ================================================ /** * @author flakjacket95 [dflack95@gmail.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import OperationError from "../errors/OperationError.mjs"; import Operation from "../Operation.mjs"; import { SM2 } from "../lib/SM2.mjs"; /** * SM2 Encrypt operation */ class SM2Encrypt extends Operation { /** * SM2Encrypt constructor */ constructor() { super(); this.name = "SM2 Encrypt"; this.module = "Crypto"; this.description = "Encrypts a message utilizing the SM2 standard"; this.infoURL = ""; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Public Key X", type: "string", value: "DEADBEEF" }, { name: "Public Key Y", type: "string", value: "DEADBEEF" }, { "name": "Output Format", "type": "option", "value": ["C1C3C2", "C1C2C3"], "defaultIndex": 0 }, { name: "Curve", type: "option", "value": ["sm2p256v1"], "defaultIndex": 0 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const [publicKeyX, publicKeyY, outputFormat, curveName] = args; this.outputFormat = outputFormat; if (publicKeyX.length !== 64 || publicKeyY.length !== 64) { throw new OperationError("Invalid Public Key - Ensure each component is 32 bytes in size and in hex"); } const sm2 = new SM2(curveName, outputFormat); sm2.setPublicKey(publicKeyX, publicKeyY); const result = sm2.encrypt(new Uint8Array(input)); return result; } } export default SM2Encrypt; ================================================ FILE: src/core/operations/SM3.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import Sm3 from "crypto-api/src/hasher/sm3.mjs"; import {toHex} from "crypto-api/src/encoder/hex.mjs"; /** * SM3 operation */ class SM3 extends Operation { /** * SM3 constructor */ constructor() { super(); this.name = "SM3"; this.module = "Crypto"; 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."; this.infoURL = "https://wikipedia.org/wiki/SM3_(hash_function)"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Length", type: "number", value: 256 }, { name: "Rounds", type: "number", value: 64, min: 16 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const msg = Utils.arrayBufferToStr(input, false); const hasher = new Sm3({length: args[0], rounds: args[1]}); hasher.update(msg); return toHex(hasher.finalize()); } } export default SM3; ================================================ FILE: src/core/operations/SM4Decrypt.mjs ================================================ /** * @author swesven * @copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import { toHex } from "../lib/Hex.mjs"; import { decryptSM4 } from "../lib/SM4.mjs"; /** * SM4 Decrypt operation */ class SM4Decrypt extends Operation { /** * SM4Encrypt constructor */ constructor() { super(); this.name = "SM4 Decrypt"; this.module = "Ciphers"; this.description = "SM4 is a 128-bit block cipher, currently established as a national standard (GB/T 32907-2016) of China."; this.infoURL = "https://wikipedia.org/wiki/SM4_(cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Mode", "type": "option", "value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"] }, { "name": "Input", "type": "option", "value": ["Raw", "Hex"] }, { "name": "Output", "type": "option", "value": ["Hex", "Raw"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), iv = Utils.convertToByteArray(args[1].string, args[1].option), [,, mode, inputType, outputType] = args; if (key.length !== 16) throw new OperationError(`Invalid key length: ${key.length} bytes SM4 uses a key length of 16 bytes (128 bits).`); if (iv.length !== 16 && !mode.startsWith("ECB")) throw new OperationError(`Invalid IV length: ${iv.length} bytes SM4 uses an IV length of 16 bytes (128 bits). Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); input = Utils.convertToByteArray(input, inputType); const output = decryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith("NoPadding")); return outputType === "Hex" ? toHex(output) : Utils.byteArrayToUtf8(output); } } export default SM4Decrypt; ================================================ FILE: src/core/operations/SM4Encrypt.mjs ================================================ /** * @author swesven * @copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import { toHex } from "../lib/Hex.mjs"; import { encryptSM4 } from "../lib/SM4.mjs"; /** * SM4 Encrypt operation */ class SM4Encrypt extends Operation { /** * SM4Encrypt constructor */ constructor() { super(); this.name = "SM4 Encrypt"; this.module = "Ciphers"; 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."; this.infoURL = "https://wikipedia.org/wiki/SM4_(cipher)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Mode", "type": "option", "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] }, { "name": "Input", "type": "option", "value": ["Raw", "Hex"] }, { "name": "Output", "type": "option", "value": ["Hex", "Raw"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), iv = Utils.convertToByteArray(args[1].string, args[1].option), [,, mode, inputType, outputType] = args; if (key.length !== 16) throw new OperationError(`Invalid key length: ${key.length} bytes SM4 uses a key length of 16 bytes (128 bits).`); if (iv.length !== 16 && !mode.startsWith("ECB")) throw new OperationError(`Invalid IV length: ${iv.length} bytes SM4 uses an IV length of 16 bytes (128 bits). Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); input = Utils.convertToByteArray(input, inputType); const output = encryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith("NoPadding")); return outputType === "Hex" ? toHex(output) : Utils.byteArrayToUtf8(output); } } export default SM4Encrypt; ================================================ FILE: src/core/operations/SQLBeautify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import { format } from "sql-formatter"; import Operation from "../Operation.mjs"; /** * SQL Beautify operation */ class SQLBeautify extends Operation { /** * SQLBeautify constructor */ constructor() { super(); this.name = "SQL Beautify"; this.module = "Code"; this.description = "Indents and prettifies Structured Query Language (SQL) code."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Indent string", "type": "binaryShortString", "value": "\\t" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const indentStr = args[0]; // Extract and replace bind variables like :Bind1 with __BIND_0__ const bindRegex = /:\w+/g; const bindMap = {}; let bindCounter=0; const placeholderInput = input.replace(bindRegex, (match) => { const placeholder = `__BIND_${bindCounter++}__`; bindMap[placeholder] = match; return placeholder; }); // Format the SQL with chosen options let formatted= format(placeholderInput, { language: "mysql", // Use MySQL as the default dialect for better compatibility with real-world SQL useTabs: indentStr==="\t", // true if tab, false if spaces tabWidth: indentStr.length || 4, // fallback if empty indentStyle: "standard" // fine for most SQL }); // Replace placeholders back with original bind variables formatted = formatted.replace(/__BIND_\d+__/g, match => bindMap[match] || match); return formatted; } } export default SQLBeautify; ================================================ FILE: src/core/operations/SQLMinify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import vkbeautify from "vkbeautify"; import Operation from "../Operation.mjs"; /** * SQL Minify operation */ class SQLMinify extends Operation { /** * SQLMinify constructor */ constructor() { super(); this.name = "SQL Minify"; this.module = "Code"; this.description = "Compresses Structured Query Language (SQL) code."; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return vkbeautify.sqlmin(input); } } export default SQLMinify; ================================================ FILE: src/core/operations/SSDEEP.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import ssdeepjs from "ssdeep.js"; /** * SSDEEP operation */ class SSDEEP extends Operation { /** * SSDEEP constructor */ constructor() { super(); this.name = "SSDEEP"; this.module = "Crypto"; 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.

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.

This operation is fundamentally the same as the CTPH operation, however their outputs differ in format."; this.infoURL = "https://forensics.wiki/ssdeep"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return ssdeepjs.digest(input); } } export default SSDEEP; ================================================ FILE: src/core/operations/SUB.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { bitOp, sub, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; /** * SUB operation */ class SUB extends Operation { /** * SUB constructor */ constructor() { super(); this.name = "SUB"; this.module = "Default"; this.description = "SUB the input with the given key (e.g. fe023da5), MOD 255"; this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bitwise_operators"; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": BITWISE_OP_DELIMS } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return bitOp(input, key, sub); } /** * Highlight SUB * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight SUB in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default SUB; ================================================ FILE: src/core/operations/Salsa20.mjs ================================================ /** * @author joostrijneveld [joost@joostrijneveld.nl] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { toHex } from "../lib/Hex.mjs"; import { salsa20Block } from "../lib/Salsa20.mjs"; /** * Salsa20 operation */ class Salsa20 extends Operation { /** * Salsa20 constructor */ constructor() { super(); this.name = "Salsa20"; this.module = "Ciphers"; 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.

Key: Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).

Nonce: Salsa20 uses a nonce of 8 bytes (64 bits).

Counter: 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."; this.infoURL = "https://wikipedia.org/wiki/Salsa20"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Nonce", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"] }, { "name": "Counter", "type": "number", "value": 0, "min": 0 }, { "name": "Rounds", "type": "option", "value": ["20", "12", "8"] }, { "name": "Input", "type": "option", "value": ["Hex", "Raw"] }, { "name": "Output", "type": "option", "value": ["Raw", "Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), nonceType = args[1].option, rounds = parseInt(args[3], 10), inputType = args[4], outputType = args[5]; if (key.length !== 16 && key.length !== 32) { throw new OperationError(`Invalid key length: ${key.length} bytes. Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`); } let counter, nonce; if (nonceType === "Integer") { nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little"); } else { nonce = Utils.convertToByteArray(args[1].string, args[1].option); if (!(nonce.length === 8)) { throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. Salsa20 uses a nonce of 8 bytes (64 bits).`); } } counter = Utils.intToByteArray(args[2], 8, "little"); const output = []; input = Utils.convertToByteArray(input, inputType); let counterAsInt = Utils.byteArrayToInt(counter, "little"); for (let i = 0; i < input.length; i += 64) { counter = Utils.intToByteArray(counterAsInt, 8, "little"); const stream = salsa20Block(key, nonce, counter, rounds); for (let j = 0; j < 64 && i + j < input.length; j++) { output.push(input[i + j] ^ stream[j]); } counterAsInt++; } if (outputType === "Hex") { return toHex(output); } else { return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); } } /** * Highlight Salsa20 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { const inputType = args[4], outputType = args[5]; if (inputType === "Raw" && outputType === "Raw") { return pos; } } /** * Highlight Salsa20 in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { const inputType = args[4], outputType = args[5]; if (inputType === "Raw" && outputType === "Raw") { return pos; } } } export default Salsa20; ================================================ FILE: src/core/operations/ScanForEmbeddedFiles.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { scanForFileTypes } from "../lib/FileType.mjs"; import { FILE_SIGNATURES } from "../lib/FileSignatures.mjs"; /** * Scan for Embedded Files operation */ class ScanForEmbeddedFiles extends Operation { /** * ScanForEmbeddedFiles constructor */ constructor() { super(); this.name = "Scan for Embedded Files"; this.module = "Default"; this.description = "Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.

WARNING: Files over about 100KB in size will take a VERY long time to process."; this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = Object.keys(FILE_SIGNATURES).map(cat => { return { name: cat, type: "boolean", value: cat === "Miscellaneous" ? false : true }; }); } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { 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", numFound = 0; const categories = [], data = new Uint8Array(input); args.forEach((cat, i) => { if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]); }); const types = scanForFileTypes(data, categories); if (types.length) { types.forEach(type => { numFound++; output += `\nOffset ${type.offset} (0x${Utils.hex(type.offset)}): File type: ${type.fileDetails.name} Extension: ${type.fileDetails.extension} MIME type: ${type.fileDetails.mime}\n`; if (type?.fileDetails?.description?.length) { output += ` Description: ${type.fileDetails.description}\n`; } }); } if (numFound === 0) { output += "\nNo embedded files were found."; } return output; } } export default ScanForEmbeddedFiles; ================================================ FILE: src/core/operations/ScatterChart.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import * as d3temp from "d3"; import * as nodomtemp from "nodom"; import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; const d3 = d3temp.default ? d3temp.default : d3temp; const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; /** * Scatter chart operation */ class ScatterChart extends Operation { /** * ScatterChart constructor */ constructor() { super(); this.name = "Scatter chart"; this.module = "Charts"; this.description = "Plots two-variable data as single points on a graph."; this.infoURL = "https://wikipedia.org/wiki/Scatter_plot"; this.inputType = "string"; this.outputType = "html"; this.args = [ { name: "Record delimiter", type: "option", value: RECORD_DELIMITER_OPTIONS, }, { name: "Field delimiter", type: "option", value: FIELD_DELIMITER_OPTIONS, }, { name: "Use column headers as labels", type: "boolean", value: true, }, { name: "X label", type: "string", value: "", }, { name: "Y label", type: "string", value: "", }, { name: "Colour", type: "string", value: COLOURS.max, }, { name: "Point radius", type: "number", value: 10, }, { name: "Use colour from third column", type: "boolean", value: false, } ]; } /** * Scatter chart operation. * * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const recordDelimiter = Utils.charRep(args[0]), fieldDelimiter = Utils.charRep(args[1]), columnHeadingsAreIncluded = args[2], fillColour = Utils.escapeHtml(args[5]), radius = args[6], colourInInput = args[7], dimension = 500; let xLabel = args[3], yLabel = args[4]; const dataFunction = colourInInput ? getScatterValuesWithColour : getScatterValues; const { headings, values } = dataFunction( input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded ); if (headings) { xLabel = headings.x; yLabel = headings.y; } const document = new nodom.Document(); let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") .attr("height", "100%") .attr("viewBox", `0 0 ${dimension} ${dimension}`); const margin = { top: 10, right: 0, bottom: 40, left: 30, }, width = dimension - margin.left - margin.right, height = dimension - margin.top - margin.bottom, marginedSpace = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); const xExtent = d3.extent(values, d => d[0]), xDelta = xExtent[1] - xExtent[0], yExtent = d3.extent(values, d => d[1]), yDelta = yExtent[1] - yExtent[0], xAxis = d3.scaleLinear() .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)]) .range([0, width]), yAxis = d3.scaleLinear() .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)]) .range([height, 0]); marginedSpace.append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", height); marginedSpace.append("g") .attr("class", "points") .attr("clip-path", "url(#clip)") .selectAll("circle") .data(values) .enter() .append("circle") .attr("cx", (d) => xAxis(d[0])) .attr("cy", (d) => yAxis(d[1])) .attr("r", d => radius) .attr("fill", d => { return colourInInput ? d[2] : fillColour; }) .attr("stroke", "rgba(0, 0, 0, 0.5)") .attr("stroke-width", "0.5") .append("title") .text(d => { const x = d[0], y = d[1], tooltip = `X: ${x}\n Y: ${y}\n `.replace(/\s{2,}/g, "\n"); return tooltip; }); marginedSpace.append("g") .attr("class", "axis axis--y") .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); svg.append("text") .attr("transform", "rotate(-90)") .attr("y", -margin.left) .attr("x", -(height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text(yLabel); marginedSpace.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); svg.append("text") .attr("x", width / 2) .attr("y", dimension) .style("text-anchor", "middle") .text(xLabel); return svg._groups[0][0].outerHTML; } } export default ScatterChart; ================================================ FILE: src/core/operations/Scrypt.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import scryptsy from "scryptsy"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * Scrypt operation */ class Scrypt extends Operation { /** * Scrypt constructor */ constructor() { super(); this.name = "Scrypt"; this.module = "Crypto"; 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.

Enter the password in the input to generate its hash."; this.infoURL = "https://wikipedia.org/wiki/Scrypt"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Salt", "type": "toggleString", "value": "", "toggleValues": ["Hex", "Base64", "UTF8", "Latin1"] }, { "name": "Iterations (N)", "type": "number", "value": 16384 }, { "name": "Memory factor (r)", "type": "number", "value": 8 }, { "name": "Parallelization factor (p)", "type": "number", "value": 1 }, { "name": "Key length", "type": "number", "value": 64 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const salt = Buffer.from(Utils.convertToByteArray(args[0].string || "", args[0].option)), iterations = args[1], memFactor = args[2], parallelFactor = args[3], keyLength = args[4]; try { const data = scryptsy( input, salt, iterations, memFactor, parallelFactor, keyLength, p => { // Progress callback if (isWorkerEnvironment()) self.sendStatusMessage(`Progress: ${p.percent.toFixed(0)}%`); } ); return data.toString("hex"); } catch (err) { throw new OperationError("Error: " + err.toString()); } } } export default Scrypt; ================================================ FILE: src/core/operations/SeriesChart.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import * as d3temp from "d3"; import * as nodomtemp from "nodom"; import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; const d3 = d3temp.default ? d3temp.default : d3temp; const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; /** * Series chart operation */ class SeriesChart extends Operation { /** * SeriesChart constructor */ constructor() { super(); this.name = "Series chart"; this.module = "Charts"; this.description = "A time series graph is a line graph of repeated measurements taken over regular time intervals."; this.inputType = "string"; this.outputType = "html"; this.args = [ { name: "Record delimiter", type: "option", value: RECORD_DELIMITER_OPTIONS, }, { name: "Field delimiter", type: "option", value: FIELD_DELIMITER_OPTIONS, }, { name: "X label", type: "string", value: "", }, { name: "Point radius", type: "number", value: 1, }, { name: "Series colours", type: "string", value: "mediumseagreen, dodgerblue, tomato", }, ]; } /** * Series chart operation. * * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const recordDelimiter = Utils.charRep(args[0]), fieldDelimiter = Utils.charRep(args[1]), xLabel = args[2], pipRadius = args[3], // Escape HTML from all colours to prevent reflected XSS. See https://github.com/gchq/CyberChef/issues/1265 seriesColours = args[4].split(",").map((colour) => { return Utils.escapeHtml(colour); }), svgWidth = 500, interSeriesPadding = 20, xAxisHeight = 50, seriesLabelWidth = 50, seriesHeight = 100, seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding; const { xValues, series } = getSeriesValues(input, recordDelimiter, fieldDelimiter), allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; const document = new nodom.Document(); let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") .attr("height", "100%") .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); const xAxis = d3.scalePoint() .domain(xValues) .range([0, seriesWidth]); svg.append("g") .attr("class", "axis axis--x") .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`) .call( d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => { return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0; })) ); svg.append("text") .attr("x", svgWidth / 2) .attr("y", xAxisHeight / 2) .style("text-anchor", "middle") .text(xLabel); const tooltipText = {}, tooltipAreaWidth = seriesWidth / xValues.length; xValues.forEach(x => { const tooltip = []; series.forEach(serie => { const y = serie.data[x]; if (typeof y === "undefined") return; tooltip.push(`${serie.name}: ${y}`); }); tooltipText[x] = tooltip.join("\n"); }); const chartArea = svg.append("g") .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`); chartArea .append("g") .selectAll("rect") .data(xValues) .enter() .append("rect") .attr("x", x => { return xAxis(x) - (tooltipAreaWidth / 2); }) .attr("y", 0) .attr("width", tooltipAreaWidth) .attr("height", allSeriesHeight) .attr("stroke", "none") .attr("fill", "transparent") .append("title") .text(x => { return `${x}\n --\n ${tooltipText[x]}\n `.replace(/\s{2,}/g, "\n"); }); const yAxesArea = svg.append("g") .attr("transform", `translate(0, ${xAxisHeight})`); series.forEach((serie, seriesIndex) => { const yExtent = d3.extent(Object.values(serie.data)), yAxis = d3.scaleLinear() .domain(yExtent) .range([seriesHeight, 0]); const seriesGroup = chartArea .append("g") .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`); let path = ""; xValues.forEach((x, xIndex) => { let nextX = xValues[xIndex + 1], y = serie.data[x], nextY= serie.data[nextX]; if (typeof y === "undefined" || typeof nextY === "undefined") return; x = xAxis(x); nextX = xAxis(nextX); y = yAxis(y); nextY = yAxis(nextY); path += `M ${x} ${y} L ${nextX} ${nextY} z `; }); seriesGroup .append("path") .attr("d", path) .attr("fill", "none") .attr("stroke", seriesColours[seriesIndex % seriesColours.length]) .attr("stroke-width", "1"); xValues.forEach(x => { const y = serie.data[x]; if (typeof y === "undefined") return; seriesGroup .append("circle") .attr("cx", xAxis(x)) .attr("cy", yAxis(y)) .attr("r", pipRadius) .attr("fill", seriesColours[seriesIndex % seriesColours.length]) .append("title") .text(d => { return `${x}\n --\n ${tooltipText[x]}\n `.replace(/\s{2,}/g, "\n"); }); }); yAxesArea .append("g") .attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) .attr("class", "axis axis--y") .call(d3.axisLeft(yAxis).ticks(5)); yAxesArea .append("g") .attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) .append("text") .style("text-anchor", "middle") .attr("transform", "rotate(-90)") .text(serie.name); }); return svg._groups[0][0].outerHTML; } } export default SeriesChart; ================================================ FILE: src/core/operations/SetDifference.mjs ================================================ /** * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Set Difference operation */ class SetDifference extends Operation { /** * Set Difference constructor */ constructor() { super(); this.name = "Set Difference"; this.module = "Default"; this.description = "Calculates the difference, or relative complement, of two sets."; this.infoURL = "https://wikipedia.org/wiki/Complement_(set_theory)#Relative_complement"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Sample delimiter", type: "binaryString", value: "\\n\\n" }, { name: "Item delimiter", type: "binaryString", value: "," }, ]; } /** * Validate input length * * @param {Object[]} sets * @throws {Error} if not two sets */ validateSampleNumbers(sets) { if (!sets || (sets.length !== 2)) { throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); } } /** * Run the difference operation * * @param {string} input * @param {Object[]} args * @returns {string} * @throws {OperationError} */ run(input, args) { [this.sampleDelim, this.itemDelimiter] = args; const sets = input.split(this.sampleDelim); this.validateSampleNumbers(sets); return this.runSetDifference(...sets.map(s => s.split(this.itemDelimiter))); } /** * Get elements in set a that are not in set b * * @param {Object[]} a * @param {Object[]} b * @returns {Object[]} */ runSetDifference(a, b) { return a .filter((item) => { return b.indexOf(item) === -1; }) .join(this.itemDelimiter); } } export default SetDifference; ================================================ FILE: src/core/operations/SetIntersection.mjs ================================================ /** * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Set Intersection operation */ class SetIntersection extends Operation { /** * Set Intersection constructor */ constructor() { super(); this.name = "Set Intersection"; this.module = "Default"; this.description = "Calculates the intersection of two sets."; this.infoURL = "https://wikipedia.org/wiki/Intersection_(set_theory)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Sample delimiter", type: "binaryString", value: "\\n\\n" }, { name: "Item delimiter", type: "binaryString", value: "," }, ]; } /** * Validate input length * * @param {Object[]} sets * @throws {Error} if not two sets */ validateSampleNumbers(sets) { if (!sets || (sets.length !== 2)) { throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); } } /** * Run the intersection operation * * @param {string} input * @param {Object[]} args * @returns {string} * @throws {OperationError} */ run(input, args) { [this.sampleDelim, this.itemDelimiter] = args; const sets = input.split(this.sampleDelim); this.validateSampleNumbers(sets); return this.runIntersect(...sets.map(s => s.split(this.itemDelimiter))); } /** * Get the intersection of the two sets. * * @param {Object[]} a * @param {Object[]} b * @returns {Object[]} */ runIntersect(a, b) { return a .filter((item) => { return b.indexOf(item) > -1; }) .join(this.itemDelimiter); } } export default SetIntersection; ================================================ FILE: src/core/operations/SetUnion.mjs ================================================ /** * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Set Union operation */ class SetUnion extends Operation { /** * Set Union constructor */ constructor() { super(); this.name = "Set Union"; this.module = "Default"; this.description = "Calculates the union of two sets."; this.infoURL = "https://wikipedia.org/wiki/Union_(set_theory)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Sample delimiter", type: "binaryString", value: "\\n\\n" }, { name: "Item delimiter", type: "binaryString", value: "," }, ]; } /** * Validate input length * * @param {Object[]} sets * @throws {Error} if not two sets */ validateSampleNumbers(sets) { if (!sets || (sets.length !== 2)) { throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); } } /** * Run the union operation * * @param {string} input * @param {Object[]} args * @returns {string} * @throws {OperationError} */ run(input, args) { [this.sampleDelim, this.itemDelimiter] = args; const sets = input.split(this.sampleDelim); this.validateSampleNumbers(sets); return this.runUnion(...sets.map(s => s.split(this.itemDelimiter))); } /** * Get the union of the two sets. * * @param {Object[]} a * @param {Object[]} b * @returns {Object[]} */ runUnion(a, b) { const result = {}; /** * Only add non-existing items * @param {Object} hash */ const addUnique = (hash) => (item) => { if (!hash[item]) { hash[item] = true; } }; a.map(addUnique(result)); b.map(addUnique(result)); return Object.keys(result).join(this.itemDelimiter); } } export default SetUnion; ================================================ FILE: src/core/operations/Shake.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import JSSHA3 from "js-sha3"; /** * Shake operation */ class Shake extends Operation { /** * Shake constructor */ constructor() { super(); this.name = "Shake"; this.module = "Crypto"; 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."; this.infoURL = "https://wikipedia.org/wiki/SHA-3#Instances"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Capacity", "type": "option", "value": ["256", "128"] }, { "name": "Size", "type": "number", "value": 512 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const capacity = parseInt(args[0], 10), size = args[1]; let algo; if (size < 0) throw new OperationError("Size must be greater than 0"); switch (capacity) { case 128: algo = JSSHA3.shake128; break; case 256: algo = JSSHA3.shake256; break; default: throw new OperationError("Invalid size"); } return algo(input, size); } } export default Shake; ================================================ FILE: src/core/operations/SharpenImage.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Sharpen Image operation */ class SharpenImage extends Operation { /** * SharpenImage constructor */ constructor() { super(); this.name = "Sharpen Image"; this.module = "Image"; this.description = "Sharpens an image (Unsharp mask)"; this.infoURL = "https://wikipedia.org/wiki/Unsharp_masking"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Radius", type: "number", value: 2, min: 1, }, { name: "Amount", type: "number", value: 1, min: 0, step: 0.1, }, { name: "Threshold", type: "number", value: 10, min: 0, max: 100, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ async run(input, args) { const [radius, amount, threshold] = args; if (!isImage(input)) { throw new OperationError("Invalid file type."); } let image; try { image = await Jimp.read(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (isWorkerEnvironment()) self.sendStatusMessage("Sharpening image... (Cloning image)"); const blurMask = image.clone(); if (isWorkerEnvironment()) self.sendStatusMessage( "Sharpening image... (Blurring cloned image)", ); const blurImage = image.clone().gaussian(radius); if (isWorkerEnvironment()) self.sendStatusMessage( "Sharpening image... (Creating unsharp mask)", ); blurMask.scan( 0, 0, blurMask.bitmap.width, blurMask.bitmap.height, function (x, y, idx) { const blurRed = blurImage.bitmap.data[idx]; const blurGreen = blurImage.bitmap.data[idx + 1]; const blurBlue = blurImage.bitmap.data[idx + 2]; const normalRed = this.bitmap.data[idx]; const normalGreen = this.bitmap.data[idx + 1]; const normalBlue = this.bitmap.data[idx + 2]; // Subtract blurred pixel value from normal image this.bitmap.data[idx] = normalRed > blurRed ? normalRed - blurRed : 0; this.bitmap.data[idx + 1] = normalGreen > blurGreen ? normalGreen - blurGreen : 0; this.bitmap.data[idx + 2] = normalBlue > blurBlue ? normalBlue - blurBlue : 0; }, ); if (isWorkerEnvironment()) self.sendStatusMessage( "Sharpening image... (Merging with unsharp mask)", ); image.scan( 0, 0, image.bitmap.width, image.bitmap.height, function (x, y, idx) { let maskRed = blurMask.bitmap.data[idx]; let maskGreen = blurMask.bitmap.data[idx + 1]; let maskBlue = blurMask.bitmap.data[idx + 2]; const normalRed = this.bitmap.data[idx]; const normalGreen = this.bitmap.data[idx + 1]; const normalBlue = this.bitmap.data[idx + 2]; // Calculate luminance const maskLuminance = 0.2126 * maskRed + 0.7152 * maskGreen + 0.0722 * maskBlue; const normalLuminance = 0.2126 * normalRed + 0.7152 * normalGreen + 0.0722 * normalBlue; let luminanceDiff; if (maskLuminance > normalLuminance) { luminanceDiff = maskLuminance - normalLuminance; } else { luminanceDiff = normalLuminance - maskLuminance; } // Scale mask colours by amount maskRed = maskRed * amount; maskGreen = maskGreen * amount; maskBlue = maskBlue * amount; // Only change pixel value if the difference is higher than threshold if ((luminanceDiff / 255) * 100 >= threshold) { this.bitmap.data[idx] = normalRed + maskRed <= 255 ? normalRed + maskRed : 255; this.bitmap.data[idx + 1] = normalGreen + maskGreen <= 255 ? normalGreen + maskGreen : 255; this.bitmap.data[idx + 2] = normalBlue + maskBlue <= 255 ? normalBlue + maskBlue : 255; } }, ); let imageBuffer; if (image.mime === "image/gif") { imageBuffer = await image.getBuffer(JimpMime.png); } else { imageBuffer = await image.getBuffer(image.mime); } return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error sharpening image. (${err})`); } } /** * Displays the sharpened image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default SharpenImage; ================================================ FILE: src/core/operations/ShowBase64Offsets.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {fromBase64, toBase64} from "../lib/Base64.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Show Base64 offsets operation */ class ShowBase64Offsets extends Operation { /** * ShowBase64Offsets constructor */ constructor() { super(); this.name = "Show Base64 offsets"; this.module = "Default"; 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.

This operation shows all possible offsets for a given string so that each possible encoding can be considered."; this.infoURL = "https://wikipedia.org/wiki/Base64#Output_padding"; this.inputType = "byteArray"; this.outputType = "html"; this.args = [ { name: "Alphabet", type: "binaryString", value: "A-Za-z0-9+/=" }, { name: "Show variable chars and padding", type: "boolean", value: true }, { name: "Input format", type: "option", value: ["Raw", "Base64"] } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {html} */ run(input, args) { const [alphabet, showVariable, format] = args; if (format === "Base64") { input = fromBase64(Utils.byteArrayToUtf8(input), null, "byteArray"); } let offset0 = toBase64(input, alphabet), offset1 = toBase64([0].concat(input), alphabet), offset2 = toBase64([0, 0].concat(input), alphabet), staticSection = "", padding = ""; const len0 = offset0.indexOf("="), len1 = offset1.indexOf("="), len2 = offset2.indexOf("="), script = ""; if (input.length < 1) { throw new OperationError("Please enter a string."); } // Highlight offset 0 if (len0 % 4 === 2) { staticSection = offset0.slice(0, -3); offset0 = "" + staticSection + "" + "" + offset0.substr(offset0.length - 3, 1) + "" + "" + offset0.substr(offset0.length - 2) + ""; } else if (len0 % 4 === 3) { staticSection = offset0.slice(0, -2); offset0 = "" + staticSection + "" + "" + offset0.substr(offset0.length - 2, 1) + "" + "" + offset0.substr(offset0.length - 1) + ""; } else { staticSection = offset0; offset0 = "" + staticSection + ""; } if (!showVariable) { offset0 = staticSection; } // Highlight offset 1 padding = "" + offset1.substr(0, 1) + "" + "" + offset1.substr(1, 1) + ""; offset1 = offset1.substr(2); if (len1 % 4 === 2) { staticSection = offset1.slice(0, -3); offset1 = padding + "" + staticSection + "" + "" + offset1.substr(offset1.length - 3, 1) + "" + "" + offset1.substr(offset1.length - 2) + ""; } else if (len1 % 4 === 3) { staticSection = offset1.slice(0, -2); offset1 = padding + "" + staticSection + "" + "" + offset1.substr(offset1.length - 2, 1) + "" + "" + offset1.substr(offset1.length - 1) + ""; } else { staticSection = offset1; offset1 = padding + "" + staticSection + ""; } if (!showVariable) { offset1 = staticSection; } // Highlight offset 2 padding = "" + offset2.substr(0, 2) + "" + "" + offset2.substr(2, 1) + ""; offset2 = offset2.substr(3); if (len2 % 4 === 2) { staticSection = offset2.slice(0, -3); offset2 = padding + "" + staticSection + "" + "" + offset2.substr(offset2.length - 3, 1) + "" + "" + offset2.substr(offset2.length - 2) + ""; } else if (len2 % 4 === 3) { staticSection = offset2.slice(0, -2); offset2 = padding + "" + staticSection + "" + "" + offset2.substr(offset2.length - 2, 1) + "" + "" + offset2.substr(offset2.length - 1) + ""; } else { staticSection = offset2; offset2 = padding + "" + staticSection + ""; } if (!showVariable) { offset2 = staticSection; } return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." + "\nCharacters highlighted in red are for padding purposes only." + "\nUnhighlighted characters are static." + "\nHover over the static sections to see what they decode to on their own.\n" + "\nOffset 0: " + offset0 + "\nOffset 1: " + offset1 + "\nOffset 2: " + offset2 + script : offset0 + "\n" + offset1 + "\n" + offset2); } } export default ShowBase64Offsets; ================================================ FILE: src/core/operations/ShowOnMap.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @author 0xff1ce [github.com/0xff1ce] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Show on map operation */ class ShowOnMap extends Operation { /** * ShowOnMap constructor */ constructor() { super(); this.name = "Show on map"; this.module = "Hashing"; this.description = "Displays co-ordinates on a slippy map.

Co-ordinates will be converted to decimal degrees before being shown on the map.

Supported formats:
  • Degrees Minutes Seconds (DMS)
  • Degrees Decimal Minutes (DDM)
  • Decimal Degrees (DD)
  • Geohash
  • Military Grid Reference System (MGRS)
  • Ordnance Survey National Grid (OSNG)
  • Universal Transverse Mercator (UTM)

This operation will not work offline."; this.infoURL = "https://osmfoundation.org/wiki/Terms_of_Use"; this.inputType = "string"; this.outputType = "string"; this.presentType = "html"; this.args = [ { name: "Zoom Level", type: "number", value: 13 }, { name: "Input Format", type: "option", value: ["Auto"].concat(FORMATS) }, { name: "Input Delimiter", type: "option", value: [ "Auto", "Direction Preceding", "Direction Following", "\\n", "Comma", "Semi-colon", "Colon" ] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (input.replace(/\s+/g, "") !== "") { const inFormat = args[1], inDelim = args[2]; let latLong; try { latLong = convertCoordinates(input, inFormat, inDelim, "Decimal Degrees", "Comma", "None", 5); } catch (error) { throw new OperationError(error); } latLong = latLong.replace(/[,]$/, ""); latLong = latLong.replace(/°/g, ""); return latLong; } return input; } /** * @param {string} data * @param {Object[]} args * @returns {string} */ async present(data, args) { if (data.replace(/\s+/g, "") === "") { data = "0, 0"; } const zoomLevel = args[0]; const tileUrl = "https://tile.openstreetmap.org/{z}/{x}/{y}.png", tileAttribution = "© OpenStreetMap contributors", leafletUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js", leafletCssUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"; return `
`; } } export default ShowOnMap; ================================================ FILE: src/core/operations/Shuffle.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * Shuffle operation */ class Shuffle extends Operation { /** * Shuffle constructor */ constructor() { super(); this.name = "Shuffle"; this.module = "Default"; this.description = "Randomly reorders input elements."; this.infoURL = "https://wikipedia.org/wiki/Shuffling"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Delimiter", type: "option", value: INPUT_DELIM_OPTIONS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = Utils.charRep(args[0]); if (input.length === 0) return input; // return a random number in [0, 1) const rng = (typeof crypto) !== "undefined" && crypto.getRandomValues ? (function() { const buf = new Uint32Array(2); return function() { // generate 53-bit random integer: 21 + 32 bits crypto.getRandomValues(buf); const value = (buf[0] >>> (32 - 21)) * ((1 << 30) * 4) + buf[1]; return value / ((1 << 23) * (1 << 30)); }; })() : Math.random; // return a random integer in [0, max) const randint = function(max) { return Math.floor(rng() * max); }; // Split input into shuffleable sections const toShuffle = input.split(delim); // shuffle elements for (let i = toShuffle.length - 1; i > 0; i--) { const idx = randint(i + 1); const tmp = toShuffle[idx]; toShuffle[idx] = toShuffle[i]; toShuffle[i] = tmp; } return toShuffle.join(delim); } } export default Shuffle; ================================================ FILE: src/core/operations/Sleep.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Sleep operation */ class Sleep extends Operation { /** * Sleep constructor */ constructor() { super(); this.name = "Sleep"; this.module = "Default"; this.description = "Sleep causes the recipe to wait for a specified number of milliseconds before continuing execution."; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { "name": "Time (ms)", "type": "number", "value": 1000 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ async run(input, args) { const ms = args[0]; await new Promise(r => setTimeout(r, ms)); return input; } } export default Sleep; ================================================ FILE: src/core/operations/Snefru.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * Snefru operation */ class Snefru extends Operation { /** * Snefru constructor */ constructor() { super(); this.name = "Snefru"; this.module = "Crypto"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/Snefru"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Size", type: "number", value: 128, min: 32, max: 480, step: 32 }, { name: "Rounds", type: "option", value: ["8", "4", "2"] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { return runHash("snefru", input, { length: args[0], rounds: args[1] }); } } export default Snefru; ================================================ FILE: src/core/operations/Sort.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort, lengthSort} from "../lib/Sort.mjs"; /** * Sort operation */ class Sort extends Operation { /** * Sort constructor */ constructor() { super(); this.name = "Sort"; this.module = "Default"; this.description = "Alphabetically sorts strings separated by the specified delimiter.

The IP address option supports IPv4 only."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": INPUT_DELIM_OPTIONS }, { "name": "Reverse", "type": "boolean", "value": false }, { "name": "Order", "type": "option", "value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric", "Numeric (hexadecimal)", "Length"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = Utils.charRep(args[0]), sortReverse = args[1], order = args[2]; let sorted = input.split(delim); if (order === "Alphabetical (case sensitive)") { sorted = sorted.sort(); } else if (order === "Alphabetical (case insensitive)") { sorted = sorted.sort(caseInsensitiveSort); } else if (order === "IP address") { sorted = sorted.sort(ipSort); } else if (order === "Numeric") { sorted = sorted.sort(numericSort); } else if (order === "Numeric (hexadecimal)") { sorted = sorted.sort(hexadecimalSort); } else if (order === "Length") { sorted = sorted.sort(lengthSort); } if (sortReverse) sorted.reverse(); return sorted.join(delim); } } export default Sort; ================================================ FILE: src/core/operations/Split.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {SPLIT_DELIM_OPTIONS, JOIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * Split operation */ class Split extends Operation { /** * Split constructor */ constructor() { super(); this.name = "Split"; this.module = "Default"; this.description = "Splits a string into sections around a given delimiter."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Split delimiter", "type": "editableOptionShort", "value": SPLIT_DELIM_OPTIONS }, { "name": "Join delimiter", "type": "editableOptionShort", "value": JOIN_DELIM_OPTIONS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const splitDelim = args[0], joinDelim = args[1], sections = input.split(splitDelim); return sections.join(joinDelim); } } export default Split; ================================================ FILE: src/core/operations/SplitColourChannels.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { isImage } from "../lib/FileType.mjs"; import { Jimp, JimpMime } from "jimp"; /** * Split Colour Channels operation */ class SplitColourChannels extends Operation { /** * SplitColourChannels constructor */ constructor() { super(); this.name = "Split Colour Channels"; this.module = "Image"; this.description = "Splits the given image into its red, green and blue colour channels."; this.infoURL = "https://wikipedia.org/wiki/Channel_(digital_image)"; this.inputType = "ArrayBuffer"; this.outputType = "List"; this.presentType = "html"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {List} */ async run(input, args) { input = new Uint8Array(input); // Make sure that the input is an image if (!isImage(input)) throw new OperationError("Invalid file type."); const parsedImage = await Jimp.read(Buffer.from(input)); const red = new Promise(async (resolve, reject) => { try { const split = parsedImage .clone() .color([ { apply: "blue", params: [-255] }, { apply: "green", params: [-255] }, ]) .getBuffer(JimpMime.png); resolve( new File( [new Uint8Array((await split).values())], "red.png", { type: "image/png" }, ), ); } catch (err) { reject( new OperationError(`Could not split red channel: ${err}`), ); } }); const green = new Promise(async (resolve, reject) => { try { const split = parsedImage .clone() .color([ { apply: "red", params: [-255] }, { apply: "blue", params: [-255] }, ]) .getBuffer(JimpMime.png); resolve( new File( [new Uint8Array((await split).values())], "green.png", { type: "image/png" }, ), ); } catch (err) { reject( new OperationError(`Could not split green channel: ${err}`), ); } }); const blue = new Promise(async (resolve, reject) => { try { const split = parsedImage .color([ { apply: "red", params: [-255] }, { apply: "green", params: [-255] }, ]) .getBuffer(JimpMime.png); resolve( new File( [new Uint8Array((await split).values())], "blue.png", { type: "image/png" }, ), ); } catch (err) { reject( new OperationError(`Could not split blue channel: ${err}`), ); } }); return await Promise.all([red, green, blue]); } /** * Displays the files in HTML for web apps. * * @param {File[]} files * @returns {html} */ async present(files) { return await Utils.displayFilesAsHTML(files); } } export default SplitColourChannels; ================================================ FILE: src/core/operations/StandardDeviation.mjs ================================================ /** * @author bwhitn [brian.m.whitney@outlook.com] * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import BigNumber from "bignumber.js"; import Operation from "../Operation.mjs"; import { stdDev, createNumArray } from "../lib/Arithmetic.mjs"; import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; /** * Standard Deviation operation */ class StandardDeviation extends Operation { /** * StandardDeviation constructor */ constructor() { super(); this.name = "Standard Deviation"; this.module = "Default"; 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.

e.g. 0x0a 8 .5 becomes 4.089281382128433"; this.infoURL = "https://wikipedia.org/wiki/Standard_deviation"; this.inputType = "string"; this.outputType = "BigNumber"; this.args = [ { "name": "Delimiter", "type": "option", "value": ARITHMETIC_DELIM_OPTIONS, } ]; } /** * @param {string} input * @param {Object[]} args * @returns {BigNumber} */ run(input, args) { const val = stdDev(createNumArray(input, args[0])); return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); } } export default StandardDeviation; ================================================ FILE: src/core/operations/Streebog.mjs ================================================ /** * @author mshwed [m@ttshwed.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import GostDigest from "../vendor/gost/gostDigest.mjs"; import {toHexFast} from "../lib/Hex.mjs"; /** * Streebog operation */ class Streebog extends Operation { /** * Streebog constructor */ constructor() { super(); this.name = "Streebog"; this.module = "Hashing"; this.description = "Streebog is a cryptographic hash function defined in the Russian national standard GOST R 34.11-2012 Information Technology \u2013 Cryptographic Information Security \u2013 Hash Function. 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."; this.infoURL = "https://wikipedia.org/wiki/Streebog"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Digest length", "type": "option", "value": ["256", "512"] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [length] = args; const algorithm = { version: 2012, mode: "HASH", length: parseInt(length, 10) }; try { const gostDigest = new GostDigest(algorithm); return toHexFast(gostDigest.digest(input)); } catch (err) { throw new OperationError(err); } } } export default Streebog; ================================================ FILE: src/core/operations/Strings.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import XRegExp from "xregexp"; import { search } from "../lib/Extract.mjs"; import { caseInsensitiveSort } from "../lib/Sort.mjs"; /** * Strings operation */ class Strings extends Operation { /** * Strings constructor */ constructor() { super(); this.name = "Strings"; this.module = "Regex"; this.description = "Extracts all strings from the input."; this.infoURL = "https://wikipedia.org/wiki/Strings_(Unix)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Encoding", type: "option", value: ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"] }, { name: "Minimum length", type: "number", value: 4 }, { name: "Match", type: "option", value: [ "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)", "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)" ] }, { name: "Display total", type: "boolean", value: false }, { name: "Sort", type: "boolean", value: false }, { name: "Unique", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [encoding, minLen, matchType, displayTotal, sort, unique] = args, alphanumeric = "A-Z\\d", punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@", printable = "\x20-\x7e", uniAlphanumeric = "\\pL\\pN", uniPunctuation = "\\pP\\pZ", uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP"; let strings = ""; switch (matchType) { case "Alphanumeric + punctuation (A)": strings = `[${alphanumeric + punctuation}]`; break; case "All printable chars (A)": case "Null-terminated strings (A)": strings = `[${printable}]`; break; case "Alphanumeric + punctuation (U)": strings = `[${uniAlphanumeric + uniPunctuation}]`; break; case "All printable chars (U)": case "Null-terminated strings (U)": strings = `[${uniPrintable}]`; break; } // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars switch (encoding) { case "All": strings = `(\x00?${strings}\x00?)`; break; case "16-bit littleendian": strings = `(${strings}\x00)`; break; case "16-bit bigendian": strings = `(\x00${strings})`; break; case "Single byte": default: break; } strings = `${strings}{${minLen},}`; if (matchType.includes("Null-terminated")) { strings += "\x00"; } const regex = new XRegExp(strings, "ig"); const results = search( input, regex, null, sort ? caseInsensitiveSort : null, unique ); if (displayTotal) { return `Total found: ${results.length}\n\n${results.join("\n")}`; } else { return results.join("\n"); } } } export default Strings; ================================================ FILE: src/core/operations/StripHTMLTags.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Strip HTML tags operation */ class StripHTMLTags extends Operation { /** * StripHTMLTags constructor */ constructor() { super(); this.name = "Strip HTML tags"; this.module = "Default"; this.description = "Removes all HTML tags from the input."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Remove indentation", "type": "boolean", "value": true }, { "name": "Remove excess line breaks", "type": "boolean", "value": true } ]; this.checks = [ { pattern: "(|
|)", flags: "i", args: [true, true] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [removeIndentation, removeLineBreaks] = args; input = Utils.stripHtmlTags(input); if (removeIndentation) { input = input.replace(/\n[ \f\t]+/g, "\n"); } if (removeLineBreaks) { input = input .replace(/^\s*\n/, "") // first line .replace(/(\n\s*){2,}/g, "\n"); // all others } return input; } } export default StripHTMLTags; ================================================ FILE: src/core/operations/StripHTTPHeaders.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Strip HTTP headers operation */ class StripHTTPHeaders extends Operation { /** * StripHTTPHeaders constructor */ constructor() { super(); this.name = "Strip HTTP headers"; this.module = "Default"; this.description = "Removes HTTP headers from a request or response by looking for the first instance of a double newline."; this.infoURL = "https://wikipedia.org/wiki/Hypertext_Transfer_Protocol#Message_format"; this.inputType = "string"; this.outputType = "string"; this.args = []; this.checks = [ { pattern: "^HTTP(.|\\s)+?(\\r?\\n){2}", flags: "", args: [] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let headerEnd = input.indexOf("\r\n\r\n"); headerEnd = (headerEnd < 0) ? input.indexOf("\n\n") + 2 : headerEnd + 4; return (headerEnd < 2) ? input : input.slice(headerEnd, input.length); } } export default StripHTTPHeaders; ================================================ FILE: src/core/operations/StripIPv4Header.mjs ================================================ /** * @author c65722 [] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Stream from "../lib/Stream.mjs"; /** * Strip IPv4 header operation */ class StripIPv4Header extends Operation { /** * StripIPv4Header constructor */ constructor() { super(); this.name = "Strip IPv4 header"; this.module = "Default"; this.description = "Strips the IPv4 header from an IPv4 packet, outputting the payload."; this.infoURL = "https://wikipedia.org/wiki/IPv4"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const MIN_HEADER_LEN = 20; const s = new Stream(new Uint8Array(input)); if (s.length < MIN_HEADER_LEN) { throw new OperationError("Input length is less than minimum IPv4 header length"); } const ihl = s.readInt(1) & 0x0f; const dataOffsetBytes = ihl * 4; if (s.length < dataOffsetBytes) { throw new OperationError("Input length is less than IHL"); } s.moveTo(dataOffsetBytes); return s.getBytes().buffer; } } export default StripIPv4Header; ================================================ FILE: src/core/operations/StripTCPHeader.mjs ================================================ /** * @author c65722 [] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Stream from "../lib/Stream.mjs"; /** * Strip TCP header operation */ class StripTCPHeader extends Operation { /** * StripTCPHeader constructor */ constructor() { super(); this.name = "Strip TCP header"; this.module = "Default"; this.description = "Strips the TCP header from a TCP segment, outputting the payload."; this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const MIN_HEADER_LEN = 20; const DATA_OFFSET_OFFSET = 12; const DATA_OFFSET_LEN_BITS = 4; const s = new Stream(new Uint8Array(input)); if (s.length < MIN_HEADER_LEN) { throw new OperationError("Need at least 20 bytes for a TCP Header"); } s.moveTo(DATA_OFFSET_OFFSET); const dataOffsetWords = s.readBits(DATA_OFFSET_LEN_BITS); const dataOffsetBytes = dataOffsetWords * 4; if (s.length < dataOffsetBytes) { throw new OperationError("Input length is less than data offset"); } s.moveTo(dataOffsetBytes); return s.getBytes().buffer; } } export default StripTCPHeader; ================================================ FILE: src/core/operations/StripUDPHeader.mjs ================================================ /** * @author c65722 [] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Stream from "../lib/Stream.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Strip UDP header operation */ class StripUDPHeader extends Operation { /** * StripUDPHeader constructor */ constructor() { super(); this.name = "Strip UDP header"; this.module = "Default"; this.description = "Strips the UDP header from a UDP datagram, outputting the payload."; this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const HEADER_LEN = 8; const s = new Stream(new Uint8Array(input)); if (s.length < HEADER_LEN) { throw new OperationError("Need 8 bytes for a UDP Header"); } s.moveTo(HEADER_LEN); return s.getBytes().buffer; } } export default StripUDPHeader; ================================================ FILE: src/core/operations/Subsection.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Recipe from "../Recipe.mjs"; import Dish from "../Dish.mjs"; /** * Subsection operation */ class Subsection extends Operation { /** * Subsection constructor */ constructor() { super(); this.name = "Subsection"; this.flowControl = true; this.module = "Default"; this.description = "Select a part of the input data using a regular expression (regex), and run all subsequent operations on each match separately.

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.

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

Enter the bytes you want to replace in the Plaintext field and the bytes to replace them with in the Ciphertext field.

Non-printable bytes can be specified using string escape notation. For example, a line feed character can be written as either \\n or \\x0a.

Byte ranges can be specified using a hyphen. For example, the sequence 0123456789 can be written as 0-9.

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.\\\\)."; this.infoURL = "https://wikipedia.org/wiki/Substitution_cipher"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Plaintext", "type": "binaryString", "value": "ABCDEFGHIJKLMNOPQRSTUVWXYZ" }, { "name": "Ciphertext", "type": "binaryString", "value": "XYZABCDEFGHIJKLMNOPQRSTUVW" }, { "name": "Ignore case", "type": "boolean", "value": false } ]; } /** * Convert a single character using the dictionary, if ignoreCase is true then * check in the dictionary for both upper and lower case versions of the character. * In output the input character case is preserved. * @param {string} char * @param {Object} dict * @param {boolean} ignoreCase * @returns {string} */ cipherSingleChar(char, dict, ignoreCase) { if (!ignoreCase) return dict[char] || char; const isUpperCase = char === char.toUpperCase(); // convert using the dictionary keeping the case of the input character if (dict[char] !== undefined) { // if the character is in the dictionary return the value with the input case return isUpperCase ? dict[char].toUpperCase() : dict[char].toLowerCase(); } // check for the other case, if it is in the dictionary return the value with the right case if (isUpperCase) { if (dict[char.toLowerCase()] !== undefined) return dict[char.toLowerCase()].toUpperCase(); } else { if (dict[char.toUpperCase()] !== undefined) return dict[char.toUpperCase()].toLowerCase(); } return char; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const plaintext = Utils.expandAlphRange([...args[0]]), ciphertext = Utils.expandAlphRange([...args[1]]), ignoreCase = args[2]; let output = ""; if (plaintext.length !== ciphertext.length) { output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; } // create dictionary for conversion const dict = {}; for (let i = 0; i < Math.min(ciphertext.length, plaintext.length); i++) { dict[plaintext[i]] = ciphertext[i]; } // map every letter with the conversion function for (const character of input) { output += this.cipherSingleChar(character, dict, ignoreCase); } return output; } } export default Substitute; ================================================ FILE: src/core/operations/Subtract.mjs ================================================ /** * @author bwhitn [brian.m.whitney@outlook.com] * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import BigNumber from "bignumber.js"; import Operation from "../Operation.mjs"; import { sub, createNumArray } from "../lib/Arithmetic.mjs"; import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; /** * Subtract operation */ class Subtract extends Operation { /** * Subtract constructor */ constructor() { super(); this.name = "Subtract"; this.module = "Default"; this.description = "Subtracts a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 1.5"; this.infoURL = "https://wikipedia.org/wiki/Subtraction"; this.inputType = "string"; this.outputType = "BigNumber"; this.args = [ { "name": "Delimiter", "type": "option", "value": ARITHMETIC_DELIM_OPTIONS, } ]; } /** * @param {string} input * @param {Object[]} args * @returns {BigNumber} */ run(input, args) { const val = sub(createNumArray(input, args[0])); return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); } } export default Subtract; ================================================ FILE: src/core/operations/Sum.mjs ================================================ /** * @author bwhitn [brian.m.whitney@outlook.com] * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import BigNumber from "bignumber.js"; import Operation from "../Operation.mjs"; import { sum, createNumArray } from "../lib/Arithmetic.mjs"; import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; /** * Sum operation */ class Sum extends Operation { /** * Sum constructor */ constructor() { super(); this.name = "Sum"; this.module = "Default"; this.description = "Adds together a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 18.5"; this.infoURL = "https://wikipedia.org/wiki/Summation"; this.inputType = "string"; this.outputType = "BigNumber"; this.args = [ { "name": "Delimiter", "type": "option", "value": ARITHMETIC_DELIM_OPTIONS, } ]; } /** * @param {string} input * @param {Object[]} args * @returns {BigNumber} */ run(input, args) { const val = sum(createNumArray(input, args[0])); return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); } } export default Sum; ================================================ FILE: src/core/operations/SwapCase.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Swap case operation */ class SwapCase extends Operation { /** * SwapCase constructor */ constructor() { super(); this.name = "Swap case"; this.module = "Default"; this.description = "Converts uppercase letters to lowercase ones, and lowercase ones to uppercase ones."; this.infoURL = ""; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let result = ""; for (let i = 0; i < input.length; i++) { const c = input.charAt(i); const upper = c.toUpperCase(); if (c === upper) { result += c.toLowerCase(); } else { result += upper; } } return result; } /** * Highlight Swap case * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight Swap case in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default SwapCase; ================================================ FILE: src/core/operations/SwapEndianness.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {toHex, fromHex} from "../lib/Hex.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Swap endianness operation */ class SwapEndianness extends Operation { /** * SwapEndianness constructor */ constructor() { super(); this.name = "Swap endianness"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Endianness"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Data format", "type": "option", "value": ["Hex", "Raw"] }, { "name": "Word length (bytes)", "type": "number", "value": 4 }, { "name": "Pad incomplete words", "type": "boolean", "value": true } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [dataFormat, wordLength, padIncompleteWords] = args, result = [], words = []; let i = 0, j = 0, data = []; if (wordLength <= 0) { throw new OperationError("Word length must be greater than 0"); } // Convert input to raw data based on specified data format switch (dataFormat) { case "Hex": data = fromHex(input); break; case "Raw": data = Utils.strToByteArray(input); break; default: data = input; } // Split up into words for (i = 0; i < data.length; i += wordLength) { const word = data.slice(i, i + wordLength); // Pad word if too short if (padIncompleteWords && word.length < wordLength) { for (j = word.length; j < wordLength; j++) { word.push(0); } } words.push(word); } // Swap endianness and flatten for (i = 0; i < words.length; i++) { j = words[i].length; while (j--) { result.push(words[i][j]); } } // Convert data back to specified data format switch (dataFormat) { case "Hex": return toHex(result); case "Raw": return Utils.byteArrayToUtf8(result); default: return result; } } /** * Highlight Swap endianness * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight Swap endianness in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default SwapEndianness; ================================================ FILE: src/core/operations/SymmetricDifference.mjs ================================================ /** * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Utils from "../Utils.mjs"; import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Set Symmetric Difference operation */ class SymmetricDifference extends Operation { /** * Symmetric Difference constructor */ constructor() { super(); this.name = "Symmetric Difference"; this.module = "Default"; this.description = "Calculates the symmetric difference of two sets."; this.infoURL = "https://wikipedia.org/wiki/Symmetric_difference"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Sample delimiter", type: "binaryString", value: Utils.escapeHtml("\\n\\n") }, { name: "Item delimiter", type: "binaryString", value: "," }, ]; } /** * Validate input length * * @param {Object[]} sets * @throws {Error} if not two sets */ validateSampleNumbers(sets) { if (!sets || (sets.length !== 2)) { throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); } } /** * Run the difference operation * * @param {string} input * @param {Object[]} args * @returns {string} * @throws {OperationError} */ run(input, args) { [this.sampleDelim, this.itemDelimiter] = args; const sets = input.split(this.sampleDelim); this.validateSampleNumbers(sets); return this.runSymmetricDifference(...sets.map(s => s.split(this.itemDelimiter))); } /** * Get elements in set a that are not in set b * * @param {Object[]} a * @param {Object[]} b * @returns {Object[]} */ runSetDifference(a, b) { return a.filter((item) => { return b.indexOf(item) === -1; }); } /** * Get elements of each set that aren't in the other set. * * @param {Object[]} a * @param {Object[]} b * @return {Object[]} */ runSymmetricDifference(a, b) { return this.runSetDifference(a, b) .concat(this.runSetDifference(b, a)) .join(this.itemDelimiter); } } export default SymmetricDifference; ================================================ FILE: src/core/operations/SyntaxHighlighter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import hljs from "highlight.js"; /** * Syntax highlighter operation */ class SyntaxHighlighter extends Operation { /** * SyntaxHighlighter constructor */ constructor() { super(); this.name = "Syntax highlighter"; this.module = "Code"; 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."; this.infoURL = "https://wikipedia.org/wiki/Syntax_highlighting"; this.inputType = "string"; this.outputType = "html"; this.args = [ { "name": "Language", "type": "option", "value": ["auto detect"].concat(hljs.listLanguages()) } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const language = args[0]; if (language === "auto detect") { return hljs.highlightAuto(input).value; } return hljs.highlight(language, input, true).value; } /** * Highlight Syntax highlighter * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight Syntax highlighter in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default SyntaxHighlighter; ================================================ FILE: src/core/operations/TCPIPChecksum.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * TCP/IP Checksum operation */ class TCPIPChecksum extends Operation { /** * TCPIPChecksum constructor */ constructor() { super(); this.name = "TCP/IP Checksum"; this.module = "Crypto"; this.description = "Calculates the checksum for a TCP (Transport Control Protocol) or IP (Internet Protocol) header from an input of raw bytes."; this.infoURL = "https://wikipedia.org/wiki/IPv4_header_checksum"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { input = new Uint8Array(input); let csum = 0; for (let i = 0; i < input.length; i++) { if (i % 2 === 0) { csum += (input[i] << 8); } else { csum += input[i]; } } csum = (csum >> 16) + (csum & 0xffff); return Utils.hex(0xffff - csum); } } export default TCPIPChecksum; ================================================ FILE: src/core/operations/Tail.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * Tail operation */ class Tail extends Operation { /** * Tail constructor */ constructor() { super(); this.name = "Tail"; this.module = "Default"; this.description = "Like the UNIX tail utility.
Gets the last n lines.
Optionally you can select all lines after line n by entering a negative value for n.
The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead."; this.infoURL = "https://wikipedia.org/wiki/Tail_(Unix)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": INPUT_DELIM_OPTIONS }, { "name": "Number", "type": "number", "value": 10 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { let delimiter = args[0]; const number = args[1]; delimiter = Utils.charRep(delimiter); const splitInput = input.split(delimiter); return splitInput .filter((line, lineIndex) => { lineIndex += 1; if (number < 0) { return lineIndex > -number; } else { return lineIndex > splitInput.length - number; } }) .join(delimiter); } } export default Tail; ================================================ FILE: src/core/operations/TakeBytes.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * Take bytes operation */ class TakeBytes extends Operation { /** * TakeBytes constructor */ constructor() { super(); this.name = "Take bytes"; this.module = "Default"; this.description = "Takes a slice of the specified number of bytes from the data. Negative values are allowed."; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { "name": "Start", "type": "number", "value": 0 }, { "name": "Length", "type": "number", "value": 5 }, { "name": "Apply to each line", "type": "boolean", "value": false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} * * @throws {OperationError} if invalid value */ run(input, args) { let start = args[0], length = args[1]; const applyToEachLine = args[2]; if (!applyToEachLine) { if (start < 0) { // Take from the end start = input.byteLength + start; } if (length < 0) { // Flip start point start = start + length; if (start < 0) { start = input.byteLength + start; length = start - length; } else { length = -length; } } return input.slice(start, start+length); } // Split input into lines const data = new Uint8Array(input); const lines = []; let line = [], i; for (i = 0; i < data.length; i++) { if (data[i] === 0x0a) { lines.push(line); line = []; } else { line.push(data[i]); } } lines.push(line); let output = []; let s = start, l = length; for (i = 0; i < lines.length; i++) { if (s < 0) { // Take from the end s = lines[i].length + s; } if (l < 0) { // Flip start point s = s + l; if (s < 0) { s = lines[i].length + s; l = s - l; } else { l = -l; } } output = output.concat(lines[i].slice(s, s+l)); output.push(0x0a); s = start; l = length; } return new Uint8Array(output.slice(0, output.length-1)).buffer; } } export default TakeBytes; ================================================ FILE: src/core/operations/TakeNthBytes.mjs ================================================ /** * @author Oshawk [oshawk@protonmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Take nth bytes operation */ class TakeNthBytes extends Operation { /** * TakeNthBytes constructor */ constructor() { super(); this.name = "Take nth bytes"; this.module = "Default"; this.description = "Takes every nth byte starting with a given byte."; this.infoURL = ""; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = [ { name: "Take every", type: "number", value: 4 }, { name: "Starting at", type: "number", value: 0 }, { name: "Apply to each line", type: "boolean", value: false } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const n = args[0]; const start = args[1]; const eachLine = args[2]; if (parseInt(n, 10) !== n || n <= 0) { throw new OperationError("'Take every' must be a positive integer."); } if (parseInt(start, 10) !== start || start < 0) { throw new OperationError("'Starting at' must be a positive or zero integer."); } let offset = 0; const output = []; for (let i = 0; i < input.length; i++) { if (eachLine && input[i] === 0x0a) { output.push(0x0a); offset = i + 1; } else if (i - offset >= start && (i - (start + offset)) % n === 0) { output.push(input[i]); } } return output; } } export default TakeNthBytes; ================================================ FILE: src/core/operations/Tar.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Tar operation */ class Tar extends Operation { /** * Tar constructor */ constructor() { super(); this.name = "Tar"; this.module = "Compression"; this.description = "Packs the input into a tarball.

No support for multiple files at this time."; this.infoURL = "https://wikipedia.org/wiki/Tar_(computing)"; this.inputType = "ArrayBuffer"; this.outputType = "File"; this.args = [ { "name": "Filename", "type": "string", "value": "file.txt" } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { input = new Uint8Array(input); const Tarball = function() { this.bytes = new Array(512); this.position = 0; }; Tarball.prototype.addEmptyBlock = function() { const filler = new Array(512); filler.fill(0); this.bytes = this.bytes.concat(filler); }; Tarball.prototype.writeBytes = function(bytes) { const self = this; if (this.position + bytes.length > this.bytes.length) { this.addEmptyBlock(); } Array.prototype.forEach.call(bytes, function(b, i) { if (typeof b.charCodeAt !== "undefined") { b = b.charCodeAt(); } self.bytes[self.position] = b; self.position += 1; }); }; Tarball.prototype.writeEndBlocks = function() { const numEmptyBlocks = 2; for (let i = 0; i < numEmptyBlocks; i++) { this.addEmptyBlock(); } }; const fileSize = input.length.toString(8).padStart(11, "0"); const currentUnixTimestamp = Math.floor(Date.now() / 1000); const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0"); const file = { fileName: Utils.padBytesRight(args[0], 100), fileMode: Utils.padBytesRight("0000664", 8), ownerUID: Utils.padBytesRight("0", 8), ownerGID: Utils.padBytesRight("0", 8), size: Utils.padBytesRight(fileSize, 12), lastModTime: Utils.padBytesRight(lastModTime, 12), checksum: " ", type: "0", linkedFileName: Utils.padBytesRight("", 100), USTARFormat: Utils.padBytesRight("ustar", 6), version: "00", ownerUserName: Utils.padBytesRight("", 32), ownerGroupName: Utils.padBytesRight("", 32), deviceMajor: Utils.padBytesRight("", 8), deviceMinor: Utils.padBytesRight("", 8), fileNamePrefix: Utils.padBytesRight("", 155), }; let checksum = 0; for (const key in file) { const bytes = file[key]; Array.prototype.forEach.call(bytes, function(b) { if (typeof b.charCodeAt !== "undefined") { checksum += b.charCodeAt(); } else { checksum += b; } }); } checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8); file.checksum = checksum; const tarball = new Tarball(); tarball.writeBytes(file.fileName); tarball.writeBytes(file.fileMode); tarball.writeBytes(file.ownerUID); tarball.writeBytes(file.ownerGID); tarball.writeBytes(file.size); tarball.writeBytes(file.lastModTime); tarball.writeBytes(file.checksum); tarball.writeBytes(file.type); tarball.writeBytes(file.linkedFileName); tarball.writeBytes(file.USTARFormat); tarball.writeBytes(file.version); tarball.writeBytes(file.ownerUserName); tarball.writeBytes(file.ownerGroupName); tarball.writeBytes(file.deviceMajor); tarball.writeBytes(file.deviceMinor); tarball.writeBytes(file.fileNamePrefix); tarball.writeBytes(Utils.padBytesRight("", 12)); tarball.writeBytes(input); tarball.writeEndBlocks(); return new File([new Uint8Array(tarball.bytes)], args[0]); } } export default Tar; ================================================ FILE: src/core/operations/Template.mjs ================================================ /** * @author kendallgoto [k@kgo.to] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Handlebars from "handlebars"; /** * Template operation */ class Template extends Operation { /** * Template constructor */ constructor() { super(); this.name = "Template"; this.module = "Handlebars"; this.description = "Render a template with Handlebars/Mustache substituting variables using JSON input. Templates will be rendered to plain-text only, to prevent XSS."; this.infoURL = "https://handlebarsjs.com/"; this.inputType = "JSON"; this.outputType = "string"; this.args = [ { name: "Template definition (.handlebars)", type: "text", value: "" } ]; } /** * @param {JSON} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [templateStr] = args; try { const template = Handlebars.compile(templateStr); return template(input); } catch (e) { throw new OperationError(e); } } } export default Template; ================================================ FILE: src/core/operations/TextEncodingBruteForce.mjs ================================================ /** * @author Cynser * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import cptable from "codepage"; import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; /** * Text Encoding Brute Force operation */ class TextEncodingBruteForce extends Operation { /** * TextEncodingBruteForce constructor */ constructor() { super(); this.name = "Text Encoding Brute Force"; this.module = "Encodings"; this.description = [ "Enumerates all supported text encodings for the input, allowing you to quickly spot the correct one.", "

", "Supported charsets are:", "
    ", Object.keys(CHR_ENC_CODE_PAGES).map(e => `
  • ${e}
  • `).join("\n"), "
" ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; this.inputType = "string"; this.outputType = "json"; this.presentType = "html"; this.args = [ { name: "Mode", type: "option", value: ["Encode", "Decode"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {json} */ run(input, args) { const output = {}, charsets = Object.keys(CHR_ENC_CODE_PAGES), mode = args[0]; charsets.forEach(charset => { try { if (mode === "Decode") { output[charset] = cptable.utils.decode(CHR_ENC_CODE_PAGES[charset], input); } else { output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(CHR_ENC_CODE_PAGES[charset], input)); } } catch (err) { output[charset] = "Could not decode."; } }); return output; } /** * Displays the encodings in an HTML table for web apps. * * @param {Object[]} encodings * @returns {html} */ present(encodings) { let table = ""; for (const enc in encodings) { const value = Utils.escapeHtml(Utils.escapeWhitespace(encodings[enc])); table += ``; } table += "
EncodingValue
${enc}${value}
"; return table; } } export default TextEncodingBruteForce; ================================================ FILE: src/core/operations/TextIntegerConverter.mjs ================================================ /** * @author p-leriche [philip.leriche@cantab.net] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /* ---------- helper functions ---------- */ /** * Convert text to BigInt (big-endian byte interpretation) */ function textToBigInt(text) { if (text.length === 0) return 0n; let result = 0n; for (let i = 0; i < text.length; i++) { const charCode = BigInt(text.charCodeAt(i)); if (charCode > 255n) { throw new OperationError( `Character at position ${i} exceeds Latin-1 range (0-255).\n` + "Only ASCII and Latin-1 characters are supported."); } result = (result << 8n) | charCode; } return result; } /** * Convert BigInt to text (big-endian byte interpretation) */ function bigIntToText(value) { if (value === 0n) return ""; const bytes = []; let num = value; while (num > 0n) { bytes.unshift(Number(num & 0xFFn)); num >>= 8n; } return String.fromCharCode(...bytes); } /* ---------- operation class ---------- */ /** * Text/Integer Converter operation */ class TextIntegerConverter extends Operation { /** * TextIntegerConverter constructor */ constructor() { super(); this.description = "Converts between text strings and large integers (decimal or hexadecimal).

" + "Text is interpreted as a big-endian sequence of character codes. For example:
" + "ABC is 0x414243 (hex) is 4276803 (decimal)
" + "Input format detection:
" + "Decimal: digits 0-9 only
" + "Hexadecimal: 0x... prefix
" + "Quoted or unquoted text: treated as string

" + "Character limitations:
" + "Text input may only contain ASCII and Latin-1 characters (code point < 256).
" + "Multi-byte Unicode characters will generate an error.

." ; this.infoURL = "https://wikipedia.org/wiki/Endianness"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Output format", type: "option", value: ["String", "Decimal", "Hexadecimal"] } ]; this.name = "Text-Integer Conversion"; this.module = "Default"; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const outputFormat = args[0]; const trimmed = input.trim(); let bigIntValue; if (!trimmed) { // Null input - treat as zero bigIntValue = 0; } else if (/^0x[0-9a-f]+$/i.test(trimmed) || /^[+-]?[0-9]+$/.test(trimmed)) { // Hex or decimal integer bigIntValue = BigInt(trimmed); } else if (/^["'].*["']$/.test(trimmed)) { // Quoted string: Remove quotes and convert text to BigInt const text = trimmed.slice(1, -1); bigIntValue = textToBigInt(text); } else { // Assume it's unquoted text bigIntValue = textToBigInt(trimmed); } // Convert to output format if (outputFormat === "String") { return bigIntToText(bigIntValue); } else if (outputFormat === "Decimal") { return bigIntValue.toString(); } else { // Hexadecimal return "0x" + bigIntValue.toString(16); } } } export default TextIntegerConverter; ================================================ FILE: src/core/operations/ToBCD.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD.mjs"; import BigNumber from "bignumber.js"; /** * To BCD operation */ class ToBCD extends Operation { /** * ToBCD constructor */ constructor() { super(); this.name = "To BCD"; this.module = "Default"; 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"; this.infoURL = "https://wikipedia.org/wiki/Binary-coded_decimal"; this.inputType = "BigNumber"; this.outputType = "string"; this.args = [ { "name": "Scheme", "type": "option", "value": ENCODING_SCHEME }, { "name": "Packed", "type": "boolean", "value": true }, { "name": "Signed", "type": "boolean", "value": false }, { "name": "Output format", "type": "option", "value": FORMAT } ]; } /** * @param {BigNumber} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (input.isNaN()) throw new OperationError("Invalid input"); if (!input.integerValue(BigNumber.ROUND_DOWN).isEqualTo(input)) throw new OperationError("Fractional values are not supported by BCD"); const encoding = ENCODING_LOOKUP[args[0]], packed = args[1], signed = args[2], outputFormat = args[3]; // Split input number up into separate digits const digits = input.toFixed().split(""); if (digits[0] === "-" || digits[0] === "+") { digits.shift(); } let nibbles = []; digits.forEach(d => { const n = parseInt(d, 10); nibbles.push(encoding[n]); }); if (signed) { if (packed && digits.length % 2 === 0) { // If there are an even number of digits, we add a leading 0 so // that the sign nibble doesn't sit in its own byte, leading to // ambiguity around whether the number ends with a 0 or not. nibbles.unshift(encoding[0]); } nibbles.push(input > 0 ? 12 : 13); // 12 ("C") for + (credit) // 13 ("D") for - (debit) } let bytes = []; if (packed) { let encoded = 0, little = false; nibbles.forEach(n => { encoded ^= little ? n : (n << 4); if (little) { bytes.push(encoded); encoded = 0; } little = !little; }); if (little) bytes.push(encoded); } else { bytes = nibbles; // Add null high nibbles nibbles = nibbles.map(n => { return [0, n]; }).reduce((a, b) => { return a.concat(b); }); } // Output switch (outputFormat) { case "Nibbles": return nibbles.map(n => { return n.toString(2).padStart(4, "0"); }).join(" "); case "Bytes": return bytes.map(b => { return b.toString(2).padStart(8, "0"); }).join(" "); case "Raw": default: return Utils.byteArrayToChars(bytes); } } } export default ToBCD; ================================================ FILE: src/core/operations/ToBase.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * To Base operation */ class ToBase extends Operation { /** * ToBase constructor */ constructor() { super(); this.name = "To Base"; this.module = "Default"; this.description = "Converts a decimal number to a given numerical base."; this.infoURL = "https://wikipedia.org/wiki/Radix"; this.inputType = "BigNumber"; this.outputType = "string"; this.args = [ { "name": "Radix", "type": "number", "value": 36 } ]; } /** * @param {BigNumber} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!input) { throw new OperationError("Error: Input must be a number"); } const radix = args[0]; if (radix < 2 || radix > 36) { throw new OperationError("Error: Radix argument must be between 2 and 36"); } return input.toString(radix); } } export default ToBase; ================================================ FILE: src/core/operations/ToBase32.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {ALPHABET_OPTIONS} from "../lib/Base32.mjs"; /** * To Base32 operation */ class ToBase32 extends Operation { /** * ToBase32 constructor */ constructor() { super(); this.name = "To Base32"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/Base32"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Alphabet", type: "editableOption", value: ALPHABET_OPTIONS } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!input) return ""; input = new Uint8Array(input); const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; let output = "", chr1, chr2, chr3, chr4, chr5, enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, i = 0; while (i < input.length) { chr1 = input[i++]; chr2 = input[i++]; chr3 = input[i++]; chr4 = input[i++]; chr5 = input[i++]; enc1 = chr1 >> 3; enc2 = ((chr1 & 7) << 2) | (chr2 >> 6); enc3 = (chr2 >> 1) & 31; enc4 = ((chr2 & 1) << 4) | (chr3 >> 4); enc5 = ((chr3 & 15) << 1) | (chr4 >> 7); enc6 = (chr4 >> 2) & 31; enc7 = ((chr4 & 3) << 3) | (chr5 >> 5); enc8 = chr5 & 31; if (isNaN(chr2)) { enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32; } else if (isNaN(chr3)) { enc5 = enc6 = enc7 = enc8 = 32; } else if (isNaN(chr4)) { enc6 = enc7 = enc8 = 32; } else if (isNaN(chr5)) { enc8 = 32; } output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) + alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) + alphabet.charAt(enc7) + alphabet.charAt(enc8); } return output; } } export default ToBase32; ================================================ FILE: src/core/operations/ToBase45.mjs ================================================ /** * @author Thomas Weißschuh [thomas@t-8ch.de] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import {ALPHABET, highlightToBase45, highlightFromBase45} from "../lib/Base45.mjs"; import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * To Base45 operation */ class ToBase45 extends Operation { /** * ToBase45 constructor */ constructor() { super(); this.name = "To Base45"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Alphabet", type: "string", value: ALPHABET } ]; this.highlight = highlightToBase45; this.highlightReverse = highlightFromBase45; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!input) return ""; input = new Uint8Array(input); const alphabet = Utils.expandAlphRange(args[0]); const res = []; for (const pair of Utils.chunked(input, 2)) { let b = 0; for (const e of pair) { b *= 256; b += e; } let chars = 0; do { res.push(alphabet[b % 45]); chars++; b = Math.floor(b / 45); } while (b > 0); if (chars < 2) { res.push("0"); chars++; } if (pair.length > 1 && chars < 3) { res.push("0"); } } return res.join(""); } } export default ToBase45; ================================================ FILE: src/core/operations/ToBase58.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import {ALPHABET_OPTIONS} from "../lib/Base58.mjs"; /** * To Base58 operation */ class ToBase58 extends Operation { /** * ToBase58 constructor */ constructor() { super(); this.name = "To Base58"; this.module = "Default"; 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.

This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).

e.g. hello world becomes StV1DL6CwTryKyV

Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc)."; this.infoURL = "https://wikipedia.org/wiki/Base58"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Alphabet", "type": "editableOption", "value": ALPHABET_OPTIONS } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { input = new Uint8Array(input); let alphabet = args[0] || ALPHABET_OPTIONS[0].value, result = []; alphabet = Utils.expandAlphRange(alphabet).join(""); if (alphabet.length !== 58 || [].unique.call(alphabet).length !== 58) { throw new OperationError("Error: alphabet must be of length 58"); } if (input.length === 0) return ""; let zeroPrefix = 0; for (let i = 0; i < input.length && input[i] === 0; i++) { zeroPrefix++; } input.forEach(function(b) { let carry = b; for (let i = 0; i < result.length; i++) { carry += result[i] << 8; result[i] = carry % 58; carry = (carry / 58) | 0; } while (carry > 0) { result.push(carry % 58); carry = (carry / 58) | 0; } }); result = result.map(function(b) { return alphabet[b]; }).reverse().join(""); while (zeroPrefix--) { result = alphabet[0] + result; } return result; } } export default ToBase58; ================================================ FILE: src/core/operations/ToBase62.mjs ================================================ /** * @author tcode2k16 [tcode2k16@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import BigNumber from "bignumber.js"; import Utils from "../Utils.mjs"; import {toHexFast} from "../lib/Hex.mjs"; /** * To Base62 operation */ class ToBase62 extends Operation { /** * ToBase62 constructor */ constructor() { super(); this.name = "To Base62"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Alphabet", type: "string", value: "0-9A-Za-z" } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { input = new Uint8Array(input); if (input.length < 1) return ""; const alphabet = Utils.expandAlphRange(args[0]).join(""); const BN62 = BigNumber.clone({ ALPHABET: alphabet }); input = toHexFast(input).toUpperCase(); // Read number in as hex using normal alphabet const normalized = new BigNumber(input, 16); // Copy to BigNumber clone that uses the specified Base62 alphabet const number = new BN62(normalized); return number.toString(62); } } export default ToBase62; ================================================ FILE: src/core/operations/ToBase64.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {toBase64, ALPHABET_OPTIONS} from "../lib/Base64.mjs"; /** * To Base64 operation */ class ToBase64 extends Operation { /** * ToBase64 constructor */ constructor() { super(); this.name = "To Base64"; this.module = "Default"; this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.

This operation encodes raw data into an ASCII Base64 string.

e.g. hello becomes aGVsbG8="; this.infoURL = "https://wikipedia.org/wiki/Base64"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Alphabet", type: "editableOption", value: ALPHABET_OPTIONS } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const alphabet = args[0]; return toBase64(input, alphabet); } /** * Highlight to Base64 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { pos[0].start = Math.floor(pos[0].start / 3 * 4); pos[0].end = Math.ceil(pos[0].end / 3 * 4); return pos; } /** * Highlight from Base64 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { pos[0].start = Math.ceil(pos[0].start / 4 * 3); pos[0].end = Math.floor(pos[0].end / 4 * 3); return pos; } } export default ToBase64; ================================================ FILE: src/core/operations/ToBase85.mjs ================================================ /** * @author PenguinGeorge [george@penguingeorge.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85.mjs"; /** * To Base85 operation */ class ToBase85 extends Operation { /** * To Base85 constructor */ constructor() { super(); this.name = "To Base85"; this.module = "Default"; this.description = "Base85 (also called Ascii85) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.

This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).

e.g. hello world becomes BOu!rD]j7BEbo7

Base85 is commonly used in Adobe's PostScript and PDF file formats.

Options
Alphabet
  • Standard - The standard alphabet, referred to as Ascii85
  • Z85 (ZeroMQ) - A string-safe variant of Base85, which avoids quote marks and backslash characters
  • IPv6 - A variant of Base85 suitable for encoding IPv6 addresses (RFC 1924)
Include delimiter
Adds a '<~' and '~>' delimiter to the start and end of the data. This is standard for Adobe's implementation of Base85."; this.infoURL = "https://wikipedia.org/wiki/Ascii85"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Alphabet", type: "editableOption", value: ALPHABET_OPTIONS }, { name: "Include delimiter", type: "boolean", value: false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { input = new Uint8Array(input); const alphabet = Utils.expandAlphRange(args[0]).join(""), encoding = alphabetName(alphabet), includeDelim = args[1]; let result = ""; if (alphabet.length !== 85 || [].unique.call(alphabet).length !== 85) { throw new OperationError("Error: Alphabet must be of length 85"); } if (input.length === 0) return ""; let block; for (let i = 0; i < input.length; i += 4) { block = ( ((input[i]) << 24) + ((input[i + 1] || 0) << 16) + ((input[i + 2] || 0) << 8) + ((input[i + 3] || 0)) ) >>> 0; if (encoding !== "Standard" || block > 0) { let digits = []; for (let j = 0; j < 5; j++) { digits.push(block % 85); block = Math.floor(block / 85); } digits = digits.reverse(); if (input.length < i + 4) { digits.splice(input.length - (i + 4), 4); } result += digits.map(digit => alphabet[digit]).join(""); } else { result += (encoding === "Standard") ? "z" : null; } } return includeDelim ? `<~${result}~>` : result; } } export default ToBase85; ================================================ FILE: src/core/operations/ToBase92.mjs ================================================ /** * @author sg5506844 [sg5506844@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import { base92Chr } from "../lib/Base92.mjs"; import Operation from "../Operation.mjs"; /** * To Base92 operation */ class ToBase92 extends Operation { /** * ToBase92 constructor */ constructor() { super(); this.name = "To Base92"; this.module = "Default"; 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."; this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; this.inputType = "string"; this.outputType = "byteArray"; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const res = []; let bitString = ""; while (input.length > 0) { while (bitString.length < 13 && input.length > 0) { bitString += input[0].charCodeAt(0).toString(2).padStart(8, "0"); input = input.slice(1); } if (bitString.length < 13) break; const i = parseInt(bitString.slice(0, 13), 2); res.push(base92Chr(Math.floor(i / 91))); res.push(base92Chr(i % 91)); bitString = bitString.slice(13); } if (bitString.length > 0) { if (bitString.length < 7) { bitString = bitString.padEnd(6, "0"); res.push(base92Chr(parseInt(bitString, 2))); } else { bitString = bitString.padEnd(13, "0"); const i = parseInt(bitString.slice(0, 13), 2); res.push(base92Chr(Math.floor(i / 91))); res.push(base92Chr(i % 91)); } } return res; } } export default ToBase92; ================================================ FILE: src/core/operations/ToBech32.mjs ================================================ /** * @author Medjedtxm * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { encode } from "../lib/Bech32.mjs"; import { fromHex } from "../lib/Hex.mjs"; /** * To Bech32 operation */ class ToBech32 extends Operation { /** * ToBech32 constructor */ constructor() { super(); this.name = "To Bech32"; this.module = "Default"; 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.

Bech32m (BIP-0350) is an updated version that fixes a weakness in the original Bech32 checksum and is used for Bitcoin Taproot addresses.

The Human-Readable Part (HRP) identifies the network or purpose (e.g., 'bc' for Bitcoin mainnet, 'tb' for testnet, 'age' for AGE encryption keys).

Maximum output length is 90 characters as per specification."; this.infoURL = "https://wikipedia.org/wiki/Bech32"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Human-Readable Part (HRP)", "type": "string", "value": "bc" }, { "name": "Encoding", "type": "option", "value": ["Bech32", "Bech32m"] }, { "name": "Input Format", "type": "option", "value": ["Raw bytes", "Hex"] }, { "name": "Mode", "type": "option", "value": ["Generic", "Bitcoin SegWit"] }, { "name": "Witness Version", "type": "number", "value": 0, "hint": "SegWit witness version (0-16). Only used in Bitcoin SegWit mode." } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const hrp = args[0]; const encoding = args[1]; const inputFormat = args[2]; const mode = args[3]; const witnessVersion = args[4]; let inputArray; if (inputFormat === "Hex") { // Convert hex string to bytes const hexStr = new TextDecoder().decode(new Uint8Array(input)).replace(/\s/g, ""); inputArray = fromHex(hexStr); } else { inputArray = new Uint8Array(input); } if (mode === "Bitcoin SegWit") { // Prepend witness version to the input data const withVersion = new Uint8Array(inputArray.length + 1); withVersion[0] = witnessVersion; withVersion.set(inputArray, 1); return encode(hrp, withVersion, encoding, true); } return encode(hrp, inputArray, encoding, false); } } export default ToBech32; ================================================ FILE: src/core/operations/ToBinary.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {BIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; import {toBinary} from "../lib/Binary.mjs"; /** * To Binary operation */ class ToBinary extends Operation { /** * ToBinary constructor */ constructor() { super(); this.name = "To Binary"; this.module = "Default"; this.description = "Displays the input data as a binary string.

e.g. Hi becomes 01001000 01101001"; this.infoURL = "https://wikipedia.org/wiki/Binary_code"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": BIN_DELIM_OPTIONS }, { "name": "Byte Length", "type": "number", "value": 8 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { input = new Uint8Array(input); const padding = args[1] ? args[1] : 8; return toBinary(input, args[0], padding); } /** * Highlight To Binary * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { const delim = Utils.charRep(args[0] || "Space"); pos[0].start = pos[0].start * (8 + delim.length); pos[0].end = pos[0].end * (8 + delim.length) - delim.length; return pos; } /** * Highlight To Binary in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { const delim = Utils.charRep(args[0] || "Space"); pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length)); pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length)); return pos; } } export default ToBinary; ================================================ FILE: src/core/operations/ToBraille.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {BRAILLE_LOOKUP} from "../lib/Braille.mjs"; /** * To Braille operation */ class ToBraille extends Operation { /** * ToBraille constructor */ constructor() { super(); this.name = "To Braille"; this.module = "Default"; this.description = "Converts text to six-dot braille symbols."; this.infoURL = "https://wikipedia.org/wiki/Braille"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return input.split("").map(c => { const idx = BRAILLE_LOOKUP.ascii.indexOf(c.toUpperCase()); return idx < 0 ? c : BRAILLE_LOOKUP.dot6[idx]; }).join(""); } /** * Highlight To Braille * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight To Braille in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default ToBraille; ================================================ FILE: src/core/operations/ToCamelCase.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import camelCase from "lodash/camelCase.js"; import Operation from "../Operation.mjs"; import { replaceVariableNames } from "../lib/Code.mjs"; /** * To Camel case operation */ class ToCamelCase extends Operation { /** * ToCamelCase constructor */ constructor() { super(); this.name = "To Camel case"; this.module = "Code"; this.description = "Converts the input string to camel case.\n

\nCamel case is all lower case except letters after word boundaries which are uppercase.\n

\ne.g. thisIsCamelCase\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; this.infoURL = "https://wikipedia.org/wiki/Camel_case"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Attempt to be context aware", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const smart = args[0]; if (smart) { return replaceVariableNames(input, camelCase); } else { return camelCase(input); } } } export default ToCamelCase; ================================================ FILE: src/core/operations/ToCaseInsensitiveRegex.mjs ================================================ /** * @author masq [github.cyberchef@masq.cc] * @author n1073645 * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * To Case Insensitive Regex operation */ class ToCaseInsensitiveRegex extends Operation { /** * ToCaseInsensitiveRegex constructor */ constructor() { super(); this.name = "To Case Insensitive Regex"; this.module = "Default"; this.description = "Converts a case-sensitive regex string into a case-insensitive regex string in case the i flag is unavailable to you.

e.g. Mozilla/[0-9].[0-9] .* becomes [mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .*"; this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { /** * Simulates look behind behaviour since javascript doesn't support it. * * @param {string} input * @returns {string} */ function preProcess(input) { let result = ""; for (let i = 0; i < input.length; i++) { const temp = input.charAt(i); if (temp.match(/[a-zA-Z]/g) && (input.charAt(i-1) !== "-") && (input.charAt(i+1) !== "-")) result += "[" + temp.toLowerCase() + temp.toUpperCase() + "]"; else result += temp; } return result; } try { RegExp(input); } catch (error) { throw new OperationError("Invalid Regular Expression (Please note this version of node does not support look behinds)."); } // Example: [test] -> [[tT][eE][sS][tT]] return preProcess(input) // Example: [A-Z] -> [A-Za-z] .replace(/([A-Z]-[A-Z]|[a-z]-[a-z])/g, m => `${m[0].toUpperCase()}-${m[2].toUpperCase()}${m[0].toLowerCase()}-${m[2].toLowerCase()}`) // Example: [H-d] -> [A-DH-dh-z] .replace(/[A-Z]-[a-z]/g, m => `A-${m[2].toUpperCase()}${m}${m[0].toLowerCase()}-z`) // Example: [!-D] -> [!-Da-d] .replace(/\\?[ -@]-[A-Z]/g, m => `${m}a-${m[2].toLowerCase()}`) // Example: [%-^] -> [%-^a-z] .replace(/\\?[ -@]-\\?[[-`]/g, m => `${m}a-z`) // Example: [K-`] -> [K-`k-z] .replace(/[A-Z]-\\?[[-`]/g, m => `${m}${m[0].toLowerCase()}-z`) // Example: [[-}] -> [[-}A-Z] .replace(/\\?[[-`]-\\?[{-~]/g, m => `${m}A-Z`) // Example: [b-}] -> [b-}B-Z] .replace(/[a-z]-\\?[{-~]/g, m => `${m}${m[0].toUpperCase()}-Z`) // Example: [<-j] -> [<-z] .replace(/\\?[ -@]-[a-z]/g, m => `${m[0]}-z`) // Example: [^-j] -> [A-J^-j] .replace(/\\?[[-`]-[a-z]/g, m => `A-${m[2].toUpperCase()}${m}`); } } export default ToCaseInsensitiveRegex; ================================================ FILE: src/core/operations/ToCharcode.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { DELIM_OPTIONS } from "../lib/Delim.mjs"; import OperationError from "../errors/OperationError.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * To Charcode operation */ class ToCharcode extends Operation { /** * ToCharcode constructor */ constructor() { super(); this.name = "To Charcode"; this.module = "Default"; this.description = "Converts text to its unicode character code equivalent.

e.g. Γειά σου becomes 0393 03b5 03b9 03ac 20 03c3 03bf 03c5"; this.infoURL = "https://wikipedia.org/wiki/Plane_(Unicode)"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS }, { "name": "Base", "type": "number", "value": 16 } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} * * @throws {OperationError} if base argument out of range */ run(input, args) { const delim = Utils.charRep(args[0] || "Space"), base = args[1]; let output = "", padding, ordinal; if (base < 2 || base > 36) { throw new OperationError("Error: Base argument must be between 2 and 36"); } const charcode = Utils.strToCharcode(input); for (let i = 0; i < charcode.length; i++) { ordinal = charcode[i]; if (base === 16) { if (ordinal < 256) padding = 2; else if (ordinal < 65536) padding = 4; else if (ordinal < 16777216) padding = 6; else if (ordinal < 4294967296) padding = 8; else padding = 2; if (padding > 2 && isWorkerEnvironment()) self.setOption("attemptHighlight", false); output += Utils.hex(ordinal, padding) + delim; } else { if (isWorkerEnvironment()) self.setOption("attemptHighlight", false); output += ordinal.toString(base) + delim; } } return output.slice(0, -delim.length); } } export default ToCharcode; ================================================ FILE: src/core/operations/ToDecimal.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * To Decimal operation */ class ToDecimal extends Operation { /** * ToDecimal constructor */ constructor() { super(); this.name = "To Decimal"; this.module = "Default"; this.description = "Converts the input data to an ordinal integer array.

e.g. Hello becomes 72 101 108 108 111"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS }, { "name": "Support signed values", "type": "boolean", "value": false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = Utils.charRep(args[0]), signed = args[1]; if (signed) { input = new Int8Array(input); } else { input = new Uint8Array(input); } return input.join(delim); } } export default ToDecimal; ================================================ FILE: src/core/operations/ToFloat.mjs ================================================ /** * @author tcode2k16 [tcode2k16@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import ieee754 from "ieee754"; import {DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * To Float operation */ class ToFloat extends Operation { /** * ToFloat constructor */ constructor() { super(); this.name = "To Float"; this.module = "Default"; this.description = "Convert to IEEE754 Floating Point Numbers"; this.infoURL = "https://wikipedia.org/wiki/IEEE_754"; this.inputType = "byteArray"; this.outputType = "string"; this.args = [ { "name": "Endianness", "type": "option", "value": [ "Big Endian", "Little Endian" ] }, { "name": "Size", "type": "option", "value": [ "Float (4 bytes)", "Double (8 bytes)" ] }, { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [endianness, size, delimiterName] = args; const delim = Utils.charRep(delimiterName || "Space"); const byteSize = size === "Double (8 bytes)" ? 8 : 4; const isLE = endianness === "Little Endian"; const mLen = byteSize === 4 ? 23 : 52; if (input.length % byteSize !== 0) { throw new OperationError(`Input is not a multiple of ${byteSize}`); } const output = []; for (let i = 0; i < input.length; i+=byteSize) { output.push(ieee754.read(input, i, isLE, mLen, byteSize)); } return output.join(delim); } } export default ToFloat; ================================================ FILE: src/core/operations/ToHTMLEntity.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * To HTML Entity operation */ class ToHTMLEntity extends Operation { /** * ToHTMLEntity constructor */ constructor() { super(); this.name = "To HTML Entity"; this.module = "Encodings"; this.description = "Converts characters to HTML entities

e.g. & becomes &amp;"; this.infoURL = "https://wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Convert all characters", "type": "boolean", "value": false }, { "name": "Convert to", "type": "option", "value": ["Named entities", "Numeric entities", "Hex entities"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const convertAll = args[0], numeric = args[1] === "Numeric entities", hexa = args[1] === "Hex entities"; const charcodes = Utils.strToCharcode(input); let output = ""; for (let i = 0; i < charcodes.length; i++) { if (convertAll && numeric) { output += "&#" + charcodes[i] + ";"; } else if (convertAll && hexa) { output += "&#x" + Utils.hex(charcodes[i]) + ";"; } else if (convertAll) { output += byteToEntity[charcodes[i]] || "&#" + charcodes[i] + ";"; } else if (numeric) { if (charcodes[i] > 255 || charcodes[i] in byteToEntity) { output += "&#" + charcodes[i] + ";"; } else { output += Utils.chr(charcodes[i]); } } else if (hexa) { if (charcodes[i] > 255 || charcodes[i] in byteToEntity) { output += "&#x" + Utils.hex(charcodes[i]) + ";"; } else { output += Utils.chr(charcodes[i]); } } else { output += byteToEntity[charcodes[i]] || ( charcodes[i] > 255 ? "&#" + charcodes[i] + ";" : Utils.chr(charcodes[i]) ); } } return output; } } /** * Lookup table to translate byte values to their HTML entity codes. */ const byteToEntity = { 9: " ", 10: " ", 33: "!", 34: """, 35: "#", 36: "$", 37: "%", 38: "&", 39: "'", 40: "(", 41: ")", 42: "*", 43: "+", 44: ",", 46: ".", 47: "/", 58: ":", 59: ";", 60: "<", 61: "=", 62: ">", 63: "?", 64: "@", 91: "[", 92: "\", 93: "]", 94: "^", 95: "_", 96: "`", 123: "{", 124: "|", 125: "}", 160: " ", 161: "¡", 162: "¢", 163: "£", 164: "¤", 165: "¥", 166: "¦", 167: "§", 168: "¨", 169: "©", 170: "ª", 171: "«", 172: "¬", 173: "­", 174: "®", 175: "¯", 176: "°", 177: "±", 178: "²", 179: "³", 180: "´", 181: "µ", 182: "¶", 183: "·", 184: "¸", 185: "¹", 186: "º", 187: "»", 188: "¼", 189: "½", 190: "¾", 191: "¿", 192: "À", 193: "Á", 194: "Â", 195: "Ã", 196: "Ä", 197: "Å", 198: "Æ", 199: "Ç", 200: "È", 201: "É", 202: "Ê", 203: "Ë", 204: "Ì", 205: "Í", 206: "Î", 207: "Ï", 208: "Ð", 209: "Ñ", 210: "Ò", 211: "Ó", 212: "Ô", 213: "Õ", 214: "Ö", 215: "×", 216: "Ø", 217: "Ù", 218: "Ú", 219: "Û", 220: "Ü", 221: "Ý", 222: "Þ", 223: "ß", 224: "à", 225: "á", 226: "â", 227: "ã", 228: "ä", 229: "å", 230: "æ", 231: "ç", 232: "è", 233: "é", 234: "ê", 235: "ë", 236: "ì", 237: "í", 238: "î", 239: "ï", 240: "ð", 241: "ñ", 242: "ò", 243: "ó", 244: "ô", 245: "õ", 246: "ö", 247: "÷", 248: "ø", 249: "ù", 250: "ú", 251: "û", 252: "ü", 253: "ý", 254: "þ", 255: "ÿ", 256: "Ā", 257: "ā", 258: "Ă", 259: "ă", 260: "Ą", 261: "ą", 262: "Ć", 263: "ć", 264: "Ĉ", 265: "ĉ", 266: "Ċ", 267: "ċ", 268: "Č", 269: "č", 270: "Ď", 271: "ď", 272: "Đ", 273: "đ", 274: "Ē", 275: "ē", 278: "Ė", 279: "ė", 280: "Ę", 281: "ę", 282: "Ě", 283: "ě", 284: "Ĝ", 285: "ĝ", 286: "Ğ", 287: "ğ", 288: "Ġ", 289: "ġ", 290: "Ģ", 292: "Ĥ", 293: "ĥ", 294: "Ħ", 295: "ħ", 296: "Ĩ", 297: "ĩ", 298: "Ī", 299: "ī", 302: "Į", 303: "į", 304: "İ", 305: "ı", 306: "IJ", 307: "ij", 308: "Ĵ", 309: "ĵ", 310: "Ķ", 311: "ķ", 312: "ĸ", 313: "Ĺ", 314: "ĺ", 315: "Ļ", 316: "ļ", 317: "Ľ", 318: "ľ", 319: "Ŀ", 320: "ŀ", 321: "Ł", 322: "ł", 323: "Ń", 324: "ń", 325: "Ņ", 326: "ņ", 327: "Ň", 328: "ň", 329: "ʼn", 330: "Ŋ", 331: "ŋ", 332: "Ō", 333: "ō", 336: "Ő", 337: "ő", 338: "Œ", 339: "œ", 340: "Ŕ", 341: "ŕ", 342: "Ŗ", 343: "ŗ", 344: "Ř", 345: "ř", 346: "Ś", 347: "ś", 348: "Ŝ", 349: "ŝ", 350: "Ş", 351: "ş", 352: "Š", 353: "š", 354: "Ţ", 355: "ţ", 356: "Ť", 357: "ť", 358: "Ŧ", 359: "ŧ", 360: "Ũ", 361: "ũ", 362: "Ū", 363: "ū", 364: "Ŭ", 365: "ŭ", 366: "Ů", 367: "ů", 368: "Ű", 369: "ű", 370: "Ų", 371: "ų", 372: "Ŵ", 373: "ŵ", 374: "Ŷ", 375: "ŷ", 376: "Ÿ", 377: "Ź", 378: "ź", 379: "Ż", 380: "ż", 381: "Ž", 382: "ž", 402: "ƒ", 437: "Ƶ", 501: "ǵ", 567: "ȷ", 710: "ˆ", 711: "ˇ", 728: "˘", 729: "˙", 730: "˚", 731: "˛", 732: "˜", 785: "̑", 818: "_", 913: "Α", 914: "Β", 915: "Γ", 916: "Δ", 917: "Ε", 918: "Ζ", 919: "Η", 920: "Θ", 921: "Ι", 922: "Κ", 923: "Λ", 924: "Μ", 925: "Ν", 926: "Ξ", 927: "Ο", 928: "Π", 929: "Ρ", 931: "Σ", 932: "Τ", 933: "Υ", 934: "Φ", 935: "Χ", 936: "Ψ", 937: "Ω", 945: "α", 946: "β", 947: "γ", 948: "δ", 949: "ε", 950: "ζ", 951: "η", 952: "θ", 953: "ι", 954: "κ", 955: "λ", 956: "μ", 957: "ν", 958: "ξ", 959: "ο", 960: "π", 961: "ρ", 962: "ς", 963: "σ", 964: "τ", 965: "υ", 966: "φ", 967: "χ", 968: "ψ", 969: "ω", 977: "ϑ", 978: "ϒ", 981: "ϕ", 982: "ϖ", 988: "Ϝ", 989: "ϝ", 1008: "ϰ", 1009: "ϱ", 1013: "ε,", 1014: "϶", 1025: "Ё", 1026: "Ђ", 1027: "Ѓ", 1028: "Є", 1029: "Ѕ", 1030: "І", 1031: "Ї", 1032: "Ј", 1033: "Љ", 1034: "Њ", 1035: "Ћ", 1036: "Ќ", 1038: "Ў", 1039: "Џ", 1040: "А", 1041: "Б", 1042: "В", 1043: "Г", 1044: "Д", 1045: "Е", 1046: "Ж", 1047: "З", 1048: "И", 1049: "Й", 1050: "К", 1051: "Л", 1052: "М", 1053: "Н", 1054: "О", 1055: "П", 1056: "Р", 1057: "С", 1058: "Т", 1059: "У", 1060: "Ф", 1061: "Х", 1062: "Ц", 1063: "Ч", 1064: "Ш", 1065: "Щ", 1066: "Ъ", 1067: "Ы", 1068: "Ь", 1069: "Э", 1070: "Ю", 1071: "Я", 1072: "а", 1073: "б", 1074: "в", 1075: "г", 1076: "д", 1077: "е", 1078: "ж", 1079: "з", 1080: "и", 1081: "й", 1082: "к", 1083: "л", 1084: "м", 1085: "н", 1086: "о", 1087: "п", 1088: "р", 1089: "с", 1090: "т", 1091: "у", 1092: "ф", 1093: "х", 1094: "ц", 1095: "ч", 1096: "ш", 1097: "щ", 1098: "ъ", 1099: "ы", 1100: "ь", 1101: "э", 1102: "ю", 1103: "я", 1105: "ё", 1106: "ђ", 1107: "ѓ", 1108: "є", 1109: "ѕ", 1110: "і", 1111: "ї", 1112: "ј", 1113: "љ", 1114: "њ", 1115: "ћ", 1116: "ќ", 1118: "ў", 1119: "џ", 8194: " ", 8195: " ", 8196: " ", 8197: " ", 8199: " ", 8200: " ", 8201: " ", 8202: " ", 8203: "​", 8204: "‌", 8205: "‍", 8206: "‎", 8207: "‏", 8208: "‐", 8211: "–", 8212: "—", 8213: "―", 8214: "‖", 8216: "‘", 8217: "’", 8218: "‚", 8220: "“", 8221: "”", 8222: "„", 8224: "†", 8225: "‡", 8226: "•", 8229: "‥", 8230: "…", 8240: "‰", 8241: "‱", 8242: "′", 8243: "″", 8244: "‴", 8245: "‵", 8249: "‹", 8250: "›", 8254: "‾", 8257: "⁁", 8259: "⁃", 8260: "⁄", 8271: "⁏", 8279: "⁗", 8287: " ", 8288: "⁠", 8289: "⁡", 8290: "⁢", 8291: "⁣", 8364: "€", 8411: "⃛", 8412: "⃜", 8450: "ℂ", 8453: "℅", 8458: "ℊ", 8459: "ℋ", 8460: "ℌ", 8461: "ℍ", 8462: "ℎ", 8463: "ℏ", 8464: "ℐ", 8465: "ℑ", 8466: "ℒ", 8467: "ℓ", 8469: "ℕ", 8470: "№", 8471: "℗", 8472: "℘", 8473: "ℙ", 8474: "ℚ", 8475: "ℛ", 8476: "ℜ", 8477: "ℝ", 8478: "℞", 8482: "™", 8484: "ℤ", 8486: "Ω", 8487: "℧", 8488: "ℨ", 8489: "℩", 8491: "Å", 8492: "ℬ", 8493: "ℭ", 8495: "ℯ", 8496: "ℰ", 8497: "ℱ", 8499: "ℳ", 8500: "ℴ", 8501: "ℵ", 8502: "ℶ", 8503: "ℷ", 8504: "ℸ", 8517: "ⅅ", 8518: "ⅆ", 8519: "ⅇ", 8520: "ⅈ", 8531: "⅓", 8532: "⅔", 8533: "⅕", 8534: "⅖", 8535: "⅗", 8536: "⅘", 8537: "⅙", 8538: "⅚", 8539: "⅛", 8540: "⅜", 8541: "⅝", 8542: "⅞", 8592: "←", 8593: "↑", 8594: "→", 8595: "↓", 8596: "↔", 8597: "↕", 8598: "↖", 8599: "↗", 8600: "↘", 8601: "↙", 8602: "↚", 8603: "↛", 8605: "↝", 8606: "↞", 8607: "↟", 8608: "↠", 8609: "↡", 8610: "↢", 8611: "↣", 8612: "↤", 8613: "↥", 8614: "↦", 8615: "↧", 8617: "↩", 8618: "↪", 8619: "↫", 8620: "↬", 8621: "↭", 8622: "↮", 8624: "↰", 8625: "↱", 8626: "↲", 8627: "↳", 8629: "↵", 8630: "↶", 8631: "↷", 8634: "↺", 8635: "↻", 8636: "↼", 8637: "↽", 8638: "↾", 8639: "↿", 8640: "⇀", 8641: "⇁", 8642: "⇂", 8643: "⇃", 8644: "⇄", 8645: "⇅", 8646: "⇆", 8647: "⇇", 8648: "⇈", 8649: "⇉", 8650: "⇊", 8651: "⇋", 8652: "⇌;", 8653: "⇍", 8654: "⇎", 8655: "⇏", 8656: "⇐", 8657: "⇑", 8658: "⇒", 8659: "⇓", 8660: "⇔", 8661: "⇕", 8662: "⇖", 8663: "⇗", 8664: "⇘", 8665: "⇙", 8666: "⇚", 8667: "⇛", 8669: "⇝", 8676: "⇤", 8677: "⇥", 8693: "⇵", 8701: "⇽", 8702: "⇾", 8703: "⇿", 8704: "∀", 8705: "∁", 8706: "∂", 8707: "∃", 8708: "∄", 8709: "∅", 8711: "∇", 8712: "∈", 8713: "∉", 8715: "∋", 8716: "∌", 8719: "∏", 8720: "∐", 8721: "∑", 8722: "−", 8723: "∓", 8724: "∔", 8726: "∖", 8727: "∗", 8728: "∘", 8730: "√", 8733: "∝", 8734: "∞", 8735: "∟", 8736: "∠", 8737: "∡", 8738: "∢", 8739: "∣", 8740: "∤", 8741: "∥", 8742: "∦", 8743: "∧", 8744: "∨", 8745: "∩", 8746: "∪", 8747: "∫", 8748: "∬", 8749: "∭", 8750: "∮", 8751: "∯", 8752: "∰", 8753: "∱", 8754: "∲", 8755: "∳", 8756: "∴", 8757: "∵", 8758: "∶", 8759: "∷", 8760: "∸", 8762: "∺", 8763: "∻", 8764: "∼", 8765: "∽", 8766: "∾", 8767: "∿", 8768: "≀", 8769: "≁", 8770: "≂", 8771: "≃", 8772: "≄", 8773: "≅", 8774: "≆", 8775: "≇", 8776: "≈", 8777: "≉", 8778: "≊", 8779: "≋", 8780: "≌", 8781: "≍", 8782: "≎", 8783: "≏", 8784: "≐", 8785: "≑", 8786: "≒", 8787: "≓", 8788: "≔", 8789: "≕", 8790: "≖", 8791: "≗", 8793: "≙", 8794: "≚", 8796: "≜", 8799: "≟", 8800: "≠", 8801: "≡", 8802: "≢", 8804: "≤", 8805: "≥", 8806: "≦", 8807: "≧", 8808: "≨", 8809: "≩", 8810: "≪", 8811: "≫", 8812: "≬", 8813: "≭", 8814: "≮", 8815: "≯", 8816: "≰", 8817: "≱;", 8818: "≲", 8819: "≳", 8820: "≴", 8821: "≵", 8822: "≶", 8823: "≷", 8824: "≸", 8825: "≹", 8826: "≺", 8827: "≻", 8828: "≼", 8829: "≽", 8830: "≾", 8831: "≿", 8832: "⊀", 8833: "⊁", 8834: "⊂", 8835: "⊃", 8836: "⊄", 8837: "⊅", 8838: "⊆", 8839: "⊇", 8840: "⊈", 8841: "⊉", 8842: "⊊", 8843: "⊋", 8845: "⊍", 8846: "⊎", 8847: "⊏", 8848: "⊐", 8849: "⊑", 8850: "⊒", 8851: "⊓", 8852: "⊔", 8853: "⊕", 8854: "⊖", 8855: "⊗", 8856: "⊘", 8857: "⊙", 8858: "⊚", 8859: "⊛", 8861: "⊝", 8862: "⊞", 8863: "⊟", 8864: "⊠", 8865: "⊡", 8866: "⊢", 8867: "⊣", 8868: "⊤", 8869: "⊥", 8871: "⊧", 8872: "⊨", 8873: "⊩", 8874: "⊪", 8875: "⊫", 8876: "⊬", 8877: "⊭", 8878: "⊮", 8879: "⊯", 8880: "⊰", 8882: "⊲", 8883: "⊳", 8884: "⊴", 8885: "⊵", 8886: "⊶", 8887: "⊷", 8888: "⊸", 8889: "⊹", 8890: "⊺", 8891: "⊻", 8893: "⊽", 8894: "⊾", 8895: "⊿", 8896: "⋀", 8897: "⋁", 8898: "⋂", 8899: "⋃", 8900: "⋄", 8901: "⋅", 8902: "⋆", 8903: "⋇", 8904: "⋈", 8905: "⋉", 8906: "⋊", 8907: "⋋", 8908: "⋌", 8909: "⋍", 8910: "⋎", 8911: "⋏", 8912: "⋐", 8913: "⋑", 8914: "⋒", 8915: "⋓", 8916: "⋔", 8917: "⋕", 8918: "⋖", 8919: "⋗", 8920: "⋘", 8921: "⋙", 8922: "⋚", 8923: "⋛", 8926: "⋞", 8927: "⋟", 8928: "⋠", 8929: "⋡", 8930: "⋢", 8931: "⋣", 8934: "⋦", 8935: "⋧", 8936: "⋨", 8937: "⋩", 8938: "⋪", 8939: "⋫", 8940: "⋬", 8941: "⋭", 8942: "⋮", 8943: "⋯", 8944: "⋰", 8945: "⋱", 8946: "⋲", 8947: "⋳", 8948: "⋴", 8949: "⋵", 8950: "⋶", 8951: "⋷", 8953: "⋹", 8954: "⋺", 8955: "⋻", 8956: "⋼", 8957: "⋽", 8958: "⋾", 8965: "⌅", 8966: "⌆", 8968: "⌈", 8969: "⌉", 8970: "⌊", 8971: "⌋", 8972: "⌌", 8973: "⌍", 8974: "⌎", 8975: "⌏", 8976: "⌐", 8978: "⌒", 8979: "⌓", 8981: "⌕", 8982: "⌖", 8988: "⌜", 8989: "⌝", 8990: "⌞", 8991: "⌟", 8994: "⌢", 8995: "⌣", 9001: "⟨", 9002: "⟩", 9005: "⌭", 9006: "⌮", 9014: "⌶", 9021: "⌽", 9023: "⌿", 9084: "⍼", 9136: "⎰", 9137: "⎱", 9140: "⎴", 9141: "⎵", 9142: "⎶", 9180: "⏜", 9181: "⏝", 9182: "⏞", 9183: "⏟", 9186: "⏢", 9191: "⏧", 9251: "␣", 9416: "Ⓢ", 9472: "─", 9474: "│", 9484: "┌", 9488: "┐", 9492: "└", 9496: "┘", 9500: "├", 9508: "┤", 9516: "┬", 9524: "┴", 9532: "┼", 9552: "═", 9553: "║", 9554: "╒", 9555: "╓", 9556: "╔", 9557: "╕", 9558: "╖", 9559: "╗", 9560: "╘", 9561: "╙", 9562: "╚", 9563: "╛", 9564: "╜", 9565: "╝", 9566: "╞", 9567: "╟", 9568: "╠", 9569: "╡", 9570: "╢", 9571: "╣", 9572: "╤", 9573: "╥", 9674: "◊", 9675: "○", 9708: "◬", 9711: "◯", 9720: "◸", 9721: "◹", 9722: "◺", 9723: "◻", 9724: "◼", 9733: "★", 9734: "☆", 9742: "☎", 9792: "♀", 9794: "♂", 9824: "♠", 9827: "♣", 9829: "♥", 9830: "♦", 9834: "♪", 9837: "♭", 9838: "♮", 9839: "♯", 10003: "✓", 10007: "✗", 10016: "✠", 10038: "✶", 10072: "❘", 10098: "❲", 10099: "❳", 10214: "⟦", 10215: "⟧", 10216: "⟨", 10217: "⟩", 10218: "⟪", 10219: "⟫", 10220: "⟬", 10221: "⟭", 10229: "⟵", 10230: "⟶", 10231: "⟷", 10232: "⟸", 10233: "⟹", 10234: "⟺", 10236: "⟼", 10239: "⟿", 10498: "⤂", 10499: "⤃", 10500: "⤄", 10501: "⤅", 10508: "⤌", 10509: "⤍", 10510: "⤎", 10511: "⤏", 10512: "⤐", 10513: "⤑", 10514: "⤒", 10515: "⤓", 10518: "⤖", 10521: "⤙", 10522: "⤚", 10523: "⤛", 10524: "⤜", 10525: "⤝", 10526: "⤞", 10527: "⤟", 10528: "⤠", 10531: "⤣", 10532: "⤤", 10533: "⤥", 10534: "⤦", 10535: "⤧", 10536: "⤨", 10537: "⤩", 10538: "⤪", 10547: "⤳", 10549: "⤵", 10550: "⤶", 10551: "⤷", 10552: "⤸", 10553: "⤹", 10556: "⤼", 10557: "⤽", 10565: "⥅", 10568: "⥈", 10569: "⥉", 10570: "⥊", 10571: "⥋", 10574: "⥎", 10575: "⥏", 10576: "⥐", 10577: "⥑", 10578: "⥒", 10579: "⥓", 10580: "⥔", 10581: "⥕", 10582: "⥖", 10583: "⥗", 10584: "⥘", 10585: "⥙", 10586: "⥚", 10587: "⥛", 10588: "⥜", 10589: "⥝", 10590: "⥞", 10591: "⥟", 10592: "⥠", 10593: "⥡", 10594: "⥢", 10595: "⥣", 10596: "⥤", 10597: "⥥", 10598: "⥦", 10599: "⥧", 10600: "⥨", 10601: "⥩", 10602: "⥪", 10603: "⥫", 10604: "⥬", 10605: "⥭", 10606: "⥮", 10607: "⥯", 10608: "⥰", 10609: "⥱", 10610: "⥲", 10611: "⥳", 10612: "⥴", 10613: "⥵", 10614: "⥶", 10616: "⥸", 10617: "⥹", 10619: "⥻", 10620: "⥼", 10621: "⥽", 10622: "⥾", 10623: "⥿", 10629: "⦅", 10630: "⦆", 10635: "⦋", 10636: "⦌", 10637: "⦍", 10638: "⦎", 10639: "⦏", 10640: "⦐", 10641: "⦑", 10642: "⦒", 10643: "⦓", 10644: "⦔", 10645: "⦕", 10646: "⦖", 10650: "⦚", 10652: "⦜", 10653: "⦝", 10660: "⦤", 10661: "⦥", 10662: "⦦", 10663: "⦧", 10664: "⦨", 10665: "⦩", 10666: "⦪", 10667: "⦫", 10668: "⦬", 10669: "⦭", 10670: "⦮", 10671: "⦯", 10672: "⦰", 10673: "⦱", 10674: "⦲", 10675: "⦳", 10676: "⦴", 10677: "⦵", 10678: "⦶", 10679: "⦷", 10681: "⦹", 10683: "⦻", 10684: "⦼", 10686: "⦾", 10687: "⦿", 10688: "⧀", 10689: "⧁", 10690: "⧂", 10691: "⧃", 10692: "⧄", 10693: "⧅", 10697: "⧉", 10701: "⧍", 10702: "⧎", 10703: "⧏", 10704: "⧐", 10714: "∽̱", 10716: "⧜", 10717: "⧝", 10718: "⧞", 10723: "⧣", 10724: "⧤", 10725: "⧥", 10731: "⧫", 10740: "⧴", 10742: "⧶", 10752: "⨀", 10753: "⨁", 10754: "⨂", 10756: "⨄", 10758: "⨆", 10764: "⨌", 10765: "⨍", 10768: "⨐", 10769: "⨑", 10770: "⨒", 10771: "⨓", 10772: "⨔", 10773: "⨕", 10774: "⨖", 10775: "⨗", 10786: "⨢", 10787: "⨣", 10788: "⨤", 10789: "⨥", 10790: "⨦", 10791: "⨧", 10793: "⨩", 10794: "⨪", 10797: "⨭", 10798: "⨮", 10799: "⨯", 10800: "⨰", 10801: "⨱", 10803: "⨳", 10804: "⨴", 10805: "⨵", 10806: "⨶", 10807: "⨷", 10808: "⨸", 10809: "⨹", 10810: "⨺", 10811: "⨻", 10812: "⨼", 10815: "⨿", 10816: "⩀", 10818: "⩂", 10819: "⩃", 10820: "⩄", 10821: "⩅", 10822: "⩆", 10823: "⩇", 10824: "⩈", 10825: "⩉", 10826: "⩊", 10827: "⩋", 10828: "⩌", 10829: "⩍", 10832: "⩐", 10835: "⩓", 10836: "⩔", 10837: "⩕", 10838: "⩖", 10839: "⩗", 10840: "⩘", 10842: "⩚", 10843: "⩛", 10844: "⩜", 10845: "⩝", 10847: "⩟", 10854: "⩦", 10858: "⩪", 10861: "⩭", 10862: "⩮", 10863: "⩯", 10864: "⩰", 10865: "⩱", 10866: "⩲", 10867: "⩳", 10868: "⩴", 10869: "⩵", 10871: "⩷", 10872: "⩸", 10873: "⩹", 10874: "⩺", 10875: "⩻", 10876: "⩼", 10877: "⩽", 10878: "⩾", 10879: "⩿", 10880: "⪀", 10881: "⪁", 10882: "⪂", 10883: "⪃", 10884: "⪄", 10885: "⪅", 10886: "⪆", 10887: "⪇", 10888: "⪈", 10889: "⪉", 10890: "⪊", 10891: "⪋", 10892: "⪌", 10893: "⪍", 10894: "⪎", 10895: "⪏", 10896: "⪐", 10897: "⪑", 10898: "⪒", 10899: "⪓", 10900: "⪔", 10901: "⪕", 10902: "⪖", 10903: "⪗", 10904: "⪘", 10905: "⪙", 10906: "⪚", 10909: "⪝", 10910: "⪞", 10911: "⪟", 10912: "⪠", 10913: "⪡", 10914: "⪢", 10916: "⪤", 10917: "⪥", 10918: "⪦", 10919: "⪧", 10920: "⪨", 10921: "⪩", 10922: "⪪", 10923: "⪫", 10924: "⪬", 10925: "⪭", 10926: "⪮", 10927: "⪯", 10928: "⪰", 10931: "⪳", 10932: "⪴", 10933: "⪵", 10934: "⪶", 10935: "⪷", 10936: "⪸", 10937: "⪹", 10938: "⪺", 10939: "⪻", 10940: "⪼", 10941: "⪽", 10942: "⪾", 10943: "⪿", 10944: "⫀", 10945: "⫁", 10946: "⫂", 10947: "⫃", 10948: "⫄", 10949: "⫅", 10950: "⫆", 10951: "⫇", 10952: "⫈", 10955: "⫋", 10956: "⫌", 10959: "⫏", 10960: "⫐", 10961: "⫑", 10962: "⫒", 10963: "⫓", 10964: "⫔", 10965: "⫕", 10966: "⫖", 10967: "⫗", 10968: "⫘", 10969: "⫙", 10970: "⫚", 10971: "⫛", 10980: "⫤", 10982: "⫦", 10983: "⫧", 10984: "⫨", 10985: "⫩", 10987: "⫫", 10988: "⫬", 10989: "⫭", 10990: "⫮", 10991: "⫯", 10992: "⫰", 10993: "⫱", 10994: "⫲", 10995: "⫳", 11005: "⫽", 64256: "ff", 64257: "fi", 64258: "fl", 64259: "ffi", 64260: "ffl", 119964: "𝒜", 119966: "𝒞", 119967: "𝒟", 119970: "𝒢", 119973: "𝒥", 119974: "𝒦", 119977: "𝒩", 119978: "𝒪", 119979: "𝒫", 119980: "𝒬", 119982: "𝒮", 119983: "𝒯", 119984: "𝒰", 119985: "𝒱", 119986: "𝒲", 119987: "𝒳", 119988: "𝒴", 119989: "𝒵", 119990: "𝒶", 119991: "𝒷", 119992: "𝒸", 119993: "𝒹", 119995: "𝒻", 119997: "𝒽", 119998: "𝒾", 119999: "𝒿", 120000: "𝓀", 120001: "𝓁", 120002: "𝓂", 120003: "𝓃", 120005: "𝓅", 120006: "𝓆", 120007: "𝓇", 120008: "𝓈", 120009: "𝓉", 120010: "𝓊", 120011: "𝓋", 120012: "𝓌", 120013: "𝓍", 120014: "𝓎", 120015: "𝓏", 120068: "𝔄", 120069: "𝔅", 120071: "𝔇", 120072: "𝔈", 120073: "𝔉", 120074: "𝔊", 120077: "𝔍", 120078: "𝔎", 120079: "𝔏", 120080: "𝔐", 120081: "𝔑", 120082: "𝔒", 120083: "𝔓", 120084: "𝔔", 120086: "𝔖", 120087: "𝔗", 120088: "𝔘", 120089: "𝔙", 120090: "𝔚", 120091: "𝔛", 120092: "𝔜", 120094: "𝔞", 120095: "𝔟", 120096: "𝔠", 120097: "𝔡", 120098: "𝔢", 120099: "𝔣", 120100: "𝔤", 120101: "𝔥", 120102: "𝔦", 120103: "𝔧", 120104: "𝔨", 120105: "𝔩", 120106: "𝔪", 120107: "𝔫", 120108: "𝔬", 120109: "𝔭", 120110: "𝔮", 120111: "𝔯", 120112: "𝔰", 120113: "𝔱", 120114: "𝔲", 120115: "𝔳", 120116: "𝔴", 120117: "𝔵", 120118: "𝔶", 120119: "𝔷", 120120: "𝔸", 120121: "𝔹", 120123: "𝔻", 120124: "𝔼", 120125: "𝔽", 120126: "𝔾", 120128: "𝕀", 120129: "𝕁", 120130: "𝕂", 120131: "𝕃", 120132: "𝕄", 120134: "𝕆", 120138: "𝕊", 120139: "𝕋", 120140: "𝕌", 120141: "𝕍", 120142: "𝕎", 120143: "𝕏", 120144: "𝕐", 120146: "𝕒", 120147: "𝕓", 120148: "𝕔", 120149: "𝕕", 120150: "𝕖", 120151: "𝕗", 120152: "𝕘", 120153: "𝕙", 120154: "𝕚", 120155: "𝕛", 120156: "𝕜", 120157: "𝕝", 120158: "𝕞", 120159: "𝕟", 120160: "𝕠", 120161: "𝕡", 120162: "𝕢", 120163: "𝕣", 120164: "𝕤", 120165: "𝕥", 120166: "𝕦", 120167: "𝕧", 120168: "𝕨", 120169: "𝕩", 120170: "𝕪", 120171: "𝕫" }; export default ToHTMLEntity; ================================================ FILE: src/core/operations/ToHex.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {toHex, TO_HEX_DELIM_OPTIONS} from "../lib/Hex.mjs"; import Utils from "../Utils.mjs"; /** * To Hex operation */ class ToHex extends Operation { /** * ToHex constructor */ constructor() { super(); this.name = "To Hex"; this.module = "Default"; this.description = "Converts the input string to hexadecimal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a"; this.infoURL = "https://wikipedia.org/wiki/Hexadecimal"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Delimiter", type: "option", value: TO_HEX_DELIM_OPTIONS }, { name: "Bytes per line", type: "number", value: 0 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { let delim, comma; if (args[0] === "0x with comma") { delim = "0x"; comma = ","; } else { delim = Utils.charRep(args[0] || "Space"); } const lineSize = args[1]; return toHex(new Uint8Array(input), delim, 2, comma, lineSize); } /** * Highlight to Hex * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { let delim, commaLen = 0; if (args[0] === "0x with comma") { delim = "0x"; commaLen = 1; } else { delim = Utils.charRep(args[0] || "Space"); } const lineSize = args[1], len = delim.length + commaLen; const countLF = function(p) { // Count the number of LFs from 0 upto p return (p / lineSize | 0) - (p >= lineSize && p % lineSize === 0); }; pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start); pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end); // if the delimiters are not prepended, trim the trailing delimiter if (!(delim === "0x" || delim === "\\x")) { pos[0].end -= delim.length; } // if there is comma, trim the trailing comma pos[0].end -= commaLen; return pos; } /** * Highlight from Hex * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { let delim, commaLen = 0; if (args[0] === "0x with comma") { delim = "0x"; commaLen = 1; } else { delim = Utils.charRep(args[0] || "Space"); } const lineSize = args[1], len = delim.length + commaLen, width = len + 2; const countLF = function(p) { // Count the number of LFs from 0 up to p const lineLength = width * lineSize; return (p / lineLength | 0) - (p >= lineLength && p % lineLength === 0); }; pos[0].start = pos[0].start === 0 ? 0 : Math.round((pos[0].start - countLF(pos[0].start)) / width); pos[0].end = pos[0].end === 0 ? 0 : Math.ceil((pos[0].end - countLF(pos[0].end)) / width); return pos; } } export default ToHex; ================================================ FILE: src/core/operations/ToHexContent.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {toHex} from "../lib/Hex.mjs"; /** * To Hex Content operation */ class ToHexContent extends Operation { /** * ToHexContent constructor */ constructor() { super(); this.name = "To Hex Content"; this.module = "Default"; this.description = "Converts special characters in a string to hexadecimal. This format is used by SNORT for representing hex within ASCII text.

e.g. foo=bar becomes foo|3d|bar."; this.infoURL = "http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Convert", "type": "option", "value": ["Only special chars", "Only special chars including spaces", "All chars"] }, { "name": "Print spaces between bytes", "type": "boolean", "value": false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { input = new Uint8Array(input); const convert = args[0]; const spaces = args[1]; if (convert === "All chars") { let result = "|" + toHex(input) + "|"; if (!spaces) result = result.replace(/ /g, ""); return result; } let output = "", inHex = false, b; const convertSpaces = convert === "Only special chars including spaces"; for (let i = 0; i < input.length; i++) { b = input[i]; if ((b === 32 && convertSpaces) || (b < 48 && b !== 32) || (b > 57 && b < 65) || (b > 90 && b < 97) || b > 122) { if (!inHex) { output += "|"; inHex = true; } else if (spaces) output += " "; output += toHex([b]); } else { if (inHex) { output += "|"; inHex = false; } output += Utils.chr(input[i]); } } if (inHex) output += "|"; return output; } } export default ToHexContent; ================================================ FILE: src/core/operations/ToHexdump.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * To Hexdump operation */ class ToHexdump extends Operation { /** * ToHexdump constructor */ constructor() { super(); this.name = "To Hexdump"; this.module = "Default"; this.description = "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside.

The 'UNIX format' argument defines which subset of printable characters are displayed in the preview column."; this.infoURL = "https://wikipedia.org/wiki/Hex_dump"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Width", "type": "number", "value": 16, "min": 1 }, { "name": "Upper case hex", "type": "boolean", "value": false }, { "name": "Include final length", "type": "boolean", "value": false }, { "name": "UNIX format", "type": "boolean", "value": false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const data = new Uint8Array(input); const [length, upperCase, includeFinalLength, unixFormat] = args; const padding = 2; if (length < 1 || Math.round(length) !== length) throw new OperationError("Width must be a positive integer"); const lines = []; for (let i = 0; i < data.length; i += length) { let lineNo = Utils.hex(i, 8); const buff = data.slice(i, i+length); const hex = []; buff.forEach(b => hex.push(Utils.hex(b, padding))); let hexStr = hex.join(" ").padEnd(length*(padding+1), " "); const ascii = Utils.printable(Utils.byteArrayToChars(buff), false, unixFormat); const asciiStr = ascii.padEnd(buff.length, " "); if (upperCase) { hexStr = hexStr.toUpperCase(); lineNo = lineNo.toUpperCase(); } lines.push(`${lineNo} ${hexStr} |${asciiStr}|`); if (includeFinalLength && i+buff.length === data.length) { lines.push(Utils.hex(i+buff.length, 8)); } } return lines.join("\n"); } /** * Highlight To Hexdump * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { // Calculate overall selection const w = args[0] || 16, width = 14 + (w*4); let line = Math.floor(pos[0].start / w), offset = pos[0].start % w, start = 0, end = 0; pos[0].start = line*width + 10 + offset*3; line = Math.floor(pos[0].end / w); offset = pos[0].end % w; if (offset === 0) { line--; offset = w; } pos[0].end = line*width + 10 + offset*3 - 1; // Set up multiple selections for bytes let startLineNum = Math.floor(pos[0].start / width); const endLineNum = Math.floor(pos[0].end / width); if (startLineNum === endLineNum) { pos.push(pos[0]); } else { start = pos[0].start; end = (startLineNum+1) * width - w - 5; pos.push({ start: start, end: end }); while (end < pos[0].end) { startLineNum++; start = startLineNum * width + 10; end = (startLineNum+1) * width - w - 5; if (end > pos[0].end) end = pos[0].end; pos.push({ start: start, end: end }); } } // Set up multiple selections for ASCII const len = pos.length; let lineNum = 0; start = 0; end = 0; for (let i = 1; i < len; i++) { lineNum = Math.floor(pos[i].start / width); start = (((pos[i].start - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); end = (((pos[i].end + 1 - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); pos.push({ start: start, end: end }); } return pos; } /** * Highlight To Hexdump in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { const w = args[0] || 16; const width = 14 + (w*4); let line = Math.floor(pos[0].start / width); let offset = pos[0].start % width; if (offset < 10) { // In line number section pos[0].start = line*w; } else if (offset > 10+(w*3)) { // In ASCII section pos[0].start = (line+1)*w; } else { // In byte section pos[0].start = line*w + Math.floor((offset-10)/3); } line = Math.floor(pos[0].end / width); offset = pos[0].end % width; if (offset < 10) { // In line number section pos[0].end = line*w; } else if (offset > 10+(w*3)) { // In ASCII section pos[0].end = (line+1)*w; } else { // In byte section pos[0].end = line*w + Math.ceil((offset-10)/3); } return pos; } } export default ToHexdump; ================================================ FILE: src/core/operations/ToKebabCase.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import kebabCase from "lodash/kebabCase.js"; import Operation from "../Operation.mjs"; import { replaceVariableNames } from "../lib/Code.mjs"; /** * To Kebab case operation */ class ToKebabCase extends Operation { /** * ToKebabCase constructor */ constructor() { super(); this.name = "To Kebab case"; this.module = "Code"; this.description = "Converts the input string to kebab case.\n

\nKebab case is all lower case with dashes as word boundaries.\n

\ne.g. this-is-kebab-case\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; this.infoURL = "https://wikipedia.org/wiki/Kebab_case"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Attempt to be context aware", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const smart = args[0]; if (smart) { return replaceVariableNames(input, kebabCase); } else { return kebabCase(input); } } } export default ToKebabCase; ================================================ FILE: src/core/operations/ToLowerCase.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * To Lower case operation */ class ToLowerCase extends Operation { /** * ToLowerCase constructor */ constructor() { super(); this.name = "To Lower case"; this.module = "Default"; this.description = "Converts every character in the input to lower case."; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return input.toLowerCase(); } /** * Highlight To Lower case * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight To Lower case in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default ToLowerCase; ================================================ FILE: src/core/operations/ToMessagePack.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import notepack from "notepack.io"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * To MessagePack operation */ class ToMessagePack extends Operation { /** * ToMessagePack constructor */ constructor() { super(); this.name = "To MessagePack"; this.module = "Code"; 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."; this.infoURL = "https://wikipedia.org/wiki/MessagePack"; this.inputType = "JSON"; this.outputType = "ArrayBuffer"; this.args = []; } /** * @param {JSON} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { try { if (isWorkerEnvironment()) { return notepack.encode(input); } else { const res = notepack.encode(input); // Safely convert from Node Buffer to ArrayBuffer using the correct view of the data return (new Uint8Array(res)).buffer; } } catch (err) { throw new OperationError(`Could not encode JSON to MessagePack: ${err}`); } } } export default ToMessagePack; ================================================ FILE: src/core/operations/ToModhex.mjs ================================================ /** * @author linuxgemini [ilteris@asenkron.com.tr] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import { TO_MODHEX_DELIM_OPTIONS, toModhex } from "../lib/Modhex.mjs"; import Utils from "../Utils.mjs"; /** * To Modhex operation */ class ToModhex extends Operation { /** * ToModhex constructor */ constructor() { super(); this.name = "To Modhex"; this.module = "Default"; this.description = "Converts the input string to modhex bytes separated by the specified delimiter."; this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Delimiter", type: "option", value: TO_MODHEX_DELIM_OPTIONS }, { name: "Bytes per line", type: "number", value: 0 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = Utils.charRep(args[0]); const lineSize = args[1]; return toModhex(new Uint8Array(input), delim, 2, "", lineSize); } } export default ToModhex; ================================================ FILE: src/core/operations/ToMorseCode.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * To Morse Code operation */ class ToMorseCode extends Operation { /** * ToMorseCode constructor */ constructor() { super(); this.name = "To Morse Code"; this.module = "Default"; this.description = "Translates alphanumeric characters into International Morse Code.

Ignores non-Morse characters.

e.g. SOS becomes ... --- ..."; this.infoURL = "https://wikipedia.org/wiki/Morse_code"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Format options", "type": "option", "value": ["-/.", "_/.", "Dash/Dot", "DASH/DOT", "dash/dot"] }, { "name": "Letter delimiter", "type": "option", "value": LETTER_DELIM_OPTIONS }, { "name": "Word delimiter", "type": "option", "value": WORD_DELIM_OPTIONS } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const format = args[0].split("/"); const dash = format[0]; const dot = format[1]; const letterDelim = Utils.charRep(args[1]); const wordDelim = Utils.charRep(args[2]); input = input.split(/\r?\n/); input = Array.prototype.map.call(input, function(line) { let words = line.split(/ +/); words = Array.prototype.map.call(words, function(word) { const letters = Array.prototype.map.call(word, function(character) { const letter = character.toUpperCase(); if (typeof MORSE_TABLE[letter] == "undefined") { return ""; } return MORSE_TABLE[letter]; }); return letters.join(""); }); line = words.join(""); return line; }); input = input.join("\n"); input = input.replace( /|||/g, function(match) { switch (match) { case "": return dash; case "": return dot; case "": return letterDelim; case "": return wordDelim; } } ); return input; } } const MORSE_TABLE = { "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": "", "1": "", "2": "", "3": "", "4": "", "5": "", "6": "", "7": "", "8": "", "9": "", "0": "", ".": "", ",": "", ":": "", ";": "", "!": "", "?": "", "'": "", "\"": "", "/": "", "-": "", "+": "", "(": "", ")": "", "@": "", "=": "", "&": "", "_": "", "$": "", " ": "" }; export default ToMorseCode; ================================================ FILE: src/core/operations/ToOctal.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * To Octal operation */ class ToOctal extends Operation { /** * ToOctal constructor */ constructor() { super(); this.name = "To Octal"; this.module = "Default"; this.description = "Converts the input string to octal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes 316 223 316 265 316 271 316 254 40 317 203 316 277 317 205"; this.infoURL = "https://wikipedia.org/wiki/Octal"; this.inputType = "byteArray"; this.outputType = "string"; this.args = [ { "name": "Delimiter", "type": "option", "value": DELIM_OPTIONS } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = Utils.charRep(args[0] || "Space"); return input.map(val => val.toString(8)).join(delim); } } export default ToOctal; ================================================ FILE: src/core/operations/ToPunycode.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import punycode from "punycode"; /** * To Punycode operation */ class ToPunycode extends Operation { /** * ToPunycode constructor */ constructor() { super(); this.name = "To Punycode"; this.module = "Encodings"; this.description = "Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.

e.g. m\xfcnchen encodes to mnchen-3ya"; this.infoURL = "https://wikipedia.org/wiki/Punycode"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Internationalised domain name", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const idn = args[0]; if (idn) { return punycode.toASCII(input); } else { return punycode.encode(input); } } } export default ToPunycode; ================================================ FILE: src/core/operations/ToQuotedPrintable.mjs ================================================ /** * Some parts taken from mimelib (http://github.com/andris9/mimelib) * @author Andris Reinman * @license MIT * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * To Quoted Printable operation */ class ToQuotedPrintable extends Operation { /** * ToQuotedPrintable constructor */ constructor() { super(); this.name = "To Quoted Printable"; this.module = "Default"; 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.

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."; this.infoURL = "https://wikipedia.org/wiki/Quoted-printable"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { input = new Uint8Array(input); let mimeEncodedStr = this.mimeEncode(input); // fix line breaks mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, function() { return "\r\n"; }).replace(/[\t ]+$/gm, function(spaces) { return spaces.replace(/ /g, "=20").replace(/\t/g, "=09"); }); return this._addSoftLinebreaks(mimeEncodedStr, "qp"); } /** @license ======================================================================== mimelib: http://github.com/andris9/mimelib Copyright (c) 2011-2012 Andris Reinman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Encodes mime data. * * @param {byteArray|Uint8Array} buffer * @returns {string} */ mimeEncode(buffer) { const ranges = [ [0x09], [0x0A], [0x0D], [0x20], [0x21], [0x23, 0x3C], [0x3E], [0x40, 0x5E], [0x60, 0x7E] ]; let result = ""; for (let i = 0, len = buffer.length; i < len; i++) { if (this._checkRanges(buffer[i], ranges)) { result += String.fromCharCode(buffer[i]); continue; } result += "=" + (buffer[i] < 0x10 ? "0" : "") + buffer[i].toString(16).toUpperCase(); } return result; } /** * Checks if a given number falls within a given set of ranges. * * @private * @param {number} nr * @param {byteArray[]} ranges * @returns {boolean} */ _checkRanges(nr, ranges) { for (let i = ranges.length - 1; i >= 0; i--) { if (!ranges[i].length) continue; if (ranges[i].length === 1 && nr === ranges[i][0]) return true; if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) return true; } return false; } /** * Adds soft line breaks to a string. * Lines can't be longer that 76 + = 78 bytes * http://tools.ietf.org/html/rfc2045#section-6.7 * * @private * @param {string} str * @param {string} encoding * @returns {string} */ _addSoftLinebreaks(str, encoding) { const lineLengthMax = 76; encoding = (encoding || "base64").toString().toLowerCase().trim(); if (encoding === "qp") { return this._addQPSoftLinebreaks(str, lineLengthMax); } else { return this._addBase64SoftLinebreaks(str, lineLengthMax); } } /** * Adds soft line breaks to a base64 string. * * @private * @param {string} base64EncodedStr * @param {number} lineLengthMax * @returns {string} */ _addBase64SoftLinebreaks(base64EncodedStr, lineLengthMax) { base64EncodedStr = (base64EncodedStr || "").toString().trim(); return base64EncodedStr.replace(new RegExp(".{" + lineLengthMax + "}", "g"), "$&\r\n").trim(); } /** * Adds soft line breaks to a quoted printable string. * * @private * @param {string} mimeEncodedStr * @param {number} lineLengthMax * @returns {string} */ _addQPSoftLinebreaks(mimeEncodedStr, lineLengthMax) { const len = mimeEncodedStr.length, lineMargin = Math.floor(lineLengthMax / 3); let pos = 0, match, code, line, result = ""; // insert soft linebreaks where needed while (pos < len) { line = mimeEncodedStr.substr(pos, lineLengthMax); if ((match = line.match(/\r\n/))) { line = line.substr(0, match.index + match[0].length); result += line; pos += line.length; continue; } if (line.substr(-1) === "\n") { // nothing to change here result += line; pos += line.length; continue; } else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) { // truncate to nearest line break line = line.substr(0, line.length - (match[0].length - 1)); result += line; pos += line.length; continue; } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) { // truncate to nearest space line = line.substr(0, line.length - (match[0].length - 1)); } else if (line.substr(-1) === "\r") { line = line.substr(0, line.length - 1); } else { if (line.match(/=[\da-f]{0,2}$/i)) { // push incomplete encoding sequences to the next line if ((match = line.match(/=[\da-f]{0,1}$/i))) { line = line.substr(0, line.length - match[0].length); } // ensure that utf-8 sequences are not split while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/=[\da-f]{2}$/ig))) { code = parseInt(match[0].substr(1, 2), 16); if (code < 128) { break; } line = line.substr(0, line.length - 3); if (code >= 0xC0) { break; } } } } if (pos + line.length < len && line.substr(-1) !== "\n") { if (line.length === 76 && line.match(/=[\da-f]{2}$/i)) { line = line.substr(0, line.length - 3); } else if (line.length === 76) { line = line.substr(0, line.length - 1); } pos += line.length; line += "=\r\n"; } else { pos += line.length; } result += line; } return result; } } export default ToQuotedPrintable; ================================================ FILE: src/core/operations/ToSnakeCase.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import snakeCase from "lodash/snakeCase.js"; import Operation from "../Operation.mjs"; import { replaceVariableNames } from "../lib/Code.mjs"; /** * To Snake case operation */ class ToSnakeCase extends Operation { /** * ToSnakeCase constructor */ constructor() { super(); this.name = "To Snake case"; this.module = "Code"; this.description = "Converts the input string to snake case.\n

\nSnake case is all lower case with underscores as word boundaries.\n

\ne.g. this_is_snake_case\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; this.infoURL = "https://wikipedia.org/wiki/Snake_case"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Attempt to be context aware", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const smart = args[0]; if (smart) { return replaceVariableNames(input, snakeCase); } else { return snakeCase(input); } } } export default ToSnakeCase; ================================================ FILE: src/core/operations/ToTable.mjs ================================================ /** * @author Mark Jones [github.com/justanothermark] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * To Table operation */ class ToTable extends Operation { /** * ToTable constructor */ constructor() { super(); this.name = "To Table"; this.module = "Default"; this.description = "Data can be split on different characters and rendered as an HTML, ASCII or Markdown table with an optional header row.

Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to \\t to support TSV (Tab Separated Values) or | for PSV (Pipe Separated Values).

You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter."; this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; this.inputType = "string"; this.outputType = "html"; this.args = [ { "name": "Cell delimiters", "type": "binaryShortString", "value": "," }, { "name": "Row delimiters", "type": "binaryShortString", "value": "\\r\\n" }, { "name": "Make first row header", "type": "boolean", "value": false }, { "name": "Format", "type": "option", "value": ["ASCII", "HTML", "Markdown"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const [cellDelims, rowDelims, firstRowHeader, format] = args; // Process the input into a nested array of elements. const tableData = Utils.parseCSV(Utils.escapeHtml(input), cellDelims.split(""), rowDelims.split("")); if (!tableData.length) return ""; // Render the data in the requested format. switch (format) { case "ASCII": return asciiOutput(tableData); case "HTML": return htmlOutput(tableData); case "Markdown": return markdownOutput(tableData); default: return htmlOutput(tableData); } /** * Outputs an array of data as an ASCII table. * * @param {string[][]} tableData * @returns {string} */ function asciiOutput(tableData) { const horizontalBorder = "-"; const verticalBorder = "|"; const crossBorder = "+"; let output = ""; const longestCells = []; // Find longestCells value per column to pad cells equally. tableData.forEach(function(row, index) { row.forEach(function(cell, cellIndex) { if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) { longestCells[cellIndex] = cell.length; } }); }); // Add the top border of the table to the output. output += outputHorizontalBorder(longestCells); // If the first row is a header, remove the row from the data and // add it to the output with another horizontal border. if (firstRowHeader) { const row = tableData.shift(); output += outputRow(row, longestCells); output += outputHorizontalBorder(longestCells); } // Add the rest of the table rows. tableData.forEach(function(row, index) { output += outputRow(row, longestCells); }); // Close the table with a final horizontal border. output += outputHorizontalBorder(longestCells); return output; /** * Outputs a row of correctly padded cells. */ function outputRow(row, longestCells) { let rowOutput = verticalBorder; row.forEach(function(cell, index) { rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder; }); rowOutput += "\n"; return rowOutput; } /** * Outputs a horizontal border with a different character where * the horizontal border meets a vertical border. */ function outputHorizontalBorder(longestCells) { let rowOutput = crossBorder; longestCells.forEach(function(cellLength) { rowOutput += horizontalBorder.repeat(cellLength + 2) + crossBorder; }); rowOutput += "\n"; return rowOutput; } } /** * Outputs a table of data as a HTML table. * * @param {string[][]} tableData * @returns {string} */ function htmlOutput(tableData) { // Start the HTML output with suitable classes for styling. let output = "
"; // If the first row is a header then put it in with "; output += outputRow(row, "th"); output += ""; } // Output the rest of the rows in the . output += ""; tableData.forEach(function(row, index) { output += outputRow(row, "td"); }); // Close the body and table elements. output += "
cells. if (firstRowHeader) { const row = tableData.shift(); output += "
"; return output; /** * Outputs a table row. * * @param {string[]} row * @param {string} cellType */ function outputRow(row, cellType) { let output = ""; row.forEach(function(cell) { output += "<" + cellType + ">" + cell + ""; }); output += ""; return output; } } /** * Outputs an array of data as a Markdown table. * * @param {string[][]} tableData * @returns {string} */ function markdownOutput(tableData) { const headerDivider = "-"; const verticalBorder = "|"; let output = ""; const longestCells = []; // Find longestCells value per column to pad cells equally. tableData.forEach(function(row, index) { row.forEach(function(cell, cellIndex) { if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) { longestCells[cellIndex] = cell.length; } }); }); // Ignoring the checkbox, as current Mardown renderer in CF doesn't handle table without headers const row = tableData.shift(); output += outputRow(row, longestCells); let rowOutput = verticalBorder; row.forEach(function(cell, index) { rowOutput += " " + headerDivider.repeat(longestCells[index]) + " " + verticalBorder; }); output += rowOutput += "\n"; // Add the rest of the table rows. tableData.forEach(function(row, index) { output += outputRow(row, longestCells); }); return output; /** * Outputs a row of correctly padded cells. */ function outputRow(row, longestCells) { let rowOutput = verticalBorder; row.forEach(function(cell, index) { rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder; }); rowOutput += "\n"; return rowOutput; } } } } export default ToTable; ================================================ FILE: src/core/operations/ToUNIXTimestamp.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import moment from "moment-timezone"; import {UNITS} from "../lib/DateTime.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * To UNIX Timestamp operation */ class ToUNIXTimestamp extends Operation { /** * ToUNIXTimestamp constructor */ constructor() { super(); this.name = "To UNIX Timestamp"; this.module = "Default"; this.description = "Parses a datetime string in UTC and returns the corresponding UNIX timestamp.

e.g. Mon 1 January 2001 11:00:00 becomes 978346800

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch)."; this.infoURL = "https://wikipedia.org/wiki/Unix_time"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Units", "type": "option", "value": UNITS }, { "name": "Treat as UTC", "type": "boolean", "value": true }, { "name": "Show parsed datetime", "type": "boolean", "value": true } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} * * @throws {OperationError} if unit unrecognised */ run(input, args) { const [units, treatAsUTC, showDateTime] = args, d = treatAsUTC ? moment.utc(input) : moment(input); let result = ""; if (units === "Seconds (s)") { result = d.unix(); } else if (units === "Milliseconds (ms)") { result = d.valueOf(); } else if (units === "Microseconds (μs)") { result = d.valueOf() * 1000; } else if (units === "Nanoseconds (ns)") { result = d.valueOf() * 1000000; } else { throw new OperationError("Unrecognised unit"); } return showDateTime ? `${result} (${d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss")} UTC)` : result.toString(); } } export default ToUNIXTimestamp; ================================================ FILE: src/core/operations/ToUpperCase.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * To Upper case operation */ class ToUpperCase extends Operation { /** * ToUpperCase constructor */ constructor() { super(); this.name = "To Upper case"; this.module = "Default"; this.description = "Converts the input string to upper case, optionally limiting scope to only the first character in each word, sentence or paragraph."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Scope", "type": "option", "value": ["All", "Word", "Sentence", "Paragraph"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { if (!args || args.length === 0) { throw new OperationError("No capitalization scope was provided."); } const scope = args[0]; if (scope === "All") { return input.toUpperCase(); } const scopeRegex = { "Word": /(\b\w)/gi, "Sentence": /(?:\.|^)\s*(\b\w)/gi, "Paragraph": /(?:\n|^)\s*(\b\w)/gi }[scope]; if (scopeRegex === undefined) { throw new OperationError("Unrecognized capitalization scope"); } // Use the regex to capitalize the input return input.replace(scopeRegex, function(m) { return m.toUpperCase(); }); } /** * Highlight To Upper case * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight To Upper case in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default ToUpperCase; ================================================ FILE: src/core/operations/TranslateDateTimeFormat.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import moment from "moment-timezone"; import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs"; /** * Translate DateTime Format operation */ class TranslateDateTimeFormat extends Operation { /** * TranslateDateTimeFormat constructor */ constructor() { super(); this.name = "Translate DateTime Format"; this.module = "Default"; this.description = "Parses a datetime string in one format and re-writes it in another.

Run with no input to see the relevant format string examples."; this.infoURL = "https://momentjs.com/docs/#/parsing/string-format/"; this.inputType = "string"; this.outputType = "string"; this.presentType = "html"; this.args = [ { "name": "Built in formats", "type": "populateOption", "value": DATETIME_FORMATS, "target": 1 }, { "name": "Input format string", "type": "binaryString", "value": "DD/MM/YYYY HH:mm:ss" }, { "name": "Input timezone", "type": "option", "value": ["UTC"].concat(moment.tz.names()) }, { "name": "Output format string", "type": "binaryString", "value": "dddd Do MMMM YYYY HH:mm:ss Z z" }, { "name": "Output timezone", "type": "option", "value": ["UTC"].concat(moment.tz.names()) } ]; this.invalidFormatMessage = "Invalid format."; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [inputFormat, inputTimezone, outputFormat, outputTimezone] = args.slice(1); let date; try { date = moment.tz(input, inputFormat, inputTimezone); if (!date || date.format() === "Invalid date") throw Error; } catch (err) { return this.invalidFormatMessage; } return date.tz(outputTimezone).format(outputFormat.replace(/[<>]/g, "")); } /** * @param {string} data * @returns {html} */ present(data) { if (data === this.invalidFormatMessage) { return `${data}\n\n${FORMAT_EXAMPLES}`; } return Utils.escapeHtml(data); } } export default TranslateDateTimeFormat; ================================================ FILE: src/core/operations/TripleDESDecrypt.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import forge from "node-forge"; /** * Triple DES Decrypt operation */ class TripleDESDecrypt extends Operation { /** * TripleDESDecrypt constructor */ constructor() { super(); this.name = "Triple DES Decrypt"; this.module = "Ciphers"; this.description = "Triple DES applies DES three times to each block to increase key size.

Key: Triple DES uses a key length of 24 bytes (192 bits).

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used as a default."; this.infoURL = "https://wikipedia.org/wiki/Triple_DES"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Mode", "type": "option", "value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"] }, { "name": "Input", "type": "option", "value": ["Hex", "Raw"] }, { "name": "Output", "type": "option", "value": ["Raw", "Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteString(args[0].string, args[0].option), iv = Utils.convertToByteArray(args[1].string, args[1].option), mode = args[2].substring(0, 3), noPadding = args[2].endsWith("NoPadding"), inputType = args[3], outputType = args[4]; if (key.length !== 24 && key.length !== 16) { throw new OperationError(`Invalid key length: ${key.length} bytes Triple DES uses a key length of 24 bytes (192 bits).`); } if (iv.length !== 8 && mode !== "ECB") { throw new OperationError(`Invalid IV length: ${iv.length} bytes Triple DES uses an IV length of 8 bytes (64 bits). Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); } input = Utils.convertToByteString(input, inputType); const decipher = forge.cipher.createDecipher("3DES-" + mode, key.length === 16 ? key + key.substring(0, 8) : key); /* Allow for a "no padding" mode */ if (noPadding) { decipher.mode.unpad = function(output, options) { return true; }; } decipher.start({iv: iv}); decipher.update(forge.util.createBuffer(input)); const result = decipher.finish(); if (result) { return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); } else { throw new OperationError("Unable to decrypt input with these parameters."); } } } export default TripleDESDecrypt; ================================================ FILE: src/core/operations/TripleDESEncrypt.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import forge from "node-forge"; /** * Triple DES Encrypt operation */ class TripleDESEncrypt extends Operation { /** * TripleDESEncrypt constructor */ constructor() { super(); this.name = "Triple DES Encrypt"; this.module = "Ciphers"; this.description = "Triple DES applies DES three times to each block to increase key size.

Key: Triple DES uses a key length of 24 bytes (192 bits).

You can generate a password-based key using one of the KDF operations.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; this.infoURL = "https://wikipedia.org/wiki/Triple_DES"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "IV", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Mode", "type": "option", "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] }, { "name": "Input", "type": "option", "value": ["Raw", "Hex"] }, { "name": "Output", "type": "option", "value": ["Hex", "Raw"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteString(args[0].string, args[0].option), iv = Utils.convertToByteArray(args[1].string, args[1].option), mode = args[2], inputType = args[3], outputType = args[4]; if (key.length !== 24 && key.length !== 16) { throw new OperationError(`Invalid key length: ${key.length} bytes Triple DES uses a key length of 24 bytes (192 bits).`); } if (iv.length !== 8 && mode !== "ECB") { throw new OperationError(`Invalid IV length: ${iv.length} bytes Triple DES uses an IV length of 8 bytes (64 bits). Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); } input = Utils.convertToByteString(input, inputType); const cipher = forge.cipher.createCipher("3DES-" + mode, key.length === 16 ? key + key.substring(0, 8) : key); cipher.start({iv: iv}); cipher.update(forge.util.createBuffer(input)); cipher.finish(); return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); } } export default TripleDESEncrypt; ================================================ FILE: src/core/operations/Typex.mjs ================================================ /** * Emulation of the Typex machine. * * Tested against a genuine Typex machine using a variety of inputs * and settings to confirm correctness. * * @author s2224834 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import {LETTERS, Reflector} from "../lib/Enigma.mjs"; import {ROTORS, REFLECTORS, TypexMachine, Plugboard, Rotor} from "../lib/Typex.mjs"; /** * Typex operation */ class Typex extends Operation { /** * Typex constructor */ constructor() { super(); this.name = "Typex"; this.module = "Bletchley"; this.description = "Encipher/decipher with the WW2 Typex machine.

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.

To configure the reflector plugboard, enter a string of connected pairs of letters in the reflector box, e.g. AB CD EF 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 < then a list of stepping points.

More detailed descriptions of the Enigma, Typex and Bombe operations can be found here."; this.infoURL = "https://wikipedia.org/wiki/Typex"; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "1st (left-hand) rotor", type: "editableOption", value: ROTORS, defaultIndex: 0 }, { name: "1st rotor reversed", type: "boolean", value: false }, { name: "1st rotor ring setting", type: "option", value: LETTERS }, { name: "1st rotor initial value", type: "option", value: LETTERS }, { name: "2nd rotor", type: "editableOption", value: ROTORS, defaultIndex: 1 }, { name: "2nd rotor reversed", type: "boolean", value: false }, { name: "2nd rotor ring setting", type: "option", value: LETTERS }, { name: "2nd rotor initial value", type: "option", value: LETTERS }, { name: "3rd (middle) rotor", type: "editableOption", value: ROTORS, defaultIndex: 2 }, { name: "3rd rotor reversed", type: "boolean", value: false }, { name: "3rd rotor ring setting", type: "option", value: LETTERS }, { name: "3rd rotor initial value", type: "option", value: LETTERS }, { name: "4th (static) rotor", type: "editableOption", value: ROTORS, defaultIndex: 3 }, { name: "4th rotor reversed", type: "boolean", value: false }, { name: "4th rotor ring setting", type: "option", value: LETTERS }, { name: "4th rotor initial value", type: "option", value: LETTERS }, { name: "5th (right-hand, static) rotor", type: "editableOption", value: ROTORS, defaultIndex: 4 }, { name: "5th rotor reversed", type: "boolean", value: false }, { name: "5th rotor ring setting", type: "option", value: LETTERS }, { name: "5th rotor initial value", type: "option", value: LETTERS }, { name: "Reflector", type: "editableOption", value: REFLECTORS }, { name: "Plugboard", type: "string", value: "" }, { name: "Typex keyboard emulation", type: "option", value: ["None", "Encrypt", "Decrypt"] }, { name: "Strict output", hint: "Remove non-alphabet letters and group output", type: "boolean", value: true }, ]; } /** * Helper - for ease of use rotors are specified as a single string; this * method breaks the spec string into wiring and steps parts. * * @param {string} rotor - Rotor specification string. * @param {number} i - For error messages, the number of this rotor. * @returns {string[]} */ parseRotorStr(rotor, i) { if (rotor === "") { throw new OperationError(`Rotor ${i} must be provided.`); } if (!rotor.includes("<")) { return [rotor, ""]; } return rotor.split("<", 2); } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const reflectorstr = args[20]; const plugboardstr = args[21]; const typexKeyboard = args[22]; const removeOther = args[23]; const rotors = []; for (let i=0; i<5; i++) { const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*4]); rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*4 + 1], args[i*4+2], args[i*4+3])); } // Rotors are handled in reverse rotors.reverse(); const reflector = new Reflector(reflectorstr); let plugboardstrMod = plugboardstr; if (plugboardstrMod === "") { plugboardstrMod = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; } const plugboard = new Plugboard(plugboardstrMod); if (removeOther) { if (typexKeyboard === "Encrypt") { input = input.replace(/[^A-Za-z0-9 /%£()',.-]/g, ""); } else { input = input.replace(/[^A-Za-z]/g, ""); } } const typex = new TypexMachine(rotors, reflector, plugboard, typexKeyboard); let result = typex.crypt(input); if (removeOther && typexKeyboard !== "Decrypt") { // Five character cipher groups is traditional result = result.replace(/([A-Z]{5})(?!$)/g, "$1 "); } return result; } /** * Highlight Typex * This is only possible if we're passing through non-alphabet characters. * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { if (args[18] === false) { return pos; } } /** * Highlight Typex in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { if (args[18] === false) { return pos; } } } export default Typex; ================================================ FILE: src/core/operations/UNIXTimestampToWindowsFiletime.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import BigNumber from "bignumber.js"; import OperationError from "../errors/OperationError.mjs"; /** * UNIX Timestamp to Windows Filetime operation */ class UNIXTimestampToWindowsFiletime extends Operation { /** * UNIXTimestampToWindowsFiletime constructor */ constructor() { super(); this.name = "UNIX Timestamp to Windows Filetime"; this.module = "Default"; this.description = "Converts a UNIX timestamp to a Windows Filetime value.

A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).

This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds."; this.infoURL = "https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Input units", "type": "option", "value": ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"] }, { "name": "Output format", "type": "option", "value": ["Decimal", "Hex (big endian)", "Hex (little endian)"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [units, format] = args; if (!input) return ""; input = new BigNumber(input); if (units === "Seconds (s)") { input = input.multipliedBy(new BigNumber("10000000")); } else if (units === "Milliseconds (ms)") { input = input.multipliedBy(new BigNumber("10000")); } else if (units === "Microseconds (μs)") { input = input.multipliedBy(new BigNumber("10")); } else if (units === "Nanoseconds (ns)") { input = input.dividedBy(new BigNumber("100")); } else { throw new OperationError("Unrecognised unit"); } input = input.plus(new BigNumber("116444736000000000")); let result; if (format.startsWith("Hex")) { result = input.toString(16); } else { result = input.toFixed(); } if (format === "Hex (little endian)") { // Swap endianness let flipped = ""; for (let i = result.length - 2; i >= 0; i -= 2) { flipped += result.charAt(i); flipped += result.charAt(i + 1); } if (result.length % 2 !== 0) { flipped += "0" + result.charAt(0); } result = flipped; } return result; } } export default UNIXTimestampToWindowsFiletime; ================================================ FILE: src/core/operations/URLDecode.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * URL Decode operation */ class URLDecode extends Operation { /** * URLDecode constructor */ constructor() { super(); this.name = "URL Decode"; this.module = "URL"; this.description = "Converts URI/URL percent-encoded characters back to their raw values.

e.g. %3d becomes ="; this.infoURL = "https://wikipedia.org/wiki/Percent-encoding"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Treat \"+\" as space", "type": "boolean", "value": true }, ]; this.checks = [ { pattern: ".*(?:%[\\da-f]{2}.*){4}", flags: "i", args: [] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const plusIsSpace = args[0]; const data = plusIsSpace ? input.replace(/\+/g, "%20") : input; try { return decodeURIComponent(data); } catch (err) { return unescape(data); } } } export default URLDecode; ================================================ FILE: src/core/operations/URLEncode.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * URL Encode operation */ class URLEncode extends Operation { /** * URLEncode constructor */ constructor() { super(); this.name = "URL Encode"; this.module = "URL"; this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.

e.g. = becomes %3d"; this.infoURL = "https://wikipedia.org/wiki/Percent-encoding"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Encode all special chars", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const encodeAll = args[0]; return encodeAll ? this.encodeAllChars(input) : encodeURI(input); } /** * Encode characters in URL outside of encodeURI() function spec * * @param {string} str * @returns {string} */ encodeAllChars (str) { // TODO Do this programmatically return encodeURIComponent(str) .replace(/!/g, "%21") .replace(/#/g, "%23") .replace(/'/g, "%27") .replace(/\(/g, "%28") .replace(/\)/g, "%29") .replace(/\*/g, "%2A") .replace(/-/g, "%2D") .replace(/\./g, "%2E") .replace(/_/g, "%5F") .replace(/~/g, "%7E"); } } export default URLEncode; ================================================ FILE: src/core/operations/UnescapeString.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Unescape string operation */ class UnescapeString extends Operation { /** * UnescapeString constructor */ constructor() { super(); this.name = "Unescape string"; this.module = "Default"; this.description = "Unescapes characters in a string that have been escaped. For example, Don\\'t stop me now becomes Don't stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\nnn (Octal, where n is 0-7)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
  • \\unnnn (Unicode character)
  • \\u{nnnnnn} (Unicode code point)
"; this.infoURL = "https://wikipedia.org/wiki/Escape_sequence"; this.inputType = "string"; this.outputType = "string"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { return Utils.parseEscapedChars(input); } } export default UnescapeString; ================================================ FILE: src/core/operations/UnescapeUnicodeCharacters.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Unescape Unicode Characters operation */ class UnescapeUnicodeCharacters extends Operation { /** * UnescapeUnicodeCharacters constructor */ constructor() { super(); this.name = "Unescape Unicode Characters"; this.module = "Default"; this.description = "Converts unicode-escaped character notation back into raw characters.

Supports the prefixes:
  • \\u
  • %u
  • U+
e.g. \\u03c3\\u03bf\\u03c5 becomes σου"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Prefix", "type": "option", "value": ["\\u", "%u", "U+"] } ]; this.checks = [ { pattern: "\\\\u(?:[\\da-f]{4,6})", flags: "i", args: ["\\u"] }, { pattern: "%u(?:[\\da-f]{4,6})", flags: "i", args: ["%u"] }, { pattern: "U\\+(?:[\\da-f]{4,6})", flags: "i", args: ["U+"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const prefix = prefixToRegex[args[0]], regex = new RegExp(prefix+"([a-f\\d]{4})", "ig"); let output = "", m, i = 0; while ((m = regex.exec(input))) { // Add up to match output += input.slice(i, m.index); // Add match output += Utils.chr(parseInt(m[1], 16)); i = regex.lastIndex; } // Add all after final match output += input.slice(i, input.length); return output; } } /** * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex. */ const prefixToRegex = { "\\u": "\\\\u", "%u": "%u", "U+": "U\\+" }; export default UnescapeUnicodeCharacters; ================================================ FILE: src/core/operations/UnicodeTextFormat.mjs ================================================ /** * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; /** * Unicode Text Format operation */ class UnicodeTextFormat extends Operation { /** * UnicodeTextFormat constructor */ constructor() { super(); this.name = "Unicode Text Format"; this.module = "Default"; this.description = "Adds Unicode combining characters to change formatting of plaintext."; this.infoURL = "https://wikipedia.org/wiki/Combining_character"; this.inputType = "byteArray"; this.outputType = "byteArray"; this.args = [ { name: "Underline", type: "boolean", value: "false" }, { name: "Strikethrough", type: "boolean", value: "false" } ]; } /** * @param {byteArray} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { const [underline, strikethrough] = args; let output = input.map(char => [char]); if (strikethrough) { output = output.map(charFormat => { charFormat.push(...Utils.strToUtf8ByteArray("\u0336")); return charFormat; }); } if (underline) { output = output.map(charFormat => { charFormat.push(...Utils.strToUtf8ByteArray("\u0332")); return charFormat; }); } // return output.flat(); - Not supported in Node 10, polyfilled return [].concat(...output); } } export default UnicodeTextFormat; ================================================ FILE: src/core/operations/Unique.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * Unique operation */ class Unique extends Operation { /** * Unique constructor */ constructor() { super(); this.name = "Unique"; this.module = "Default"; this.description = "Removes duplicate strings from the input."; this.inputType = "string"; this.outputType = "string"; this.args = [ { name: "Delimiter", type: "option", value: INPUT_DELIM_OPTIONS }, { name: "Display count", type: "boolean", value: false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const delim = Utils.charRep(args[0]), count = args[1]; if (count) { const valMap = input.split(delim).reduce((acc, curr) => { if (Object.prototype.hasOwnProperty.call(acc, curr)) { acc[curr]++; } else { acc[curr] = 1; } return acc; }, {}); return Object.keys(valMap).map(val => `${valMap[val]} ${val}`).join(delim); } else { return input.split(delim).unique().join(delim); } } } export default Unique; ================================================ FILE: src/core/operations/Untar.mjs ================================================ /** * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import Stream from "../lib/Stream.mjs"; /** * Untar operation */ class Untar extends Operation { /** * Untar constructor */ constructor() { super(); this.name = "Untar"; this.module = "Compression"; this.description = "Unpacks a tarball and displays it per file."; this.infoURL = "https://wikipedia.org/wiki/Tar_(computing)"; this.inputType = "ArrayBuffer"; this.outputType = "List"; this.presentType = "html"; this.args = []; this.checks = [ { "pattern": "^.{257}\\x75\\x73\\x74\\x61\\x72", "flags": "", "args": [] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {List} */ run(input, args) { input = new Uint8Array(input); const stream = new Stream(input), files = []; while (stream.hasMore()) { const dataPosition = stream.position + 512; const file = { fileName: stream.readString(100), fileMode: stream.readString(8), ownerUID: stream.readString(8), ownerGID: stream.readString(8), size: parseInt(stream.readString(12), 8), // Octal lastModTime: new Date(1000 * parseInt(stream.readString(12), 8)), // Octal checksum: stream.readString(8), type: stream.readString(1), linkedFileName: stream.readString(100), USTARFormat: stream.readString(6).indexOf("ustar") >= 0, }; if (file.USTARFormat) { file.version = stream.readString(2); file.ownerUserName = stream.readString(32); file.ownerGroupName = stream.readString(32); file.deviceMajor = stream.readString(8); file.deviceMinor = stream.readString(8); file.filenamePrefix = stream.readString(155); } stream.position = dataPosition; if (file.type === "0") { // File let endPosition = stream.position + file.size; if (file.size % 512 !== 0) { endPosition += 512 - (file.size % 512); } file.bytes = stream.getBytes(file.size); files.push(new File([new Uint8Array(file.bytes)], file.fileName)); stream.position = endPosition; } else if (file.type === "5") { // Directory files.push(new File([new Uint8Array(file.bytes)], file.fileName)); } else { // Symlink or empty bytes } } return files; } /** * Displays the files in HTML for web apps. * * @param {File[]} files * @returns {html} */ async present(files) { return await Utils.displayFilesAsHTML(files); } } export default Untar; ================================================ FILE: src/core/operations/Unzip.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import unzip from "zlibjs/bin/unzip.min.js"; const Zlib = unzip.Zlib; /** * Unzip operation */ class Unzip extends Operation { /** * Unzip constructor */ constructor() { super(); this.name = "Unzip"; this.module = "Compression"; this.description = "Decompresses data using the PKZIP algorithm and displays it per file, with support for passwords."; this.infoURL = "https://wikipedia.org/wiki/Zip_(file_format)"; this.inputType = "ArrayBuffer"; this.outputType = "List"; this.presentType = "html"; this.args = [ { name: "Password", type: "binaryString", value: "" }, { name: "Verify result", type: "boolean", value: false } ]; this.checks = [ { pattern: "^\\x50\\x4b(?:\\x03|\\x05|\\x07)(?:\\x04|\\x06|\\x08)", flags: "", args: ["", false] } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {File[]} */ run(input, args) { const options = { password: Utils.strToByteArray(args[0]), verify: args[1] }, unzip = new Zlib.Unzip(new Uint8Array(input), options), filenames = unzip.getFilenames(); return filenames.map(fileName => { const bytes = unzip.decompress(fileName); return new File([bytes], fileName); }); } /** * Displays the files in HTML for web apps. * * @param {File[]} files * @returns {html} */ async present(files) { return await Utils.displayFilesAsHTML(files); } } export default Unzip; ================================================ FILE: src/core/operations/VarIntDecode.mjs ================================================ /** * @author GCHQ Contributor [3] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Protobuf from "../lib/Protobuf.mjs"; /** * VarInt Decode operation */ class VarIntDecode extends Operation { /** * VarIntDecode constructor */ constructor() { super(); this.name = "VarInt Decode"; this.module = "Default"; this.description = "Decodes a VarInt encoded integer. VarInt is an efficient way of encoding variable length integers and is commonly used with Protobuf."; this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding#varints"; this.inputType = "byteArray"; this.outputType = "string"; this.args = []; } /** * @param {byteArray} input * @param {Object[]} args * @returns {number} */ run(input, args) { try { if (typeof BigInt === "function") { let result = BigInt(0); let offset = BigInt(0); for (let i = 0; i < input.length; i++) { result |= BigInt(input[i] & 0x7f) << offset; if (!(input[i] & 0x80)) break; offset += BigInt(7); } return result.toString(); } else { return Protobuf.varIntDecode(input).toString(); } } catch (err) { throw new OperationError(err); } } } export default VarIntDecode; ================================================ FILE: src/core/operations/VarIntEncode.mjs ================================================ /** * @author GCHQ Contributor [3] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Protobuf from "../lib/Protobuf.mjs"; /** * VarInt Encode operation */ class VarIntEncode extends Operation { /** * VarIntEncode constructor */ constructor() { super(); this.name = "VarInt Encode"; this.module = "Default"; 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."; this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding#varints"; this.inputType = "string"; this.outputType = "byteArray"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { try { if (typeof BigInt === "function") { let value = BigInt(input); if (value < 0) throw new OperationError("Negative values cannot be represented as VarInt"); const result = []; while (value >= 0x80) { result.push(Number(value & BigInt(0x7f)) | 0x80); value >>= BigInt(7); } result.push(Number(value)); return result; } else { return Protobuf.varIntEncode(Number(input)); } } catch (err) { throw new OperationError(err); } } } export default VarIntEncode; ================================================ FILE: src/core/operations/ViewBitPlane.mjs ================================================ /** * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { isImage } from "../lib/FileType.mjs"; import { toBase64 } from "../lib/Base64.mjs"; import { Jimp } from "jimp"; /** * View Bit Plane operation */ class ViewBitPlane extends Operation { /** * ViewBitPlane constructor */ constructor() { super(); this.name = "View Bit Plane"; this.module = "Image"; this.description = "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."; this.infoURL = "https://wikipedia.org/wiki/Bit_plane"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.presentType = "html"; this.args = [ { name: "Colour", type: "option", value: COLOUR_OPTIONS, }, { name: "Bit", type: "number", value: 0, }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ async run(input, args) { if (!isImage(input)) throw new OperationError("Please enter a valid image file."); const [colour, bit] = args, parsedImage = await Jimp.read(input), width = parsedImage.bitmap.width, height = parsedImage.bitmap.height, colourIndex = COLOUR_OPTIONS.indexOf(colour), bitIndex = 7 - bit; if (bit < 0 || bit > 7) { throw new OperationError( "Error: Bit argument must be between 0 and 7", ); } let pixel, bin, newPixelValue; parsedImage.scan(0, 0, width, height, function (x, y, idx) { pixel = this.bitmap.data[idx + colourIndex]; bin = Utils.bin(pixel); newPixelValue = 255; if (bin.charAt(bitIndex) === "1") newPixelValue = 0; for (let i = 0; i < 3; i++) { this.bitmap.data[idx + i] = newPixelValue; } this.bitmap.data[idx + 3] = 255; }); const imageBuffer = await parsedImage.getBuffer(parsedImage.mime); return new Uint8Array(imageBuffer).buffer; } /** * Displays the extracted data as an image for web apps. * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const type = isImage(data); return ``; } } const COLOUR_OPTIONS = ["Red", "Green", "Blue", "Alpha"]; export default ViewBitPlane; ================================================ FILE: src/core/operations/VigenèreDecode.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Vigenère Decode operation */ class VigenèreDecode extends Operation { /** * VigenèreDecode constructor */ constructor() { super(); this.name = "Vigenère Decode"; this.module = "Ciphers"; 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."; this.infoURL = "https://wikipedia.org/wiki/Vigenère_cipher"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "string", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const alphabet = "abcdefghijklmnopqrstuvwxyz", key = args[0].toLowerCase(); let output = "", fail = 0, keyIndex, msgIndex, chr; if (!key) throw new OperationError("No key entered"); if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("The key must consist only of letters"); for (let i = 0; i < input.length; i++) { if (alphabet.indexOf(input[i]) >= 0) { chr = key[(i - fail) % key.length]; keyIndex = alphabet.indexOf(chr); msgIndex = alphabet.indexOf(input[i]); // Subtract indexes from each other, add 26 just in case the value is negative, // modulo to remove if necessary output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26]; } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { chr = key[(i - fail) % key.length].toLowerCase(); keyIndex = alphabet.indexOf(chr); msgIndex = alphabet.indexOf(input[i].toLowerCase()); output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase(); } else { output += input[i]; fail++; } } return output; } /** * Highlight Vigenère Decode * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight Vigenère Decode in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default VigenèreDecode; ================================================ FILE: src/core/operations/VigenèreEncode.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * Vigenère Encode operation */ class VigenèreEncode extends Operation { /** * VigenèreEncode constructor */ constructor() { super(); this.name = "Vigenère Encode"; this.module = "Ciphers"; 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."; this.infoURL = "https://wikipedia.org/wiki/Vigenère_cipher"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "string", "value": "" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const alphabet = "abcdefghijklmnopqrstuvwxyz", key = args[0].toLowerCase(); let output = "", fail = 0, keyIndex, msgIndex, chr; if (!key) throw new OperationError("No key entered"); if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("The key must consist only of letters"); for (let i = 0; i < input.length; i++) { if (alphabet.indexOf(input[i]) >= 0) { // Get the corresponding character of key for the current letter, accounting // for chars not in alphabet chr = key[(i - fail) % key.length]; // Get the location in the vigenere square of the key char keyIndex = alphabet.indexOf(chr); // Get the location in the vigenere square of the message char msgIndex = alphabet.indexOf(input[i]); // Get the encoded letter by finding the sum of indexes modulo 26 and finding // the letter corresponding to that output += alphabet[(keyIndex + msgIndex) % 26]; } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { chr = key[(i - fail) % key.length].toLowerCase(); keyIndex = alphabet.indexOf(chr); msgIndex = alphabet.indexOf(input[i].toLowerCase()); output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase(); } else { output += input[i]; fail++; } } return output; } /** * Highlight Vigenère Encode * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight Vigenère Encode in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default VigenèreEncode; ================================================ FILE: src/core/operations/Whirlpool.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {runHash} from "../lib/Hash.mjs"; /** * Whirlpool operation */ class Whirlpool extends Operation { /** * Whirlpool constructor */ constructor() { super(); this.name = "Whirlpool"; this.module = "Crypto"; 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.

Several variants exist:
  • Whirlpool-0 is the original version released in 2000.
  • Whirlpool-T is the first revision, released in 2001, improving the generation of the s-box.
  • Whirlpool is the latest revision, released in 2003, fixing a flaw in the diffusion matrix.
"; this.infoURL = "https://wikipedia.org/wiki/Whirlpool_(cryptography)"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Variant", type: "option", value: ["Whirlpool", "Whirlpool-T", "Whirlpool-0"] }, { name: "Rounds", type: "number", value: 10, min: 1, max: 10 } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const variant = args[0].toLowerCase(); return runHash(variant, input, {rounds: args[1]}); } } export default Whirlpool; ================================================ FILE: src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import BigNumber from "bignumber.js"; import OperationError from "../errors/OperationError.mjs"; /** * Windows Filetime to UNIX Timestamp operation */ class WindowsFiletimeToUNIXTimestamp extends Operation { /** * WindowsFiletimeToUNIXTimestamp constructor */ constructor() { super(); this.name = "Windows Filetime to UNIX Timestamp"; this.module = "Default"; this.description = "Converts a Windows Filetime value to a UNIX timestamp.

A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).

This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds."; this.infoURL = "https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Output units", "type": "option", "value": ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"] }, { "name": "Input format", "type": "option", "value": ["Decimal", "Hex (big endian)", "Hex (little endian)"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [units, format] = args; if (!input) return ""; if (format === "Hex (little endian)") { // Swap endianness let result = ""; if (input.length % 2 !== 0) { result += input.charAt(input.length - 1); } for (let i = input.length - input.length % 2 - 2; i >= 0; i -= 2) { result += input.charAt(i); result += input.charAt(i + 1); } input = result; } if (format.startsWith("Hex")) { input = new BigNumber(input, 16); } else { input = new BigNumber(input); } input = input.minus(new BigNumber("116444736000000000")); if (units === "Seconds (s)") { input = input.dividedBy(new BigNumber("10000000")); } else if (units === "Milliseconds (ms)") { input = input.dividedBy(new BigNumber("10000")); } else if (units === "Microseconds (μs)") { input = input.dividedBy(new BigNumber("10")); } else if (units === "Nanoseconds (ns)") { input = input.multipliedBy(new BigNumber("100")); } else { throw new OperationError("Unrecognised unit"); } return input.toFixed(); } } export default WindowsFiletimeToUNIXTimestamp; ================================================ FILE: src/core/operations/XKCDRandomNumber.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; /** * XKCD Random Number operation */ class XKCDRandomNumber extends Operation { /** * XKCDRandomNumber constructor */ constructor() { super(); this.name = "XKCD Random Number"; this.module = "Default"; this.description = "RFC 1149.5 specifies 4 as the standard IEEE-vetted random number."; this.infoURL = "https://xkcd.com/221/"; this.inputType = "string"; this.outputType = "number"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {number} */ run(input, args) { return 4; // chosen by fair dice roll. // guaranteed to be random. } } export default XKCDRandomNumber; ================================================ FILE: src/core/operations/XMLBeautify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import vkbeautify from "vkbeautify"; import Operation from "../Operation.mjs"; /** * XML Beautify operation */ class XMLBeautify extends Operation { /** * XMLBeautify constructor */ constructor() { super(); this.name = "XML Beautify"; this.module = "Code"; this.description = "Indents and prettifies eXtensible Markup Language (XML) code."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Indent string", "type": "binaryShortString", "value": "\\t" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const indentStr = args[0]; return vkbeautify.xml(input, indentStr); } } export default XMLBeautify; ================================================ FILE: src/core/operations/XMLMinify.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import vkbeautify from "vkbeautify"; import Operation from "../Operation.mjs"; /** * XML Minify operation */ class XMLMinify extends Operation { /** * XMLMinify constructor */ constructor() { super(); this.name = "XML Minify"; this.module = "Code"; this.description = "Compresses eXtensible Markup Language (XML) code."; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Preserve comments", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const preserveComments = args[0]; return vkbeautify.xmlmin(input, preserveComments); } } export default XMLMinify; ================================================ FILE: src/core/operations/XOR.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { bitOp, xor, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; /** * XOR operation */ class XOR extends Operation { /** * XOR constructor */ constructor() { super(); this.name = "XOR"; this.module = "Default"; this.description = "XOR the input with the given key.
e.g. fe023da5

Options
Null preserving: If the current byte is 0x00 or the same as the key, skip it.

Scheme:
  • Standard - key is unchanged after each round
  • Input differential - key is set to the value of the previous unprocessed byte
  • Output differential - key is set to the value of the previous processed byte
  • Cascade - key is set to the input byte shifted by one
"; this.infoURL = "https://wikipedia.org/wiki/XOR"; this.inputType = "ArrayBuffer"; this.outputType = "byteArray"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": BITWISE_OP_DELIMS }, { "name": "Scheme", "type": "option", "value": ["Standard", "Input differential", "Output differential", "Cascade"] }, { "name": "Null preserving", "type": "boolean", "value": false } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {byteArray} */ run(input, args) { input = new Uint8Array(input); const key = Utils.convertToByteArray(args[0].string || "", args[0].option), [, scheme, nullPreserving] = args; return bitOp(input, key, xor, nullPreserving, scheme); } /** * Highlight XOR * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { return pos; } /** * Highlight XOR in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { return pos; } } export default XOR; ================================================ FILE: src/core/operations/XORBruteForce.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { bitOp, xor } from "../lib/BitwiseOp.mjs"; import { toHex } from "../lib/Hex.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * XOR Brute Force operation */ class XORBruteForce extends Operation { /** * XORBruteForce constructor */ constructor() { super(); this.name = "XOR Brute Force"; this.module = "Default"; this.description = "Enumerate all possible XOR solutions. Current maximum key length is 2 due to browser performance.

Optionally enter a string that you expect to find in the plaintext to filter results (crib)."; this.infoURL = "https://wikipedia.org/wiki/Exclusive_or"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { "name": "Key length", "type": "number", "value": 1 }, { "name": "Sample length", "type": "number", "value": 100 }, { "name": "Sample offset", "type": "number", "value": 0 }, { "name": "Scheme", "type": "option", "value": ["Standard", "Input differential", "Output differential"] }, { "name": "Null preserving", "type": "boolean", "value": false }, { "name": "Print key", "type": "boolean", "value": true }, { "name": "Output as hex", "type": "boolean", "value": false }, { "name": "Crib (known plaintext string)", "type": "binaryString", "value": "" } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { input = new Uint8Array(input); const [ keyLength, sampleLength, sampleOffset, scheme, nullPreserving, printKey, outputHex, rawCrib ] = args, crib = rawCrib.toLowerCase(), output = []; let result, resultUtf8, record = ""; input = input.slice(sampleOffset, sampleOffset + sampleLength); if (isWorkerEnvironment()) self.sendStatusMessage("Calculating " + Math.pow(256, keyLength) + " values..."); /** * Converts an integer to an array of bytes expressing that number. * * @param {number} int * @param {number} len - Length of the resulting array * @returns {array} */ const intToByteArray = (int, len) => { const res = Array(len).fill(0); for (let i = len - 1; i >= 0; i--) { res[i] = int & 0xff; int = int >>> 8; } return res; }; for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) { if (key % 10000 === 0 && isWorkerEnvironment()) { self.sendStatusMessage("Calculating " + l + " values... " + Math.floor(key / l * 100) + "%"); } result = bitOp(input, intToByteArray(key, keyLength), xor, nullPreserving, scheme); resultUtf8 = Utils.byteArrayToUtf8(result); record = ""; if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue; if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; record += outputHex ? toHex(result) : Utils.escapeWhitespace(resultUtf8); output.push(record); } return output.join("\n"); } } export default XORBruteForce; ================================================ FILE: src/core/operations/XORChecksum.mjs ================================================ /** * @author Thomas Weißschuh [thomas@t-8ch.de] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import { toHex } from "../lib/Hex.mjs"; /** * XOR Checksum operation */ class XORChecksum extends Operation { /** * XORChecksum constructor */ constructor() { super(); this.name = "XOR Checksum"; this.module = "Crypto"; this.description = "XOR Checksum splits the input into blocks of a configurable size and performs the XOR operation on these blocks."; this.infoURL = "https://wikipedia.org/wiki/XOR"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Blocksize", type: "number", value: 4 }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { const blocksize = args[0]; input = new Uint8Array(input); const res = Array(blocksize); res.fill(0); for (const chunk of Utils.chunked(input, blocksize)) { for (let i = 0; i < blocksize; i++) { res[i] ^= chunk[i]; } } return toHex(res, ""); } } export default XORChecksum; ================================================ FILE: src/core/operations/XPathExpression.mjs ================================================ /** * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import xmldom from "@xmldom/xmldom"; import xpath from "xpath"; /** * XPath expression operation */ class XPathExpression extends Operation { /** * XPathExpression constructor */ constructor() { super(); this.name = "XPath expression"; this.module = "Code"; this.description = "Extract information from an XML document with an XPath query"; this.infoURL = "https://wikipedia.org/wiki/XPath"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "XPath", "type": "string", "value": "" }, { "name": "Result delimiter", "type": "binaryShortString", "value": "\\n" } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const [query, delimiter] = args; let doc; try { doc = new xmldom.DOMParser({ errorHandler: { fatalError(e) { throw e; } } }).parseFromString(input, "application/xml"); } catch (err) { throw new OperationError("Invalid input XML."); } let nodes; try { nodes = xpath.parse(query).select({ node: doc, allowAnyNamespaceForNoPrefix: true }); } catch (err) { throw new OperationError(`Invalid XPath. Details:\n${err.message}.`); } const nodeToString = function(node) { return node.toString(); }; return nodes.map(nodeToString).join(delimiter); } } export default XPathExpression; ================================================ FILE: src/core/operations/XSalsa20.mjs ================================================ /** * @author joostrijneveld [joost@joostrijneveld.nl] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; import { toHex } from "../lib/Hex.mjs"; import { salsa20Block, hsalsa20 } from "../lib/Salsa20.mjs"; /** * XSalsa20 operation */ class XSalsa20 extends Operation { /** * XSalsa20 constructor */ constructor() { super(); this.name = "XSalsa20"; this.module = "Ciphers"; this.description = "XSalsa20 is a variant of the Salsa20 stream cipher designed by Daniel J. Bernstein; XSalsa uses longer nonces.

Key: XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).

Nonce: XSalsa20 uses a nonce of 24 bytes (192 bits).

Counter: 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."; this.infoURL = "https://en.wikipedia.org/wiki/Salsa20#XSalsa20_with_192-bit_nonce"; this.inputType = "string"; this.outputType = "string"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, { "name": "Nonce", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"] }, { "name": "Counter", "type": "number", "value": 0, "min": 0 }, { "name": "Rounds", "type": "option", "value": ["20", "12", "8"] }, { "name": "Input", "type": "option", "value": ["Hex", "Raw"] }, { "name": "Output", "type": "option", "value": ["Raw", "Hex"] } ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), nonceType = args[1].option, rounds = parseInt(args[3], 10), inputType = args[4], outputType = args[5]; if (key.length !== 16 && key.length !== 32) { throw new OperationError(`Invalid key length: ${key.length} bytes. XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`); } let counter, nonce; if (nonceType === "Integer") { nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little"); } else { nonce = Utils.convertToByteArray(args[1].string, args[1].option); if (!(nonce.length === 24)) { throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. XSalsa20 uses a nonce of 24 bytes (192 bits).`); } } counter = Utils.intToByteArray(args[2], 8, "little"); const xsalsaKey = hsalsa20(key, nonce.slice(0, 16), rounds); const output = []; input = Utils.convertToByteArray(input, inputType); let counterAsInt = Utils.byteArrayToInt(counter, "little"); for (let i = 0; i < input.length; i += 64) { counter = Utils.intToByteArray(counterAsInt, 8, "little"); const stream = salsa20Block(xsalsaKey, nonce.slice(16, 24), counter, rounds); for (let j = 0; j < 64 && i + j < input.length; j++) { output.push(input[i + j] ^ stream[j]); } counterAsInt++; } if (outputType === "Hex") { return toHex(output); } else { return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); } } /** * Highlight XSalsa20 * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlight(pos, args) { const inputType = args[4], outputType = args[5]; if (inputType === "Raw" && outputType === "Raw") { return pos; } } /** * Highlight XSalsa20 in reverse * * @param {Object[]} pos * @param {number} pos[].start * @param {number} pos[].end * @param {Object[]} args * @returns {Object[]} pos */ highlightReverse(pos, args) { const inputType = args[4], outputType = args[5]; if (inputType === "Raw" && outputType === "Raw") { return pos; } } } export default XSalsa20; ================================================ FILE: src/core/operations/XXTEADecrypt.mjs ================================================ /** * @author devcydo [devcydo@gmail.com] * @author Ma Bingyao [mabingyao@gmail.com] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; import {decrypt} from "../lib/XXTEA.mjs"; /** * XXTEA Decrypt operation */ class XXTEADecrypt extends Operation { /** * XXTEADecrypt constructor */ constructor() { super(); this.name = "XXTEA Decrypt"; this.module = "Ciphers"; 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."; this.infoURL = "https://wikipedia.org/wiki/XXTEA"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option)); try { return decrypt(new Uint8Array(input), key).buffer; } catch (err) { throw new OperationError("Unable to decrypt using this key"); } } } export default XXTEADecrypt; ================================================ FILE: src/core/operations/XXTEAEncrypt.mjs ================================================ /** * @author devcydo [devcydo@gmail.com] * @author Ma Bingyao [mabingyao@gmail.com] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {encrypt} from "../lib/XXTEA.mjs"; /** * XXTEA Encrypt operation */ class XXTEAEncrypt extends Operation { /** * XXTEAEncrypt constructor */ constructor() { super(); this.name = "XXTEA Encrypt"; this.module = "Ciphers"; 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."; this.infoURL = "https://wikipedia.org/wiki/XXTEA"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { "name": "Key", "type": "toggleString", "value": "", "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ run(input, args) { const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option)); return encrypt(new Uint8Array(input), key).buffer; } } export default XXTEAEncrypt; ================================================ FILE: src/core/operations/YAMLToJSON.mjs ================================================ /** * @author ccarpo [ccarpo@gmx.net] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import jsYaml from "js-yaml"; /** * YAML to JSON operation */ class YAMLToJSON extends Operation { /** * YAMLToJSON constructor */ constructor() { super(); this.name = "YAML to JSON"; this.module = "Default"; this.description = "Convert YAML to JSON"; this.infoURL = "https://en.wikipedia.org/wiki/YAML"; this.inputType = "string"; this.outputType = "JSON"; this.args = []; } /** * @param {string} input * @param {Object[]} args * @returns {JSON} */ run(input, args) { try { return jsYaml.load(input); } catch (err) { throw new OperationError("Unable to parse YAML: " + err); } } } export default YAMLToJSON; ================================================ FILE: src/core/operations/YARARules.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Yara from "libyara-wasm"; import { isWorkerEnvironment } from "../Utils.mjs"; /** * YARA Rules operation */ class YARARules extends Operation { /** * YARARules constructor */ constructor() { super(); this.name = "YARA Rules"; this.module = "Yara"; this.description = "YARA is a tool developed at VirusTotal, primarily aimed at helping malware researchers to identify and classify malware samples. It matches based on rules specified by the user containing textual or binary patterns and a boolean expression. For help on writing rules, see the YARA documentation."; this.infoURL = "https://wikipedia.org/wiki/YARA"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { name: "Rules", type: "text", value: "", rows: 5 }, { name: "Show strings", type: "boolean", value: false }, { name: "Show string lengths", type: "boolean", value: false }, { name: "Show metadata", type: "boolean", value: false }, { name: "Show counts", type: "boolean", value: true }, { name: "Show rule warnings", type: "boolean", value: true }, { name: "Show console module messages", type: "boolean", value: true }, ]; } /** * @param {string} input * @param {Object[]} args * @returns {string} */ async run(input, args) { if (isWorkerEnvironment()) self.sendStatusMessage("Instantiating YARA..."); const [rules, showStrings, showLengths, showMeta, showCounts, showRuleWarns, showConsole] = args; return new Promise((resolve, reject) => { Yara().then(yara => { if (isWorkerEnvironment()) self.sendStatusMessage("Converting data for YARA."); let matchString = ""; const inpArr = new Uint8Array(input); // Turns out embind knows that JS uint8array <==> C++ std::string if (isWorkerEnvironment()) self.sendStatusMessage("Running YARA matching."); const resp = yara.run(inpArr, rules); if (isWorkerEnvironment()) self.sendStatusMessage("Processing data."); if (resp.compileErrors.size() > 0) { for (let i = 0; i < resp.compileErrors.size(); i++) { const compileError = resp.compileErrors.get(i); if (!compileError.warning) { reject(new OperationError(`Error on line ${compileError.lineNumber}: ${compileError.message}`)); } else if (showRuleWarns) { matchString += `Warning on line ${compileError.lineNumber}: ${compileError.message}\n`; } } } if (showConsole) { const consoleLogs = resp.consoleLogs; for (let i = 0; i < consoleLogs.size(); i++) { matchString += consoleLogs.get(i) + "\n"; } } const matchedRules = resp.matchedRules; for (let i = 0; i < matchedRules.size(); i++) { const rule = matchedRules.get(i); const matches = rule.resolvedMatches; let meta = ""; if (showMeta && rule.metadata.size() > 0) { meta += " ["; for (let j = 0; j < rule.metadata.size(); j++) { meta += `${rule.metadata.get(j).identifier}: ${rule.metadata.get(j).data}, `; } meta = meta.slice(0, -2) + "]"; } const countString = matches.size() === 0 ? "" : (showCounts ? ` (${matches.size()} time${matches.size() > 1 ? "s" : ""})` : ""); if (matches.size() === 0 || !(showStrings || showLengths)) { matchString += `Input matches rule "${rule.ruleName}"${meta}${countString.length > 0 ? ` ${countString}`: ""}.\n`; } else { matchString += `Rule "${rule.ruleName}"${meta} matches${countString}:\n`; for (let j = 0; j < matches.size(); j++) { const match = matches.get(j); if (showStrings || showLengths) { matchString += `Pos ${match.location}, ${showLengths ? `length ${match.matchLength}, ` : ""}identifier ${match.stringIdentifier}${showStrings ? `, data: "${match.data}"` : ""}\n`; } } } } resolve(matchString); }); }); } } export default YARARules; ================================================ FILE: src/core/operations/Zip.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; import zip from "zlibjs/bin/zip.min.js"; const Zlib = zip.Zlib; const ZIP_COMPRESSION_METHOD_LOOKUP = { "Deflate": Zlib.Zip.CompressionMethod.DEFLATE, "None (Store)": Zlib.Zip.CompressionMethod.STORE }; const ZIP_OS_LOOKUP = { "MSDOS": Zlib.Zip.OperatingSystem.MSDOS, "Unix": Zlib.Zip.OperatingSystem.UNIX, "Macintosh": Zlib.Zip.OperatingSystem.MACINTOSH }; /** * Zip operation */ class Zip extends Operation { /** * Zip constructor */ constructor() { super(); this.name = "Zip"; this.module = "Compression"; this.description = "Compresses data using the PKZIP algorithm with the given filename.

No support for multiple files at this time."; this.infoURL = "https://wikipedia.org/wiki/Zip_(file_format)"; this.inputType = "ArrayBuffer"; this.outputType = "File"; this.args = [ { name: "Filename", type: "string", value: "file.txt" }, { name: "Comment", type: "string", value: "" }, { name: "Password", type: "binaryString", value: "" }, { name: "Compression method", type: "option", value: ["Deflate", "None (Store)"] }, { name: "Operating system", type: "option", value: ["MSDOS", "Unix", "Macintosh"] }, { name: "Compression type", type: "option", value: COMPRESSION_TYPE } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {File} */ run(input, args) { const filename = args[0], password = Utils.strToByteArray(args[2]), options = { filename: Utils.strToByteArray(filename), comment: Utils.strToByteArray(args[1]), compressionMethod: ZIP_COMPRESSION_METHOD_LOOKUP[args[3]], os: ZIP_OS_LOOKUP[args[4]], deflateOption: { compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]] }, }, zip = new Zlib.Zip(); if (password.length) zip.setPassword(password); zip.addFile(new Uint8Array(input), options); return new File([zip.compress()], filename); } } export default Zip; ================================================ FILE: src/core/operations/ZlibDeflate.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min.js"; const Zlib = zlibAndGzip.Zlib; /** * Zlib Deflate operation */ class ZlibDeflate extends Operation { /** * ZlibDeflate constructor */ constructor() { super(); this.name = "Zlib Deflate"; this.module = "Compression"; this.description = "Compresses data using the deflate algorithm adding zlib headers."; this.infoURL = "https://wikipedia.org/wiki/Zlib"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { name: "Compression type", type: "option", value: COMPRESSION_TYPE } ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const deflate = new Zlib.Deflate(new Uint8Array(input), { compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] }); return new Uint8Array(deflate.compress()).buffer; } } export default ZlibDeflate; ================================================ FILE: src/core/operations/ZlibInflate.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import {INFLATE_BUFFER_TYPE} from "../lib/Zlib.mjs"; import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min.js"; const Zlib = zlibAndGzip.Zlib; const ZLIB_BUFFER_TYPE_LOOKUP = { "Adaptive": Zlib.Inflate.BufferType.ADAPTIVE, "Block": Zlib.Inflate.BufferType.BLOCK, }; /** * Zlib Inflate operation */ class ZlibInflate extends Operation { /** * ZlibInflate constructor */ constructor() { super(); this.name = "Zlib Inflate"; this.module = "Compression"; this.description = "Decompresses data which has been compressed using the deflate algorithm with zlib headers."; this.infoURL = "https://wikipedia.org/wiki/Zlib"; this.inputType = "ArrayBuffer"; this.outputType = "ArrayBuffer"; this.args = [ { name: "Start index", type: "number", value: 0 }, { name: "Initial output buffer size", type: "number", value: 0 }, { name: "Buffer expansion type", type: "option", value: INFLATE_BUFFER_TYPE }, { name: "Resize buffer after decompression", type: "boolean", value: false }, { name: "Verify result", type: "boolean", value: false } ]; this.checks = [ { pattern: "^\\x78(\\x01|\\x9c|\\xda|\\x5e)", flags: "", args: [0, 0, "Adaptive", false, false] }, ]; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} */ run(input, args) { const inflate = new Zlib.Inflate(new Uint8Array(input), { index: args[0], bufferSize: args[1], bufferType: ZLIB_BUFFER_TYPE_LOOKUP[args[2]], resize: args[3], verify: args[4] }); return new Uint8Array(inflate.decompress()).buffer; } } export default ZlibInflate; ================================================ FILE: src/core/vendor/DisassembleX86-64.mjs ================================================ /*------------------------------------------------------------------------------------------------------------------------- Created by Damian Recoskie (https://github.com/Recoskie/X86-64-Disassembler-JS) & exported for CyberChef by Matt [me@mitt.dev] --------------------------------------------------------------------------------------------------------------------------- MIT License Copyright (c) 2019 Damian Recoskie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------- Binary byte code array. --------------------------------------------------------------------------------------------------------------------------- Function ^LoadBinCode()^ takes a string input of hex and loads it into the BinCode array it is recommended that the location the hex string is read from a file, or sector matches the disassemblers set base address using function ^SetBasePosition()^. -------------------------------------------------------------------------------------------------------------------------*/ var BinCode = []; /*------------------------------------------------------------------------------------------------------------------------- When 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. -------------------------------------------------------------------------------------------------------------------------*/ var BitMode = 2; /*------------------------------------------------------------------------------------------------------------------------- The variable CodePos is the position in the BinCode array starts at 0 for each new section loaded in by ^LoadBinCode()^. --------------------------------------------------------------------------------------------------------------------------- The function ^NextByte()^ moves CodePos, and the Disassemblers Base address by one stored in Pos64, Pos32. The 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. -------------------------------------------------------------------------------------------------------------------------*/ var CodePos = 0x00000000; /*------------------------------------------------------------------------------------------------------------------------- The Pos64, and Pos32 is the actual base address that instructions are supposed to be from in memory when they are loaded into the BinCode array using the Function ^LoadBinCode()^. --------------------------------------------------------------------------------------------------------------------------- The function ^SetBasePosition()^ sets the base location in Pos64, and Pos32, and Code Segment. -------------------------------------------------------------------------------------------------------------------------*/ var Pos64 = 0x00000000, Pos32 = 0x00000000; /*------------------------------------------------------------------------------------------------------------------------- Code Segment is used in 16 bit binaries in which the segment is times 16 (Left Shift 4) added to the 16 bit address position. This was done to load more programs in 16 bit space at an selected segment location. In 16 bit X86 processors the instruction pointer register counts from 0000 hex to FFFF hex and starts over at 0000 hex. Allowing a program to be a max length of 65535 bytes long. The Code Segment is multiplied by 16 then is added to the instruction pointer position in memory. --------------------------------------------------------------------------------------------------------------------------- In 32 bit, and 64 bit the address combination is large enough that segmented program loading was no longer required. However 32 bit still supports Segmented addressing if used, but 64 bit binaries do not. Also if the code segment is set 36, or higher in 32 bit binaries this sets SEG:OFFSET address format for each instructions Memory position. --------------------------------------------------------------------------------------------------------------------------- In 64 bit mode, an programs instructions are in a 64 bit address using the processors full instruction pointer, but in 32 bit instructions the first 32 bit of the instruction pointer is used. In 16 bit the first 16 bits of the instruction pointer is used, but with the code segment. Each instruction is executed in order by the Instruction pointer that goes in sectional sizes "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. -------------------------------------------------------------------------------------------------------------------------*/ var CodeSeg = 0x0000; /*------------------------------------------------------------------------------------------------------------------------- The InstructionHex String stores the Bytes of decoded instructions. It is shown to the left side of the disassembled instruction. -------------------------------------------------------------------------------------------------------------------------*/ var InstructionHex = ""; /*------------------------------------------------------------------------------------------------------------------------- The InstructionPos String stores the start position of a decoded binary instruction in memory from the function ^GetPosition()^. -------------------------------------------------------------------------------------------------------------------------*/ var InstructionPos = ""; /*------------------------------------------------------------------------------------------------------------------------- Decoding display options. -------------------------------------------------------------------------------------------------------------------------*/ var ShowInstructionHex = true; //setting to show the hex code of the instruction beside the decoded instruction output. var ShowInstructionPos = true; //setting to show the instruction address position. /*------------------------------------------------------------------------------------------------------------------------- The Opcode, and Opcode map. --------------------------------------------------------------------------------------------------------------------------- The first 0 to 255 (Byte) value that is read is the selected instruction code, however some codes are used as Adjustment to remove limitations that are read by the function ^DecodePrefixAdjustments()^. --------------------------------------------------------------------------------------------------------------------------- Because X86 was limited to 255 instructions An number was sacrificed to add more instructions. By 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 new set of 0 to 255 instructions these are called escape code prefixes. --------------------------------------------------------------------------------------------------------------------------- Bellow XX is the opcode combined with the adjustment escape codes thus how opcode is used numerically in the disassembler. --------------------------------------------------------------------------------------------------------------------------- 00,00000000 = 0, lower 8 bit opcode at max 00,11111111 = 255. (First byte opcodes XX) Opcodes values 0 to 255. 01,00000000 = 256, lower 8 bit opcode at max 01,11111111 = 511. (Two byte opcodes 0F XX) Opcodes values 256 to 511. 10,00000000 = 512, lower 8 bit opcode at max 10,11111111 = 767. (Three byte opcodes 0F 38 XX) Opcodes values 512 to 767. 11,00000000 = 768, lower 8 bit opcode at max 11,11111111 = 1023. (Three byte opcodes 0F 3A XX) Opcodes values 768 to 1023. --------------------------------------------------------------------------------------------------------------------------- The 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. The vector adjustment codes contain an map bit selection the map bits go in order to the place holder map bits are in. This makes it so the map bits can be placed where the place holder bits are. --------------------------------------------------------------------------------------------------------------------------- VEX.mmmmm = 000_00b (1-byte map), 000_01b (2-byte map), 000_10b (0Fh,38h), 000_11b (0Fh,3Ah) EVEX.mm = 00b (1-byte map), 01b (2-byte map), 10b (0Fh,38h), 11b (0Fh,3Ah) -------------------------------------------------------------------------------------------------------------------------- Function ^DecodePrefixAdjustments()^ reads opcodes that act as settings it only ends when Opcode is an actual instruction code value 0 to 1023 inducing escape codes. Opcode is Used by function ^DecodeOpcode()^ with the Mnemonic array. -------------------------------------------------------------------------------------------------------------------------*/ var Opcode = 0; /*------------------------------------------------------------------------------------------------------------------------- Opcode is used as the index for the point in the structure to land on in the "Mnemonics". --------------------------------------------------------------------------------------------------------------------------- X86 has an amazing architectural pattern that is like an fractal in many ways. Previously an experiment was done to make this an one dimensional array, but after testing it proved that it was slower because each of the branches had to be calculated to an unique index in memory in which lots of combinations map to the same instructions well some changed. The calculation took more time than comparing if an index is an reference to another array to optionally use an encoding. --------------------------------------------------------------------------------------------------------------------------- The first branch is an array 2 in size which separates opcodes that change between register, and memory mode. --------------------------------------------------------------------------------------------------------------------------- The 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. The 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. --------------------------------------------------------------------------------------------------------------------------- The 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 any of the 4 elements in the SIMD modes for instructions that change by vector extension type. --------------------------------------------------------------------------------------------------------------------------- The fifth branch is an array 3 in size which branches to encoding's that change by the set size attribute. --------------------------------------------------------------------------------------------------------------------------- Each branch can be combined in any combination, but only in order. If we branch to an array 2 in size under an specific opcode like this ["",""] then decide to branch memory mode to an array 4 in size we end up with ["",["","","",""]] for making it only active 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 in 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 as 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 encoding. 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 in order from that branch point. --------------------------------------------------------------------------------------------------------------------------- Opcode is used by the function ^DecodeOpcode()^ after ^DecodePrefixAdjustments()^. The function ^DecodeOpcode()^ Gives back the instructions name. --------------------------------------------------------------------------------------------------------------------------*/ const Mnemonics = [ /*------------------------------------------------------------------------------------------------------------------------ First Byte operations 0 to 255. ------------------------------------------------------------------------------------------------------------------------*/ "ADD","ADD","ADD","ADD","ADD","ADD","PUSH ES","POP ES", "OR","OR","OR","OR","OR","OR","PUSH CS" , "" //*Two byte instructions prefix sets opcode 01,000000000 next byte read is added to the lower 8 bit's. , "ADC","ADC","ADC","ADC","ADC","ADC","PUSH SS","POP SS", "SBB","SBB","SBB","SBB","SBB","SBB","PUSH DS","POP DS", "AND","AND","AND","AND","AND","AND", "ES:[", //Extra segment override sets SegOveride "ES:[". "DAA", "SUB","SUB","SUB","SUB","SUB","SUB", "CS:[", //Code segment override sets SegOveride "CS:[". "DAS", "XOR","XOR","XOR","XOR","XOR","XOR", "SS:[", //Stack segment override sets SegOveride "SS:[". "AAA", "CMP","CMP","CMP","CMP","CMP","CMP", "DS:[", //Data Segment override sets SegOveride "DS:[". "AAS", /*------------------------------------------------------------------------------------------------------------------------ Start of Rex Prefix adjustment setting uses opcodes 40 to 4F. These opcodes are only decoded as adjustment settings by the function ^DecodePrefixAdjustments()^ while in 64 bit mode. If not in 64 bit mode the codes are not read by the function ^DecodePrefixAdjustments()^ which allows the opcode to be set 40 to 4F hex in which the defined instructions bellow are used by ^DecodeOpcode()^. ------------------------------------------------------------------------------------------------------------------------*/ "INC","INC","INC","INC","INC","INC","INC","INC", "DEC","DEC","DEC","DEC","DEC","DEC","DEC","DEC", /*------------------------------------------------------------------------------------------------------------------------ End of the Rex Prefix adjustment setting opcodes. ------------------------------------------------------------------------------------------------------------------------*/ "PUSH","PUSH","PUSH","PUSH","PUSH","PUSH","PUSH","PUSH", "POP","POP","POP","POP","POP","POP","POP","POP", ["PUSHA","PUSHAD",""],["POPA","POPAD",""], ["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. "MOVSXD", "FS:[","GS:[", //Sets SegOveride "FS:[" next opcode sets "GS:[". "","", //Operand Size, and Address size adjustment to ModR/M. "PUSH","IMUL","PUSH","IMUL", "INS","INS","OUTS","OUTS", "JO","JNO","JB","JAE","JE","JNE","JBE","JA", "JS","JNS","JP","JNP","JL","JGE","JLE","JG", ["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. ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], "TEST","TEST","XCHG","XCHG", "MOV","MOV","MOV","MOV","MOV", ["LEA","???"], //*ModR/M Register, and memory mode separation. "MOV", ["POP","???","???","???","???","???","???","???"], [["NOP","","",""],["NOP","","",""],["PAUSE","","",""],["NOP","","",""]], "XCHG","XCHG","XCHG","XCHG","XCHG","XCHG","XCHG", ["CWDE","CBW","CDQE"], //*Opcode 0 to 3 for instructions that change name by size setting. ["CDQ","CWD","CQO"], "CALL","WAIT", ["PUSHFQ","PUSHF","PUSHFQ"], ["POPFQ","POPF","POPFQ"], "SAHF","LAHF", "MOV","MOV","MOV","MOV", "MOVS","MOVS", "CMPS","CMPS", "TEST","TEST", "STOS","STOS", "LODS","LODS", "SCAS","SCAS", "MOV","MOV","MOV","MOV","MOV","MOV","MOV","MOV", "MOV","MOV","MOV","MOV","MOV","MOV","MOV","MOV", ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], "RET","RET", "LES", //VEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined instruction is used. "LDS", //VEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined instruction is used. [ "MOV","???","???","???","???","???","???", ["XABORT","XABORT","XABORT","XABORT","XABORT","XABORT","XABORT","XABORT"] ], [ "MOV","???","???","???","???","???","???", ["XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN"] ], "ENTER","LEAVE","RETF","RETF","INT","INT","INTO", ["IRETD","IRET","IRETQ"], ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], "AAMB","AADB","???", "XLAT", /*------------------------------------------------------------------------------------------------------------------------ X87 FPU. ------------------------------------------------------------------------------------------------------------------------*/ [ ["FADD","FMUL","FCOM","FCOMP","FSUB","FSUBR","FDIV","FDIVR"], ["FADD","FMUL","FCOM","FCOMP","FSUB","FSUBR","FDIV","FDIVR"] ], [ ["FLD","???","FST","FSTP","FLDENV","FLDCW","FNSTENV","FNSTCW"], [ "FLD","FXCH", ["FNOP","???","???","???","???","???","???","???"], "FSTP1", ["FCHS","FABS","???","???","FTST","FXAM","???","???"], ["FLD1","FLDL2T","FLDL2E","FLDPI","FLDLG2","FLDLN2","FLDZ","???"], ["F2XM1","FYL2X","FPTAN","FPATAN","FXTRACT","FPREM1","FDECSTP","FINCSTP"], ["FPREM","FYL2XP1","FSQRT","FSINCOS","FRNDINT","FSCALE","FSIN","FCOS"] ] ], [ ["FIADD","FIMUL","FICOM","FICOMP","FISUB","FISUBR","FIDIV","FIDIVR"], [ "FCMOVB","FCMOVE","FCMOVBE","FCMOVU","???", ["???","FUCOMPP","???","???","???","???","???","???"], "???","???" ] ], [ ["FILD","FISTTP","FIST","FISTP","???","FLD","???","FSTP"], [ "CMOVNB","FCMOVNE","FCMOVNBE","FCMOVNU", ["FENI","FDISI","FNCLEX","FNINIT","FSETPM","???","???","???"], "FUCOMI","FCOMI","???" ] ], [ ["FADD","FMUL","FCOM","DCOMP","FSUB","FSUBR","FDIV","FDIVR"], ["FADD","FMUL","FCOM2","FCOMP3","FSUBR","FSUB","FDIVR","FDIV"] ], [ ["FLD","FISTTP","FST","FSTP","FRSTOR","???","FNSAVE","FNSTSW"], ["FFREE","FXCH4","FST","FSTP","FUCOM","FUCOMP","???","???"] ], [ ["FIADD","FIMUL","FICOM","FICOMP","FISUB","FISUBR","FIDIV","FIDIVR"], [ "FADDP","FMULP","FCOMP5", ["???","FCOMPP","???","???","???","???","???","???"], "FSUBRP","FSUBP","FDIVRP","FDIVP" ] ], [ ["FILD","FISTTP","FIST","FISTP","FBLD","FILD","FBSTP","FISTP"], [ "FFREEP","FXCH7","FSTP8","FSTP9", ["FNSTSW","???","???","???","???","???","???","???"], "FUCOMIP","FCOMIP","???" ] ], /*------------------------------------------------------------------------------------------------------------------------ End of X87 FPU. ------------------------------------------------------------------------------------------------------------------------*/ "LOOPNE","LOOPE","LOOP","JRCXZ", "IN","IN","OUT","OUT", "CALL","JMP","JMP","JMP", "IN","IN","OUT","OUT", /*------------------------------------------------------------------------------------------------------------------------ The Repeat, and lock prefix opcodes apply to the next opcode. ------------------------------------------------------------------------------------------------------------------------*/ "LOCK", //Adds LOCK to the start of instruction. When Opcode F0 hex is read by function ^DecodePrefixAdjustments()^ sets PrefixG2 to LOCK. "ICEBP", //Instruction ICEBP. "REPNE", //Adds REPNE (Opcode F2 hex) to the start of instruction. Read by function ^DecodePrefixAdjustments()^ sets PrefixG1 to REPNE. "REP", //Adds REP (Opcode F3 hex) to the start of instruction. Read by function ^DecodePrefixAdjustments()^ sets PrefixG1 to REP. /*------------------------------------------------------------------------------------------------------------------------ End of Repeat, and lock instruction adjustment codes. ------------------------------------------------------------------------------------------------------------------------*/ "HLT","CMC", ["TEST","???","NOT","NEG","MUL","IMUL","DIV","IDIV"], ["TEST","???","NOT","NEG","MUL","IMUL","DIV","IDIV"], "CLC","STC","CLI","STI","CLD","STD", ["INC","DEC","???","???","???","???","???","???"], [ ["INC","DEC","CALL","CALL","JMP","JMP","PUSH","???"], ["INC","DEC","CALL","???","JMP","???","PUSH","???"] ], /*------------------------------------------------------------------------------------------------------------------------ Two Byte Opcodes 256 to 511. Opcodes plus 256 goes to 511 used by escape code "0F", Or set directly by adding map bits "01" because "01 00000000" bin = 256 plus opcode. ------------------------------------------------------------------------------------------------------------------------*/ [ ["SLDT","STR","LLDT","LTR","VERR","VERW","JMPE","???"], ["SLDT","STR","LLDT","LTR","VERR","VERW","JMPE","???"] ], [ ["SGDT","SIDT","LGDT","LIDT","SMSW","???","LMSW","INVLPG"], [ ["???","VMCALL","VMLAUNCH","VMRESUME","VMXOFF","???","???","???"], ["MONITOR","MWAIT","CLAC","STAC","???","???","???","ENCLS"], ["XGETBV","XSETBV","???","???","VMFUNC","XEND","XTEST","ENCLU"], ["VMRUN","VMMCALL","VMLOAD","VMSAVE","STGI","CLGI","SKINIT","INVLPGA"], "SMSW","???","LMSW", ["SWAPGS","RDTSCP","MONITORX","MWAITX","???","???","???","???"] ] ], ["LAR","LAR"],["LSL","LSL"],"???", "SYSCALL","CLTS","SYSRET","INVD", "WBINVD","???","UD2","???", [["PREFETCH","PREFETCHW","???","???","???","???","???","???"],"???"], "FEMMS", "", //3DNow Instruction name is encoded by the IMM8 operand. [ ["MOVUPS","MOVUPD","MOVSS","MOVSD"], ["MOVUPS","MOVUPD","MOVSS","MOVSD"] ], [ ["MOVUPS","MOVUPD","MOVSS","MOVSD"], ["MOVUPS","MOVUPD","MOVSS","MOVSD"] ], [ ["MOVLPS","MOVLPD","MOVSLDUP","MOVDDUP"], ["MOVHLPS","???","MOVSLDUP","MOVDDUP"] ], [["MOVLPS","MOVLPD","???","???"],"???"], ["UNPCKLPS","UNPCKLPD","???","???"], //An instruction with 4 operations uses the 4 SIMD modes as an Vector instruction. ["UNPCKHPS","UNPCKHPD","???","???"], [["MOVHPS","MOVHPD","MOVSHDUP","???"],["MOVLHPS","???","MOVSHDUP","???"]], [["MOVHPS","MOVHPD","???","???"],"???"], [["PREFETCHNTA","PREFETCHT0","PREFETCHT1","PREFETCHT2","???","???","???","???"],"???"], "???", [[["BNDLDX","","",""],["BNDMOV","","",""],["BNDCL","","",""],["BNDCU","","",""]], ["???",["BNDMOV","","",""],["BNDCL","","",""],["BNDCU","","",""]]], [[["BNDSTX","","",""],["BNDMOV","","",""],["BNDMK","","",""],["BNDCN","","",""]], ["???",["BNDMOV","","",""],"???",["BNDCN","","",""]]], "???","???","???", "NOP", ["???","MOV"],["???","MOV"], //CR and DR register Move ["???","MOV"],["???","MOV"], //CR and DR register Move ["???","MOV"],"???", //TR (TEST REGISTER) register Move ["???","MOV"],"???", //TR (TEST REGISTER) register Move [ ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], "???","???" ], [ [ ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], ["","","",["MOVNRAPS","MOVNRNGOAPS","MOVNRAPS"]], ["","","",["MOVNRAPD","MOVNRNGOAPD","MOVNRAPD"]] ], [ ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], "???","???" ] ], [ ["CVTPI2PS","","",""],["CVTPI2PD","","",""], //Is not allowed to be Vector encoded. "CVTSI2SS","CVTSI2SD" ], [ [ "MOVNTPS","MOVNTPD", ["MOVNTSS","","",""],["MOVNTSD","","",""] //SSE4a can not be vector encoded. ],"???" ], [ ["CVTTPS2PI","","",""],["CVTTPD2PI","","",""], //Is not allowed to be Vector encoded. "CVTTSS2SI","CVTTSD2SI" ], [ ["CVTPS2PI","","",""],["CVTPD2PI","","",""], //Is not allowed to be Vector encoded. "CVTSS2SI","CVTSD2SI" ], ["UCOMISS","UCOMISD","???","???"], ["COMISS","COMISD","???","???"], "WRMSR","RDTSC","RDMSR","RDPMC", "SYSENTER","SYSEXIT","???", "GETSEC", "", //*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. "???", "", //*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. "???","???","???","???","???", "CMOVO", [ ["CMOVNO",["KANDW","","KANDQ"],"",""], ["CMOVNO",["KANDB","","KANDD"],"",""],"","" ], [ ["CMOVB",["KANDNW","","KANDNQ"],"",""], ["CMOVB",["KANDNB","","KANDND"],"",""],"","" ], [["CMOVAE","KANDNR","",""],"","",""], [ ["CMOVE",["KNOTW","","KNOTQ"],"",""], ["CMOVE",["KNOTB","","KNOTD"],"",""],"","" ], [ ["CMOVNE",["KORW","","KORQ"],"",""], ["CMOVNE",["KORB","","KORD"],"",""],"","" ], [ ["CMOVBE",["KXNORW","","KXNORQ"],"",""], ["CMOVBE",["KXNORB","","KXNORD"],"",""],"","" ], [ ["CMOVA",["KXORW","","KXORQ"],"",""], ["CMOVA",["KXORB","","KXORD"],"",""],"","" ], [["CMOVS","KMERGE2L1H","",""],"","",""], [["CMOVNS","KMERGE2L1L","",""],"","",""], [ ["CMOVP",["KADDW","","KADDQ"],"",""], ["CMOVP",["KADDB","","KADDD"],"",""],"","" ], [ ["CMOVNP",["KUNPCKWD","","KUNPCKDQ"],"",""], ["CMOVNP",["KUNPCKBW","","???"],"",""],"","" ], "CMOVL","CMOVGE","CMOVLE","CMOVG", [ "???", [ ["MOVMSKPS","MOVMSKPS","",""],["MOVMSKPD","MOVMSKPD","",""], "???","???" ] ], ["SQRTPS","SQRTPD","SQRTSS","SQRTSD"], [ ["RSQRTPS","RSQRTPS","",""],"???", ["RSQRTSS","RSQRTSS","",""],"???" ], [ ["RCPPS","RCPPS","",""],"???", ["RCPSS","RCPSS","",""],"???" ], ["ANDPS","ANDPD","???","???"], ["ANDNPS","ANDNPD","???","???"], ["ORPS","ORPD","???","???"], ["XORPS","XORPD","???","???"], [ ["ADDPS","ADDPS","ADDPS","ADDPS"], ["ADDPD","ADDPD","ADDPD","ADDPD"], "ADDSS","ADDSD" ], [ ["MULPS","MULPS","MULPS","MULPS"], ["MULPD","MULPD","MULPD","MULPD"], "MULSS","MULSD" ], [ ["CVTPS2PD","CVTPS2PD","CVTPS2PD","CVTPS2PD"], ["CVTPD2PS","CVTPD2PS","CVTPD2PS","CVTPD2PS"], "CVTSS2SD","CVTSD2SS" ], [["CVTDQ2PS","","CVTQQ2PS"],["CVTPS2DQ","","???"],"CVTTPS2DQ","???"], [ ["SUBPS","SUBPS","SUBPS","SUBPS"], ["SUBPD","SUBPD","SUBPD","SUBPD"], "SUBSS","SUBSD" ], ["MINPS","MINPD","MINSS","MINSD"], ["DIVPS","DIVPD","DIVSS","DIVSD"], ["MAXPS","MAXPD","MAXSS","MAXSD"], [["PUNPCKLBW","","",""],"PUNPCKLBW","",""], [["PUNPCKLWD","","",""],"PUNPCKLWD","",""], [["PUNPCKLDQ","","",""],"PUNPCKLDQ","",""], [["PACKSSWB","","",""],"PACKSSWB","",""], [["PCMPGTB","","",""],["PCMPGTB","PCMPGTB","PCMPGTB",""],"",""], [["PCMPGTW","","",""],["PCMPGTW","PCMPGTW","PCMPGTW",""],"",""], [["PCMPGTD","","",""],["PCMPGTD","PCMPGTD",["PCMPGTD","","???"],["PCMPGTD","","???"]],"",""], [["PACKUSWB","","",""],"PACKUSWB","",""], [["PUNPCKHBW","","",""],"PUNPCKHBW","",""], [["PUNPCKHWD","","",""],"PUNPCKHWD","",""], [["PUNPCKHDQ","","",""],["PUNPCKHDQ","","???"],"",""], [["PACKSSDW","","",""],["PACKSSDW","","???"],"",""], ["???","PUNPCKLQDQ","???","???"], ["???","PUNPCKHQDQ","???","???"], [["MOVD","","",""],["MOVD","","MOVQ"],"",""], [ [ ["MOVQ","","",""], ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], ["","",["MOVDQU8","","MOVDQU16"],""] ], [ ["MOVQ","","",""], ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], ["","",["MOVDQU8","","MOVDQU16"],""] ] ], [ ["PSHUFW","","",""], ["PSHUFD","PSHUFD",["PSHUFD","","???"],["PSHUFD","","???"]], "PSHUFHW", "PSHUFLW" ], [ "???", [ "???","???", [["PSRLW","","",""],"PSRLW","",""],"???", [["PSRAW","","",""],"PSRAW","",""],"???", [["PSLLW","","",""],"PSLLW","",""],"???" ] ], [ ["???",["","",["PRORD","","PRORQ"],""],"???","???"], ["???",["","",["PROLD","","PROLQ"],""],"???","???"], [["PSRLD","","",""],["PSRLD","PSRLD",["PSRLD","","???"],["PSRLD","","???"]],"",""], "???", [["PSRAD","","",""],["PSRAD","PSRAD",["PSRAD","","PSRAQ"],["PSRAD","","???"]],"",""], "???", [["PSLLD","","",""],["PSLLD","PSLLD",["PSLLD","","???"],["PSLLD","","???"]],"",""], "???" ], [ "???", [ "???","???", [["PSRLQ","PSRLQ","",""],"PSRLQ","",""],["???","PSRLDQ","???","???"], "???","???", [["PSLLQ","PSLLQ","",""],"PSLLQ","",""],["???","PSLLDQ","???","???"] ] ], [["PCMPEQB","","",""],["PCMPEQB","PCMPEQB","PCMPEQB",""],"",""], [["PCMPEQW","","",""],["PCMPEQW","PCMPEQW","PCMPEQW",""],"",""], [["PCMPEQD","","",""],["PCMPEQD","PCMPEQD",["PCMPEQD","","???"],["PCMPEQD","","???"]],"",""], [["EMMS",["ZEROUPPER","ZEROALL",""],"",""],"???","???","???"], [ ["VMREAD","",["CVTTPS2UDQ","","CVTTPD2UDQ"],""], ["EXTRQ","",["CVTTPS2UQQ","","CVTTPD2UQQ"],""], ["???","","CVTTSS2USI",""], ["INSERTQ","","CVTTSD2USI",""] ], [ ["VMWRITE","",["CVTPS2UDQ","","CVTPD2UDQ"], ""], ["EXTRQ","",["CVTPS2UQQ","","CVTPD2UQQ"],""], ["???","","CVTSS2USI",""], ["INSERTQ","","CVTSD2USI",""] ], [ "???", ["","",["CVTTPS2QQ","","CVTTPD2QQ"],""], ["","",["CVTUDQ2PD","","CVTUQQ2PD"],"CVTUDQ2PD"], ["","",["CVTUDQ2PS","","CVTUQQ2PS"],""] ], [ "???", ["","",["CVTPS2QQ","","CVTPD2QQ"],""], ["","","CVTUSI2SS",""], ["","","CVTUSI2SD",""] ], [ "???",["HADDPD","HADDPD","",""], "???",["HADDPS","HADDPS","",""] ], [ "???",["HSUBPD","HSUBPD","",""], "???",["HSUBPS","HSUBPS","",""] ], [["MOVD","","",""],["MOVD","","MOVQ"],["MOVQ","MOVQ",["???","","MOVQ"],""],"???"], [ ["MOVQ","","",""], ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], ["???","",["MOVDQU8","","MOVDQU16"],""] ], "JO","JNO","JB","JAE", [["JE","JKZD","",""],"","",""],[["JNE","JKNZD","",""],"","",""], //K1OM. "JBE","JA","JS","JNS","JP","JNP","JL","JGE","JLE","JG", [ ["SETO",["KMOVW","","KMOVQ"],"",""], ["SETO",["KMOVB","","KMOVD"],"",""],"","" ], [ ["SETNO",["KMOVW","","KMOVQ"],"",""], ["SETNO",["KMOVB","","KMOVD"],"",""],"","" ], [ ["SETB",["KMOVW","","???"],"",""], ["SETB",["KMOVB","","???"],"",""],"", ["SETB",["KMOVD","","KMOVQ"],"",""] ], [ ["SETAE",["KMOVW","","???"],"",""], ["SETAE",["KMOVB","","???"],"",""],"", ["SETAE",["KMOVD","","KMOVQ"],"",""] ], "SETE",[["SETNE","KCONCATH","",""],"","",""], "SETBE",[["SETA","KCONCATL","",""],"","",""], [ ["SETS",["KORTESTW","","KORTESTQ"],"",""], ["SETS",["KORTESTB","","KORTESTD"],"",""],"","" ], [ ["SETNS",["KTESTW","","KTESTQ"],"",""], ["SETNS",["KTESTB","","KTESTD"],"",""],"","" ], "SETP","SETNP","SETL","SETGE","SETLE","SETG", "PUSH","POP", "CPUID", //Identifies the CPU and which Instructions the current CPU can use. "BT", "SHLD","SHLD", "XBTS","IBTS", "PUSH","POP", "RSM", "BTS", "SHRD","SHRD", [ [ ["FXSAVE","???","FXSAVE64"],["FXRSTOR","???","FXRSTOR64"], "LDMXCSR","STMXCSR", ["XSAVE","","XSAVE64"],["XRSTOR","","XRSTOR64"], ["XSAVEOPT","CLWB","XSAVEOPT64"], ["CLFLUSHOPT","CLFLUSH",""] ], [ ["???","???",["RDFSBASE","","",""],"???"],["???","???",["RDGSBASE","","",""],"???"], ["???","???",["WRFSBASE","","",""],"???"],["???","???",["WRGSBASE","","",""],"???"], "???", ["LFENCE","???","???","???","???","???","???","???"], ["MFENCE","???","???","???","???","???","???","???"], ["SFENCE","???","???","???","???","???","???","???"] ] ], "IMUL", "CMPXCHG","CMPXCHG", ["LSS","???"], "BTR", ["LFS","???"], ["LGS","???"], "MOVZX","MOVZX", [ ["JMPE","","",""],"???", ["POPCNT","POPCNT","",""],"???" ], "???", ["???","???","???","???","BT","BTS","BTR","BTC"], "BTC", [ ["BSF","","",""],"???", ["TZCNT","TZCNT","",""],["BSF","TZCNTI","",""] ], [ ["BSR","","",""],"???", ["LZCNT","LZCNT","",""],["BSR","","",""] ], "MOVSX","MOVSX", "XADD","XADD", [ ["CMP,PS,","CMP,PS,","CMP,PS,","CMP,PS,"], ["CMP,PD,","CMP,PD,","CMP,PD,","CMP,PD,"], ["CMP,SS,","CMP,SS,","CMP,SS,",""], ["CMP,SD,","CMP,SD,","CMP,SD,",""] ], ["MOVNTI","???"], [["PINSRW","","",""],"PINSRW","",""], ["???",[["PEXTRW","","",""],"PEXTRW","",""]], ["SHUFPS","SHUFPD","???","???"], [ [ "???", ["CMPXCHG8B","","CMPXCHG16B"], "???", ["XRSTORS","","XRSTORS64"], ["XSAVEC","","XSAVEC64"], ["XSAVES","","XSAVES64"], ["VMPTRLD","VMCLEAR","VMXON","???"],["VMPTRST","???","???","???"] ], [ "???", ["SSS","???","???","???","???","???","???","???"], //Synthetic virtual machine operation codes. "???","???","???","???", "RDRAND","RDSEED" ] ], "BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP", ["???",["ADDSUBPD","ADDSUBPD","",""],"???",["ADDSUBPS","ADDSUBPS","",""]], [["PSRLW","","",""],"PSRLW","",""], [["PSRLD","","",""],["PSRLD","PSRLD",["PSRLD","","???"],""],"",""], [["PSRLQ","","",""],"PSRLQ","",""], [["PADDQ","","",""],"PADDQ","",""], [["PMULLW","","",""],"PMULLW","",""], [ ["???","MOVQ","???","???"], ["???","MOVQ",["MOVQ2DQ","","",""],["MOVDQ2Q","","",""]] ], ["???",[["PMOVMSKB","","",""],["PMOVMSKB","PMOVMSKB","",""],"???","???"]], [["PSUBUSB","","",""],"PSUBUSB","",""], [["PSUBUSW","","",""],"PSUBUSW","",""], [["PMINUB","","",""],"PMINUB","",""], [["PAND","","",""],["PAND","PAND",["PANDD","","PANDQ"],["PANDD","","PANDQ"]],"",""], [["PADDUSB","","",""],"PADDUSB","",""], [["PADDUSW","","",""],"PADDUSW","",""], [["PMAXUB","","",""],"PMAXUB","",""], [["PANDN","","",""],["PANDN","PANDN",["PANDND","","PANDNQ"],["PANDND","","PANDNQ"]],"",""], [["PAVGB","","",""],"PAVGB","",""], [ [["PSRAW","","",""],["PSRAW","PSRAW","PSRAW",""],"",""], [["PSRAW","","",""],["PSRAW","PSRAW","PSRAW",""],"",""] ], [["PSRAD","","",""],["PSRAD","PSRAD",["PSRAD","","PSRAQ"],""],"",""], [["PAVGW","","",""],"PAVGW","",""], [["PMULHUW","","",""],"PMULHUW","",""], [["PMULHW","","",""],"PMULHW","",""], [ "???", ["CVTTPD2DQ","CVTTPD2DQ","CVTTPD2DQ",""], ["CVTDQ2PD","CVTDQ2PD",["CVTDQ2PD","CVTDQ2PD","CVTQQ2PD"],"CVTDQ2PD"], "CVTPD2DQ" ], [[["MOVNTQ","","",""],["MOVNTDQ","","???"],"???","???"],"???"], [["PSUBSB","","",""],"PSUBSB","",""], [["PSUBSW","","",""],"PSUBSW","",""], [["PMINSW","","",""],"PMINSW","",""], [["POR","","",""],["POR","POR",["PORD","","PORQ"],["PORD","","PORQ"]],"",""], [["PADDSB","","",""],"PADDSB","",""], [["PADDSW","","",""],"PADDSW","",""], [["PMAXSW","","",""],"PMAXSW","",""], [["PXOR","","",""],["PXOR","PXOR",["PXORD","","PXORQ"],["PXORD","","PXORQ"]],"",""], [["???","???","???",["LDDQU","LDDQU","",""]],"???"], [["PSLLW","","",""],"PSLLW","",""], [["PSLLD","","",""],["PSLLD","","???"],"",""], [["PSLLQ","","",""],"PSLLQ","",""], [["PMULUDQ","","",""],"PMULUDQ","",""], [["PMADDWD","","",""],"PMADDWD","",""], [["PSADBW","","",""],"PSADBW","",""], ["???",[["MASKMOVQ","","",""],["MASKMOVDQU","MASKMOVDQU","",""],"???","???"]], [["PSUBB","","",""],"PSUBB","",""], [["PSUBW","","",""],"PSUBW","",""], [["PSUBD","","",""],["PSUBD","PSUBD",["PSUBD","","???"],["PSUBD","","???"]],"",""], [["PSUBQ","","",""],"PSUBQ","",""], [["PADDB","","",""],"PADDB","",""], [["PADDW","","",""],"PADDW","",""], [["PADDD","","",""],["PADDD","PADDD",["PADDD","","???"],["PADDD","","???"]],"",""], "???", /*------------------------------------------------------------------------------------------------------------------------ Three Byte operations 0F38. Opcodes plus 512 goes to 767 used by escape codes "0F,38", Or set directly by adding map bits "10" because "10 00000000" bin = 512 plus opcode. ------------------------------------------------------------------------------------------------------------------------*/ [["PSHUFB","","",""],"PSHUFB","???","???"], [["PHADDW","","",""],["PHADDW","PHADDW","",""],"???","???"], [["PHADDD","","",""],["PHADDD","PHADDD","",""],"???","???"], [["PHADDSW","","",""],["PHADDSW","PHADDSW","",""],"???","???"], [["PMADDUBSW","","",""],"PMADDUBSW","???","???"], [["PHSUBW","","",""],["PHSUBW","PHSUBW","",""],"???","???"], [["PHSUBD","","",""],["PHSUBD","PHSUBD","",""],"???","???"], [["PHSUBSW","","",""],["PHSUBSW","PHSUBSW","",""],"???","???"], [["PSIGNB","","",""],["PSIGNB","PSIGNB","",""],"???","???"], [["PSIGNW","","",""],["PSIGNW","PSIGNW","",""],"???","???"], [["PSIGND","","",""],["PSIGND","PSIGND","",""],"???","???"], [["PMULHRSW","","",""],"PMULHRSW","???","???"], ["???",["","PERMILPS",["PERMILPS","","???"],""],"???","???"], ["???",["","PERMILPD","PERMILPD",""],"???","???"], ["???",["","TESTPS","",""],"???","???"], ["???",["","TESTPD","",""],"???","???"], ["???",["PBLENDVB","PBLENDVB","PSRLVW",""],["","","PMOVUSWB",""],"???"], ["???",["","","PSRAVW",""],["","","PMOVUSDB",""],"???"], ["???",["","","PSLLVW",""],["","","PMOVUSQB",""],"???"], ["???",["","CVTPH2PS",["CVTPH2PS","","???"],""],["","","PMOVUSDW",""],"???"], ["???",["BLENDVPS","BLENDVPS",["PRORVD","","PRORVQ"],""],["","","PMOVUSQW",""],"???"], ["???",["BLENDVPD","BLENDVPD",["PROLVD","","PROLVQ"],""],["","","PMOVUSQD",""],"???"], ["???",["","PERMPS",["PERMPS","","PERMPD"],""],"???","???"], ["???",["PTEST","PTEST","",""],"???","???"], ["???",["","BROADCASTSS",["BROADCASTSS","","???"],["BROADCASTSS","","???"]],"???","???"], ["???",["","BROADCASTSD",["BROADCASTF32X2","","BROADCASTSD"],["???","","BROADCASTSD"]],"???","???"], ["???",["","BROADCASTF128",["BROADCASTF32X4","","BROADCASTF64X2"],["BROADCASTF32X4","","???"]],"???","???"], ["???",["","",["BROADCASTF32X8","","BROADCASTF64X4"],["???","","BROADCASTF64X4"]],"???","???"], [["PABSB","","",""],"PABSB","???","???"], [["PABSW","","",""],"PABSW","???","???"], [["PABSD","","",""],["PABSD","","???"],"???","???"], ["???",["","","PABSQ",""],"???","???"], ["???","PMOVSXBW",["","","PMOVSWB",""],"???"], ["???","PMOVSXBD",["","","PMOVSDB",""],"???"], ["???","PMOVSXBQ",["","","PMOVSQB",""],"???"], ["???","PMOVSXWD",["","","PMOVSDW",""],"???"], ["???","PMOVSXWQ",["","","PMOVSQW",""],"???"], ["???","PMOVSXDQ",["","","PMOVSQD",""],"???"], ["???",["","",["PTESTMB","","PTESTMW"],""],["","",["PTESTNMB","","PTESTNMW"],""],"???"], ["???",["","",["PTESTMD","","PTESTMQ"],["PTESTMD","","???"]],["","",["PTESTNMD","","PTESTNMQ"],""],"???"], ["???","PMULDQ",["","",["PMOVM2B","","PMOVM2W"],""],"???"], ["???",["PCMPEQQ","PCMPEQQ","PCMPEQQ",""],["","",["PMOVB2M","","PMOVW2M"],""],"???"], [["???",["MOVNTDQA","","???"],"???","???"],["???","???",["","",["???","","PBROADCASTMB2Q"],""],"???"]], ["???",["PACKUSDW","","???"],"???","???"], ["???",["","MASKMOVPS",["SCALEFPS","","SCALEFPD"],""],"???","???"], ["???",["","MASKMOVPD",["SCALEFSS","","SCALEFSD"],""],"???","???"], ["???",["","MASKMOVPS","",""],"???","???"], ["???",["","MASKMOVPD","",""],"???","???"], ["???","PMOVZXBW",["","","PMOVWB",""],"???"], ["???","PMOVZXBD",["","","PMOVDB",""],"???"], ["???","PMOVZXBQ",["","","PMOVQB",""],"???"], ["???","PMOVZXWD",["","","PMOVDW",""],"???"], ["???","PMOVZXWQ",["","","PMOVQW",""],"???"], ["???","PMOVZXDQ",["","",["PMOVQD","PMOVQD",""],""],"???"], ["???",["","PERMD",["PERMD","","PERMQ"],["PERMD","","???"]],"???","???"], ["???",["PCMPGTQ","PCMPGTQ","PCMPGTQ",""],"???","???"], ["???","PMINSB",["","",["PMOVM2D","","PMOVM2Q"],""],"???"], ["???",["PMINSD","PMINSD",["PMINSD","","PMINSQ"],["PMINSD","","???"]],["","",["PMOVD2M","","PMOVQ2M"],""],"???"], ["???","PMINUW",["","","PBROADCASTMW2D",""],"???"], ["???",["PMINUD","PMINUD",["PMINUD","","PMINUQ"],["PMINUD","","???"]],"???","???"], ["???","PMAXSB","???","???"], ["???",["PMAXSD","PMAXSD",["PMAXSD","","PMAXSQ"],["PMAXSD","","???"]],"???","???"], ["???","PMAXUW","???","???"], ["???",["PMAXUD","PMAXUD",["PMAXUD","","PMAXUQ"],["PMAXUD","","???"]],"???","???"], ["???",["PMULLD","PMULLD",["PMULLD","","PMULLQ"],["PMULLD","",""]],"???","???"], ["???",["PHMINPOSUW",["PHMINPOSUW","PHMINPOSUW",""],"",""],"???","???"], ["???",["","",["GETEXPPS","","GETEXPPD"],["GETEXPPS","","GETEXPPD"]],"???","???"], ["???",["","",["GETEXPSS","","GETEXPSD"],""],"???","???"], ["???",["","",["PLZCNTD","","PLZCNTQ"],""],"???","???"], ["???",["",["PSRLVD","","PSRLVQ"],["PSRLVD","","PSRLVQ"],["PSRLVD","","???"]],"???","???"], ["???",["",["PSRAVD","",""],["PSRAVD","","PSRAVQ"],["PSRAVD","","???"]],"???","???"], ["???",["",["PSLLVD","","PSLLVQ"],["PSLLVD","","PSLLVQ"],["PSLLVD","","???"]],"???","???"], "???","???","???","???", ["???",["","",["RCP14PS","","RCP14PD"],""],"???","???"], ["???",["","",["RCP14SS","","RCP14SD"],""],"???","???"], ["???",["","",["RSQRT14PS","","RSQRT14PD"],""],"???","???"], ["???",["","",["RSQRT14SS","","RSQRT14SD"],""],"???","???"], ["???",["","","",["ADDNPS","","ADDNPD"]],"???","???"], ["???",["","","",["GMAXABSPS","","???"]],"???","???"], ["???",["","","",["GMINPS","","GMINPD"]],"???","???"], ["???",["","","",["GMAXPS","","GMAXPD"]],"???","???"], "", ["???",["","","",["FIXUPNANPS","","FIXUPNANPD"]],"???","???"], "","", ["???",["","PBROADCASTD",["PBROADCASTD","","???"],["PBROADCASTD","","???"]],"???","???"], ["???",["","PBROADCASTQ",["BROADCASTI32X2","","PBROADCASTQ"],["???","","PBROADCASTQ"]],"???","???"], ["???",["","BROADCASTI128",["BROADCASTI32X4","","BROADCASTI64X2"],["BROADCASTI32X4","","???"]],"???","???"], ["???",["","",["BROADCASTI32X8","","BROADCASTI64X4"],["???","","BROADCASTI64X4"]],"???","???"], ["???",["","","",["PADCD","","???"]],"???","???"], ["???",["","","",["PADDSETCD","","???"]],"???","???"], ["???",["","","",["PSBBD","","???"]],"???","???"], ["???",["","","",["PSUBSETBD","","???"]],"???","???"], "???","???","???","???", ["???",["","",["PBLENDMD","","PBLENDMQ"],["PBLENDMD","","PBLENDMQ"]],"???","???"], ["???",["","",["BLENDMPS","","BLENDMPD"],["BLENDMPS","","BLENDMPD"]],"???","???"], ["???",["","",["PBLENDMB","","PBLENDMW"],""],"???","???"], "???","???","???","???","???", ["???",["","","",["PSUBRD","","???"]],"???","???"], ["???",["","","",["SUBRPS","","SUBRPD"]],"???","???"], ["???",["","","",["PSBBRD","","???"]],"???","???"], ["???",["","","",["PSUBRSETBD","","???"]],"???","???"], "???","???","???","???", ["???",["","","",["PCMPLTD","","???"]],"???","???"], ["???",["","",["PERMI2B","","PERMI2W"],""],"???","???"], ["???",["","",["PERMI2D","","PERMI2Q"],""],"???","???"], ["???",["","",["PERMI2PS","","PERMI2PD"],""],"???","???"], ["???",["","PBROADCASTB",["PBROADCASTB","","???"],""],"???","???"], ["???",["","PBROADCASTW",["PBROADCASTW","","???"],""],"???","???"], ["???",["???",["","",["PBROADCASTB","","???"],""],"???","???"]], ["???",["???",["","",["PBROADCASTW","","???"],""],"???","???"]], ["???",["","",["PBROADCASTD","","PBROADCASTQ"],""],"???","???"], ["???",["","",["PERMT2B","","PERMT2W"],""],"???","???"], ["???",["","",["PERMT2D","","PERMT2Q"],""],"???","???"], ["???",["","",["PERMT2PS","","PERMT2PD"],""],"???","???"], [["???","INVEPT","???","???"],"???"], [["???","INVVPID","???","???"],"???"], [["???","INVPCID","???","???"],"???"], ["???",["???","???","PMULTISHIFTQB","???"],"???","???"], ["???",["","","",["SCALEPS","","???"]],"???","???"], "???", ["???",["","","",["PMULHUD","","???"]],"???","???"], ["???",["","","",["PMULHD","","???"]],"???","???"], ["???",["","",["EXPANDPS","","EXPANDPD"],""],"???","???"], ["???",["","",["PEXPANDD","","PEXPANDQ"],""],"???","???"], ["???",["","",["COMPRESSPS","","COMPRESSPD"],""],"???","???"], ["???",["","",["PCOMPRESSD","","PCOMPRESSQ"],""],"???","???"], "???", ["???",["","",["PERMB","","PERMW"],""],"???","???"], "???","???", ["???",["",["PGATHERDD","","PGATHERDQ"],["PGATHERDD","","PGATHERDQ"],["PGATHERDD","","PGATHERDQ"]],"???","???"], ["???",["",["PGATHERQD","","PGATHERQQ"],["PGATHERQD","","PGATHERQQ"],""],"???","???"], ["???",["",["GATHERDPS","","GATHERDPD"],["GATHERDPS","","GATHERDPD"],["GATHERDPS","","GATHERDPD"]],"???","???"], ["???",["",["GATHERQPS","","GATHERQPD"],["GATHERQPS","","GATHERQPD"],""],"???","???"], "???","???", ["???",["",["FMADDSUB132PS","","FMADDSUB132PD"],["FMADDSUB132PS","","FMADDSUB132PD"],""],"???","???"], ["???",["",["FMSUBADD132PS","","FMSUBADD132PD"],["FMSUBADD132PS","","FMSUBADD132PD"],""],"???","???"], ["???",["",["FMADD132PS","","FMADD132PD"],["FMADD132PS","","FMADD132PD"],["FMADD132PS","","FMADD132PD"]],"???","???"], ["???",["",["FMADD132SS","","FMADD132SD"],["FMADD132SS","","FMADD132SD"],""],"???","???"], ["???",["",["FMSUB132PS","","FMSUB132PD"],["FMSUB132PS","","FMSUB132PD"],["FMSUB132PS","","FMSUB132PD"]],"???","???"], ["???",["",["FMSUB132SS","","FMSUB132SD"],["FMSUB132SS","","FMSUB132SD"],""],"???","???"], ["???",["",["FNMADD132PS","","FNMADD132PD"],["FNMADD132PS","","FNMADD132PD"],["NMADD132PS","","FNMADD132PD"]],"???","???"], ["???",["",["FNMADD132SS","","FNMADD132SD"],["FNMADD132SS","","FNMADD132SD"],""],"???","???"], ["???",["",["FNMSUB132PS","","FNMSUB132PD"],["FNMSUB132PS","","FNMSUB132PD"],["FNMSUB132PS","","FNMSUB132PS"]],"???","???"], ["???",["",["FNMSUB132SS","","FNMSUB132SD"],["FNMSUB132SS","","FNMSUB132SD"],""],"???","???"], ["???",["","",["PSCATTERDD","","PSCATTERDQ"],["PSCATTERDD","","PSCATTERDQ"]],"???","???"], ["???",["","",["PSCATTERQD","","PSCATTERQQ"],""],"???","???"], ["???",["","",["SCATTERDPS","","SCATTERDPD"],["SCATTERDPS","","SCATTERDPD"]],"???","???"], ["???",["","",["SCATTERQPS","","SCATTERQPD"],""],"???","???"], ["???",["","","",["FMADD233PS","","???"]],"???","???"], "???", ["???",["",["FMADDSUB213PS","","FMADDSUB213PD"],["FMADDSUB213PS","","FMADDSUB213PD"],""],"???","???"], ["???",["",["FMSUBADD213PS","","FMSUBADD213PD"],["FMSUBADD213PS","","FMSUBADD213PD"],""],"???","???"], ["???",["",["FMADD213PS","","FMADD213PD"],["FMADD213PS","","FMADD213PD"],["FMADD213PS","","FMADD213PD"]],"???","???"], ["???",["",["FMADD213SS","","FMADD213SD"],["FMADD213SS","","FMADD213SD"],""],"???","???"], ["???",["",["FMSUB213PS","","FMSUB213PD"],["FMSUB213PS","","FMSUB213PD"],["FMSUB213PS","","FMSUB213PD"]],"???","???"], ["???",["",["FMSUB213SS","","FMSUB213SD"],["FMSUB213SS","","FMSUB213SD"],""],"???","???"], ["???",["",["FNMADD213PS","","FNMADD213PD"],["FNMADD213PS","","FNMADD213PD"],["FNMADD213PS","","FNMADD213PD"]],"???","???"], ["???",["",["FNMADD213SS","","FNMADD213SD"],["FNMADD213SS","","FNMADD213SD"],""],"???","???"], ["???",["",["FNMSUB213PS","","FNMSUB213PD"],["FNMSUB213PS","","FNMSUB213PD"],["FNMSUB213PS","","FNMSUB213PD"]],"???","???"], ["???",["",["FNMSUB213SS","","FNMSUB213SD"],["FNMSUB213SS","","FNMSUB213SD"],""],"???","???"], "???","???","???","???", ["???",["","","PMADD52LUQ",["PMADD233D","","???"]],"???","???"], ["???",["","","PMADD52HUQ",["PMADD231D","","???"]],"???","???"], ["???",["",["FMADDSUB231PS","","FMADDSUB231PD"],["FMADDSUB231PS","","FMADDSUB231PD"],""],"???","???"], ["???",["",["FMSUBADD231PS","","FMSUBADD231PD"],["FMSUBADD231PS","","FMSUBADD231PD"],""],"???","???"], ["???",["",["FMADD231PS","","FMADD231PD"],["FMADD231PS","","FMADD231PD"],["FMADD231PS","","FMADD231PD"]],"???","???"], ["???",["",["FMADD231SS","","FMADD231SD"],["FMADD231SS","","FMADD231SD"],""],"???","???"], ["???",["",["FMSUB231PS","","FMSUB231PD"],["FMSUB231PS","","FMSUB231PD"],["FMSUB231PS","","FMSUB231PD"]],"???","???"], ["???",["",["FMSUB231SS","","FMSUB231SD"],["FMSUB231SS","","FMSUB231SD"],""],"???","???"], ["???",["",["FNMADD231PS","","FNMADD231PD"],["FNMADD231PS","","FNMADD231PD"],["FNMADD231PS","","FNMADD231PD"]],"???","???"], ["???",["",["FNMADD231SS","","FNMADD231SD"],["FNMADD231SS","","FNMADD231SD"],""],"???","???"], ["???",["",["FNMSUB231PS","","FNMSUB231PD"],["FNMSUB231PS","","FNMSUB231PD"],["FNMSUB231PS","","FNMSUB231PD"]],"???","???"], ["???",["",["FNMSUB231SS","","FNMSUB231SD"],["FNMSUB231SS","","FNMSUB231SD"],""],"???","???"], "???","???","???","???", ["???",["","",["PCONFLICTD","","PCONFLICTQ"],""],"???","???"], "???", [ [ ["???",["","","",["GATHERPF0HINTDPS","","GATHERPF0HINTDPD"]],"???","???"], ["???",["","",["GATHERPF0DPS","","GATHERPF0DPD"],["GATHERPF0DPS","",""]],"???","???"], ["???",["","",["GATHERPF1DPS","","GATHERPF1DPD"],["GATHERPF1DPS","",""]],"???","???"], "???", ["???",["","","",["SCATTERPF0HINTDPS","","SCATTERPF0HINTDPD"]],"???","???"], ["???",["","",["SCATTERPF0DPS","","SCATTERPF0DPD"],["VSCATTERPF0DPS","",""]],"???","???"], ["???",["","",["SCATTERPF1DPS","","SCATTERPF1DPD"],["VSCATTERPF1DPS","",""]],"???","???"], "???" ],"???" ], [ [ "???", ["???",["","",["GATHERPF0QPS","","GATHERPF0QPD"],""],"???","???"], ["???",["","",["GATHERPF1QPS","","GATHERPF1QPD"],""],"???","???"], "???","???", ["???",["","",["SCATTERPF0QPS","","SCATTERPF0QPD"],""],"???","???"], ["???",["","",["SCATTERPF1QPS","","SCATTERPF1QPD"],""],"???","???"], "???" ],"???" ], [["SHA1NEXTE","","",""],["","",["EXP2PS","","EXP2PD"],["EXP223PS","","???"]],"???","???"], [["SHA1MSG1","","",""],["","","",["LOG2PS","","???"]],"???","???"], [["SHA1MSG2","","",""],["","",["RCP28PS","","RCP28PD"],["RCP23PS","","???"]],"???","???"], [["SHA256RNDS2","","",""],["","",["RCP28SS","","RCP28SD"],["RSQRT23PS","","???"]],"???","???"], [["SHA256MSG1","","",""],["","",["RSQRT28PS","","RSQRT28PD"],["ADDSETSPS","","???"]],"???","???"], [["SHA256MSG2","","",""],["","",["RSQRT28SS","","RSQRT28SD"],["PADDSETSD","","???"]],"???","???"], "???","???", [[["","","",["LOADUNPACKLD","","LOADUNPACKLQ"]],["","","",["PACKSTORELD","","PACKSTORELQ"]],"???","???"],"???"], [[["","","",["LOADUNPACKLPS","","LOADUNPACKLPD"]],["","","",["PACKSTORELPS","","PACKSTORELPD"]],"???","???"],"???"], "???","???", [[["","","",["LOADUNPACKHD","","LOADUNPACKHQ"]],["","","",["PACKSTOREHD","","PACKSTOREHQ"]],"???","???"],"???"], [[["","","",["LOADUNPACKHPS","","LOADUNPACKHPD"]],["","","",["PACKSTOREHPS","","PACKSTOREHPD"]],"???","???"],"???"], "???","???","???","???","???", ["???",["AESIMC","AESIMC","",""],"???","???"], ["???",["AESENC","AESENC","",""],"???","???"], ["???",["AESENCLAST","AESENCLAST","",""],"???","???"], ["???",["AESDEC","AESDEC","",""],"???","???"], ["???",["AESDECLAST","AESDECLAST","",""],"???","???"], "???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???", [ ["MOVBE","","",""], ["MOVBE","","",""],"???", ["CRC32","","",""] ], [ ["MOVBE","","",""], ["MOVBE","","",""],"???", ["CRC32","","",""] ], ["???",["","ANDN","",""],"???","???"], [ "???", ["???",["","BLSR","",""],"???","???"], ["???",["","BLSMSK","",""],"???","???"], ["???",["","BLSI","",""],"???","???"], "???","???","???","???" ],"???", [ ["","BZHI","",""],"???", ["","PEXT","",""], ["","PDEP","",""] ], [ "???", ["ADCX","","",""], ["ADOX","","",""], ["","MULX","",""] ], [ ["","BEXTR","",""], ["","SHLX","",""], ["","SARX","",""], ["","SHRX","",""] ], "???","???","???","???","???","???","???","???", /*------------------------------------------------------------------------------------------------------------------------ Three Byte operations 0F38. Opcodes plus 768 goes to 767 used by escape codes "0F, 3A", Or set directly by adding map bits "11" because "11 00000000" bin = 768 plus opcode. ------------------------------------------------------------------------------------------------------------------------*/ ["???",["","PERMQ","PERMQ",""],"???","???"], ["???",["","PERMPD","PERMPD",""],"???","???"], ["???",["",["PBLENDD","",""],"",""],"???","???"], ["???",["","",["ALIGND","","ALIGNQ"],["ALIGND","","???"]],"???","???"], ["???",["","PERMILPS",["PERMILPS","","???"],""],"???","???"], ["???",["","PERMILPD","PERMILPD",""],"???","???"], ["???",["","PERM2F128","",""],"???","???"], ["???",["","","",["PERMF32X4","","???"]],"???","???"], ["???",["ROUNDPS","ROUNDPS",["RNDSCALEPS","","???"],""],"???","???"], ["???",["ROUNDPD","ROUNDPD","RNDSCALEPD",""],"???","???"], ["???",["ROUNDSS","ROUNDSS",["RNDSCALESS","","???"],""],"???","???"], ["???",["ROUNDSD","ROUNDSD","RNDSCALESD",""],"???","???"], ["???",["BLENDPS","BLENDPS","",""],"???","???"], ["???",["BLENDPD","BLENDPD","",""],"???","???"], ["???",["PBLENDW","PBLENDW","",""],"???","???"], [["PALIGNR","","",""],"PALIGNR","???","???"], "???","???","???","???", [["???","PEXTRB","???","???"],["???","PEXTRB","???","???"]], [["???","PEXTRW","???","???"],["???","PEXTRW","???","???"]], ["???",["PEXTRD","","PEXTRQ"],"???","???"], ["???","EXTRACTPS","???","???"], ["???",["","INSERTF128",["INSERTF32X4","","INSERTF64X2"],""],"???","???"], ["???",["","EXTRACTF128",["EXTRACTF32X4","","EXTRACTF64X2"],""],"???","???"], ["???",["","",["INSERTF32X8","","INSERTF64X4"],""],"???","???"], ["???",["","",["EXTRACTF32X8","","EXTRACTF64X4"],""],"???","???"], "???", ["???",["","CVTPS2PH",["CVTPS2PH","","???"],""],"???","???"], ["???",["","",["PCMP,UD,","","PCMP,UQ,"],["PCMP,UD,","","???"]],"???","???"], ["???",["","",["PCM,PD,","","PCM,PQ,"],["PCM,PD,","","???"]],"???","???"], ["???","PINSRB","???","???"], ["???",["INSERTPS","","???"],"???","???"], ["???",["",["PINSRD","","PINSRQ"],["PINSRD","","PINSRQ"],""],"???","???"], ["???",["","",["SHUFF32X4","","SHUFF64X2"],""],"???","???"], "???", ["???",["","",["PTERNLOGD","","PTERNLOGQ"],""],"???","???"], ["???",["","",["GETMANTPS","","GETMANTPD"],["GETMANTPS","","GETMANTPD"]],"???","???"], ["???",["","",["GETMANTSS","","GETMANTSD"],""],"???","???"], "???","???","???","???","???","???","???","???", ["???",["",["KSHIFTRB","","KSHIFTRW"],"",""],"???","???"], ["???",["",["KSHIFTRD","","KSHIFTRQ"],"",""],"???","???"], ["???",["",["KSHIFTLB","","KSHIFTLW"],"",""],"???","???"], ["???",["",["KSHIFTLD","","KSHIFTLQ"],"",""],"???","???"], "???","???","???","???", ["???",["","INSERTI128",["INSERTI32X4","","INSERTI64X2"],""],"???","???"], ["???",["","EXTRACTI128",["EXTRACTI32X4","","EXTRACTI64X2"],""],"???","???"], ["???",["","",["INSERTI32X8","","INSERTI64X4"],""],"???","???"], ["???",["","",["EXTRACTI32X8","","EXTRACTI64X4"],""],"???","???"], "???","???", ["???",["","KEXTRACT",["PCMP,UB,","","PCMP,UW,"],""],"???","???"], ["???",["","",["PCM,PB,","","PCM,PW,"],""],"???","???"], ["???",["DPPS","DPPS","",""],"???","???"], ["???",["DPPD","DPPD","",""],"???","???"], ["???",["MPSADBW","MPSADBW",["DBPSADBW","","???"],""],"???","???"], ["???",["","",["SHUFI32X4","","SHUFI64X2"],""],"???","???"], ["???",["PCLMULQDQ","PCLMULQDQ","",""],"???","???"], "???", ["???",["","PERM2I128","",""],"???","???"], "???", ["???",["",["PERMIL2PS","","PERMIL2PS"],"",""],"???","???"], ["???",["",["PERMIL2PD","","PERMIL2PD"],"",""],"???","???"], ["???",["","BLENDVPS","",""],"???","???"], ["???",["","BLENDVPD","",""],"???","???"], ["???",["","PBLENDVB","",""],"???","???"], "???","???","???", ["???",["","",["RANGEPS","","RANGEPD"],""],"???","???"], ["???",["","",["RANGESS","","RANGESD"],""],"???","???"], ["???",["","","",["RNDFXPNTPS","","RNDFXPNTPD"]],"???","???"], "???", ["???",["","",["FIXUPIMMPS","","FIXUPIMMPD"],""],"???","???"], ["???",["","",["FIXUPIMMSS","","FIXUPIMMSD"],""],"???","???"], ["???",["","",["REDUCEPS","","REDUCEPD"],""],"???","???"], ["???",["","",["REDUCESS","","REDUCESD"],""],"???","???"], "???","???","???","???", ["???",["",["FMADDSUBPS","","FMADDSUBPS"],"",""],"???","???"], ["???",["",["FMADDSUBPD","","FMADDSUBPD"],"",""],"???","???"], ["???",["",["FMSUBADDPS","","FMSUBADDPS"],"",""],"???","???"], ["???",["",["FMSUBADDPD","","FMSUBADDPD"],"",""],"???","???"], ["???",["PCMPESTRM","PCMPESTRM","",""],"???","???"], ["???",["PCMPESTRI","PCMPESTRI","",""],"???","???"], ["???",["PCMPISTRM","PCMPISTRM","",""],"???","???"], ["???",["PCMPISTRI","PCMPISTRI","",""],"???","???"], "???","???", ["???",["","",["FPCLASSPS","","FPCLASSPD"],""],"???","???"], ["???",["","",["FPCLASSSS","","FPCLASSSD"],""],"???","???"], ["???",["",["FMADDPS","","FMADDPS"],"",""],"???","???"], ["???",["",["FMADDPD","","FMADDPD"],"",""],"???","???"], ["???",["",["FMADDSS","","FMADDSS"],"",""],"???","???"], ["???",["",["FMADDSD","","FMADDSD"],"",""],"???","???"], ["???",["",["FMSUBPS","","FMSUBPS"],"",""],"???","???"], ["???",["",["FMSUBPD","","FMSUBPD"],"",""],"???","???"], ["???",["",["FMSUBSS","","FMSUBSS"],"",""],"???","???"], ["???",["",["FMSUBSD","","FMSUBSD"],"",""],"???","???"], "???","???","???","???","???","???","???","???", ["???",["",["FNMADDPS","","FNMADDPS"],"",""],"???","???"], ["???",["",["FNMADDPD","","FNMADDPD"],"",""],"???","???"], ["???",["",["FNMADDSS","","FNMADDSS"],"",""],"???","???"], ["???",["",["FNMADDSD","","FNMADDSD"],"",""],"???","???"], ["???",["",["FNMSUBPS","","FNMSUBPS"],"",""],"???","???"], ["???",["",["FNMSUBPD","","FNMSUBPD"],"",""],"???","???"], ["???",["",["FNMSUBSS","","FNMSUBSS"],"",""],"???","???"], ["???",["",["FNMSUBSD","","FNMSUBSD"],"",""],"???","???"], "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???", [["","","","CVTFXPNTUDQ2PS"],["","","",["CVTFXPNTPS2UDQ","","???"]],"???",["","","","CVTFXPNTPD2UDQ"]], [["","","","CVTFXPNTDQ2PS"],["","","",["CVTFXPNTPS2DQ","","???"]],"???","???"], "SHA1RNDS4", "???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", ["???",["AESKEYGENASSIST","AESKEYGENASSIST","",""],"???","???"], "???","???","???","???","???","???", ["???","???","???",["","","","CVTFXPNTPD2DQ"]], "???","???","???","???","???","???","???","???","???", ["???","???","???",["","RORX","",""]], "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", /*------------------------------------------------------------------------------------------------------------------------ AMD XOP 8. ------------------------------------------------------------------------------------------------------------------------*/ "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???", "VPMACSSWW","VPMACSSWD","VPMACSSDQL","???","???","???","???","???","???", "VPMACSSDD","VPMACSSDQH","???","???","???","???","???","VPMACSWW","VPMACSWD","VPMACSDQL", "???","???","???","???","???","???","VPMACSDD","VPMACSDQH", "???","???",["VPCMOV","","VPCMOV"],["VPPERM","","VPPERM"],"???","???","VPMADCSSWD", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "VPMADCSWD","???","???","???","???","???","???","???","???","???", "VPROTB","VPROTW","VPROTD","VPROTQ","???","???","???","???","???","???","???","???", "VPCOM,B,","VPCOM,W,","VPCOM,D,","VPCOM,Q,","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "VPCOM,UB,","VPCOM,UW,","VPCOM,UD,","VPCOM,UQ,", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", /*------------------------------------------------------------------------------------------------------------------------ AMD XOP 9. ------------------------------------------------------------------------------------------------------------------------*/ "???", ["???","BLCFILL","BLSFILL","BLCS","TZMSK","BLCIC","BLSIC","T1MSKC"],["???","BLCMSK","???","???","???","???","BLCI","???"], "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", ["???",["LLWPCB","SLWPCB","???","???","???","???","???","???"]], "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "VFRCZPS","VFRCZPD","VFRCZSS","VFRCZSD","???","???","???","???","???","???","???","???","???","???","???","???", ["VPROTB","","VPROTB"],["VPROTW","","VPROTW"],["VPROTD","","VPROTD"],["VPROTQ","","VPROTQ"], ["VPSHLB","","VPSHLB"],["VPSHLW","","VPSHLW"],["VPSHLD","","VPSHLD"],["VPSHLQ","","VPSHLQ"], ["VPSHAB","","VPSHAB"],["VPSHAW","","VPSHAW"],["VPSHAD","","VPSHAD"],["VPSHAQ","","VPSHAQ"], "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "VPHADDBW","VPHADDBD","VPHADDBQ","???","???","VPHADDWD","VPHADDWQ","???","???","???","VPHADDDQ","???","???","???","???","???", "VPHADDUBWD","VPHADDUBD","VPHADDUBQ","???","???","VPHADDUWD","VPHADDUWQ","???","???","???","VPHADDUDQ","???","???","???","???","???", "VPHSUBBW","VPHSUBWD","VPHSUBDQ","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???", /*------------------------------------------------------------------------------------------------------------------------ AMD XOP A. ------------------------------------------------------------------------------------------------------------------------*/ "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "BEXTR","???",["LWPINS","LWPVAL","???","???","???","???","???","???"], "???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", /*------------------------------------------------------------------------------------------------------------------------- L1OM Vector. -------------------------------------------------------------------------------------------------------------------------*/ "???","???","???","???","DELAY","???","???","???","???","???","???","???","???","???","???","???", [["VLOADD","VLOADQ","",""],"???"],"???", [["VLOADUNPACKLD","VLOADUNPACKLQ","",""],"???"], [["VLOADUNPACKHD","VLOADUNPACKHQ","",""],"???"], [["VSTORED","VSTOREQ","",""],"???"],"???", [["VPACKSTORELD","VPACKSTORELQ","",""],"???"], [["VPACKSTOREHD","VPACKSTOREHQ","",""],"???"], ["VGATHERD","???"],["VGATHERPFD","???"],"???",["VGATHERPF2D","???"], ["VSCATTERD","???"],["VSCATTERPFD","???"],"???",["VSCATTERPF2D","???"], ["VCMP,PS,","VCMP,PD,","",""],"VCMP,PI,","VCMP,PU,","???", ["VCMP,PS,","VCMP,PD,","",""],"VCMP,PI,","VCMP,PU,","???", "???","???","???","???","???","???","???","???", "VTESTPI","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", ["VADDPS","VADDPD","",""],"VADDPI","???","VADDSETCPI","???","VADCPI","VADDSETSPS","VADDSETSPI", ["VADDNPS","VADDNPD","",""],"???","???","???","???","???","???","???", ["VSUBPS","VSUBPD","",""],"VSUBPI","???","VSUBSETBPI","???","VSBBPI","???","???", ["VSUBRPS","VSUBRPD","",""],"VSUBRPI","???","VSUBRSETBPI","???","VSBBRPI","???","???", ["VMADD231PS","VMADD231PD","",""],"VMADD231PI", ["VMADD213PS","VMADD213PD","",""],"???", ["VMADD132PS","VMADD132PD","",""],"???", "VMADD233PS","VMADD233PI", ["VMSUB231PS","VMSUB231PD","",""],"???", ["VMSUB213PS","VMSUB213PD","",""],"???", ["VMSUB132PS","VMSUB132PD","",""],"???","???","???", ["VMADDN231PS","VMADDN231PD","",""],"???", ["VMADDN213PS","VMADDN213PD","",""],"???", ["VMADDN132PS","VMADDN132PD","",""],"???","???","???", ["VMSUBR231PS","VMSUBR231PD","",""],"???", ["VMSUBR213PS","VMSUBR213PD","",""],"???", ["VMSUBR132PS","VMSUBR132PD","",""],"???", ["VMSUBR23C1PS","VMSUBR23C1PD","",""],"???", ["VMULPS","VMULPD","",""],"VMULHPI","VMULHPU","VMULLPI","???","???","VCLAMPZPS","VCLAMPZPI", ["VMAXPS","VMAXPD","",""],"VMAXPI","VMAXPU","???", ["VMINPS","VMINPD","",""],"VMINPI","VMINPU","???", ["???","VCVT,PD2PS,","",""],["VCVTPS2PI","VCVT,PD2PI,","",""],["VCVTPS2PU","VCVT,PD2PU,","",""],"???", ["???","VCVT,PS2PD,","",""],["VCVTPI2PS","VCVT,PI2PD,","",""],["VCVTPU2PS","VCVT,PU2PD,","",""],"???", "VROUNDPS","???","VCVTINSPS2U10","VCVTINSPS2F11","???","VCVTPS2SRGB8","VMAXABSPS","???", "VSLLPI","VSRAPI","VSRLPI","???", ["VANDNPI","VANDNPQ","",""],["VANDPI","VANDPQ","",""], ["VORPI","VORPQ","",""],["VXORPI","VXORPQ","",""], "VBINTINTERLEAVE11PI","VBINTINTERLEAVE21PI","???","???","???","???","???","???", "VEXP2LUTPS","VLOG2LUTPS","VRSQRTLUTPS","???","VGETEXPPS","???","???","???", "VSCALEPS","???","???","???","???","???","???","???", "VRCPRESPS","???","VRCPREFINEPS","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", "VFIXUPPS","VSHUF128X32","VINSERTFIELDPI","VROTATEFIELDPI","???","???","???","???", "???","???","???","???","???","???","???","???", /*------------------------------------------------------------------------------------------------------------------------- L1OM Mask, Mem, and bit opcodes. -------------------------------------------------------------------------------------------------------------------------*/ ["???","BSFI"],["???","BSFI"],["???","BSFI"],["???","BSFI"], ["???","BSRI"],["???","BSRI"],["???","BSRI"],["???","BSRI"], ["???","BSFF"],["???","BSFF"],["???","BSFF"],["???","BSFF"], ["???","BSRF"],["???","BSRF"],["???","BSRF"],["???","BSRF"], ["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"], ["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"], ["???","INSERTFIELD"],["???","INSERTFIELD"],["???","INSERTFIELD"],["???","INSERTFIELD"], ["???","ROTATEFIELD"],["???","ROTATEFIELD"],["???","ROTATEFIELD"],["???","ROTATEFIELD"], ["???","COUNTBITS"],["???","COUNTBITS"],["???","COUNTBITS"],["???","COUNTBITS"], ["???","QUADMASK16"],["???","QUADMASK16"],["???","QUADMASK16"],["???","QUADMASK16"], "???","???","???","???", "VKMOVLHB", [["CLEVICT1","CLEVICT2","LDVXCSR","STVXCSR","???","???","???","???"],"???"], [["VPREFETCH1","VPREFETCH2","???","???","???","???","???","???"],"???"], [["VPREFETCH1","VPREFETCH2","???","???","???","???","???","???"],"???"], "VKMOV","VKMOV","VKMOV","VKMOV", "VKNOT","VKANDNR","VKANDN","VKAND", "VKXNOR","VKXOR","VKORTEST","VKOR", "???","VKSWAPB", ["???",["DELAY","SPFLT","???","???","???","???","???","???"]], ["???",["DELAY","SPFLT","???","???","???","???","???","???"]] ]; /*------------------------------------------------------------------------------------------------------------------------- The Operand type array each operation code can use different operands that must be decoded after the select Opcode. Basically some instruction may use the ModR/M talked about above while some may use an Immediate, or Both. An Immediate input uses the byte after the opcode as a number some instructions combine a number and an ModR/M address selection By using two bytes for each encoding after the opcode. X86 uses very few operand types for input selections to instructions, but there are many useful combinations. The order the operands are "displayed" is the order they are in the Operands string for the operation code. --------------------------------------------------------------------------------------------------------------------------- The first 2 digits is the selected operand type, and for if the operand can change size. Again more opcodes where sacrificed to make this an setting Opcode "66" goes 16 bit this is explained in detail in the SizeAttrSelect variable section that is adjusted by the function ^DecodePrefixAdjustments()^. The Variable SizeAttrSelect effects all operand formats that are decoded by different functions except for single size. Don't forget X86 uses very few operand types in which different prefix adjustments are used to add extra functionality to each operand type. The next two numbers is the operands size settings. If 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 faster decoding. Single size is also used to select numbers that are higher than the max size to select special registers that are used by some instructions like Debug Registers. --------------------------------------------------------------------------------------------------------------------------- Registers have 8, 16, 32, 64, 128, 256, 512 names. The selected ModR/M address location uses a pointer name that shows it's select size 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. --------------------------------------------------------------------------------------------------------------------------- Used by function ^DecodeOpcode()^ after ^DecodePrefixAdjustments()^. -------------------------------------------------------------------------------------------------------------------------*/ const Operands = [ //------------------------------------------------------------------------------------------------------------------------ //First Byte operations. //------------------------------------------------------------------------------------------------------------------------ "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", "06000A00","070E0B0E","0A000600","0B0E070E","16000C00","170E0DE6","","", "03060003","03060003","03060003","03060003","03060003","03060003","03060003","03060003", "03060003","03060003","03060003","03060003","03060003","03060003","03060003","03060003", "030A","030A","030A","030A","030A","030A","030A","030A", "030A","030A","030A","030A","030A","030A","030A","030A", ["","",""],["","",""], ["0A020606","0A010604",""], "0B0E0704", "","","","", "0DE6","0B0E070E0DE6", "0DA1","0B0E070E0DE1", "22001A01","230E1A01","1A012000","1A01210E", "10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C", "10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C", ["06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C00"], ["070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE6"], ["06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C00"], ["070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE1"], "06000A00","070E0B0E", "0A0006000003","0B0E070E0003", "06000A000001","070E0B0E0001", "0A0006000001","0B0E070E0001", "06020A080001", ["0B0E0601",""], "0A0806020001", ["070A","","","","","","",""], [["","","",""],["","","",""],["","","",""],["","","",""]], "170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003", ["","",""],["","",""], "0D060C01", //CALL Ap (w:z). "", ["","",""],["","",""], "","", "160004000001","170E050E0001", "040016000001","050E170E0001", "22002000","230E210E", "22002000","230E210E", "16000C00","170E0DE6", "22001600","230E170E","16002000","170E210E","16002200","170E230E", "02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001", "030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001", ["06000C00","06000C00","06000C00","06000C00","06000C00","06000C00","06000C00","06000C00"], ["070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00"], "0C010008","0008", "0B060906","0B060906", [ "06000C000001","","","","","","", ["0C00","0C00","0C00","0C00","0C00","0C00","0C00","0C00"] ], [ "070E0D060001","","","","","","", ["1002","1002","1002","1002","1002","1002","1002","1002"] ], "0C010C00","", "0C01","","2C00", "0C00","", ["","",""], ["06002A00","06002A00","06002A00","06002A00","06002A00","06002A00","06002A00","06002A00"], ["070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00"], ["06001800","06001800","06001800","06001800","06001800","06001800","06001800","06001800"], ["070E1800","070E1800","070E1800","070E1800","070E1800","070E1800","070E1800","070E1800"], "0C00","0C00","", "1E00", /*------------------------------------------------------------------------------------------------------------------------ X87 FPU. ------------------------------------------------------------------------------------------------------------------------*/ [ ["0604","0604","0604","0604","0604","0604","0604","0604"], ["24080609","24080609","0609","0609","24080609","24080609","24080609","24080609"] ], [ ["0604","","0604","0604","0601","0602","0601","0602"], [ "0609","0609", ["","","","","","","",""], "0609", ["","","","","","","",""], ["","","","","","","",""], ["","","","","","","",""], ["","","","","","","",""] ] ], [ ["0604","0604","0604","0604","0604","0604","0604","0604"], [ "24080609","24080609","24080609","24080609","", ["","","","","","","",""],"","" ] ], [ ["0604","0604","0604","0604","","0607","","0607",""], [ "24080609","24080609","24080609","24080609", ["","","","","","","",""], "24080609","24080609","" ] ], [ ["0606","0606","0606","0606","0606","0606","0606","0606"], ["06092408","06092408","0609","0609","06092408","06092408","06092408","06092408"] ], [ ["0606","0606","0606","0606","0606","","0601","0602"], ["0609","0609","0609","0609","0609","0609","",""] ], [ ["0602","0602","0602","0602","0602","0602","0602","0602"], [ "06092408","06092408","0609", ["","","","","","","",""], "06092408","06092408","06092408","06092408" ] ], [ ["0602","0602","0602","0602","0607","0606","0607","0606"], [ "0609","0609","0609","0609", ["1601","","","","","","",""], "24080609","24080609", "" ] ], /*------------------------------------------------------------------------------------------------------------------------ End of X87 FPU. ------------------------------------------------------------------------------------------------------------------------*/ "10000004","10000004","10000004","10000004", "16000C00","170E0C00","0C001600","0C00170E", "110E0008", "110E0008", "0D060C01", //JMP Ap (w:z). "100000040004", "16001A01","170E1A01", "1A011600","1A01170E", "","","","","","", ["06000C00","","06000003","06000003","16000600","0600","16000600","0600"], ["070E0D06","","070E0003","070E0003","170E070E","070E","170E070E","170E070E"], "","","","","","", ["06000003","06000003","","","","","",""], [ ["070E0003","070E0003","070A0004","090E0008","070A0008","090E0008","070A",""], ["070E0003","070E0003","070A0008","","070A0008","","070A",""] ], /*------------------------------------------------------------------------------------------------------------------------ Two Byte operations. ------------------------------------------------------------------------------------------------------------------------*/ [ ["0602","0602","0602","0602","0602","0602","070E",""], ["070E","070E","0601","0601","0601","0601","070E",""] ], [ ["0908","0908","0908","0908","0602","","0602","0601"], [ ["","","","","","","",""], ["170819081B08","17081908","","","","","",""], ["","","","","","","",""], ["1708","","1708","1708","","","1602","17081802"], "070E","","0601", ["","","170819081B08","170819081B08","","","",""] ] ], ["0B0E0612","0B0E070E"],["0B0E0612","0B0E070E"],"", "","","","", "","","","", [["0601","0601","","","","","",""],""], "", "0A0A06A9", //3DNow takes ModR/M, IMM8. [ ["0B700770","0B700770","0A040603","0A040609"], ["0B700770","0B700770","0A0412040604","0A0412040604"] ], [ ["07700B70","07700B70","06030A04","06090A04"], ["07700B70","07700B70","060412040A04","060412040A04"] ], [ ["0A0412040606","0A0412040606","0B700770","0B700768"], ["0A0412040604","","0B700770","0B700770"] ], [["06060A04","06060A04","",""],""], ["0B70137007700140","0B70137007700140","",""], ["0B70137007700140","0B70137007700140","",""], [["0A0412040606","0A0412040606","0B700770",""],["0A0412040604","","0B700770",""]], [["06060A04","06060A04","",""],""], [["0601","0601","0601","0601","","","",""],""], "", [[["0A0B07080180","","",""],["0A0B07100180","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]], ["",["0A0B060B","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]]], [[["07080A0B0180","","",""],["07100A0B0180","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]], ["",["0A0B060B","","",""],"",["0A0B07080180","","",""]]], "","","", "070E", ["","07080A0C0001"],["","07080A0D0001"], ["","0A0C07080001"],["","0A0D07080001"], ["","07080A0E0001"],"", ["","0A0E07080001"],"", [ ["0A040648","0B300730","0B700770","0A06066C0130"], ["0A040648","0B300730","0B700770","0A06066C0130"], "","" ], [ [ ["06480A04","07300B30","07700B70","066C0A060130"], ["06480A04","07300B30","07700B70","066C0A060130"], ["","","",["066C0A060138","066C0A060138","066C0A060138"]], ["","","",["066C0A060138","066C0A060138","066C0A060138"]] ], [ ["06480A04","07300B30","07700B70","066C0A06"], ["06480A04","07300B30","07700B70","066C0A06"], "","" ] ], [ ["0A0406A9","","",""],["0A0406A9","","",""], //Not Allowed to be Vector encoded. "0A041204070C010A","0A041204070C010A" ], [ [ "07700B70","07700B70", ["06030A04","","",""],["06060A04","","",""] //SSE4a can not be vector encoded. ],"" ], [ ["0A0A0649","","",""],["0A0A0648","","",""], //Not allowed to be Vector encoded. "0B0C06430109","0B0C06490109" ], [ ["0A0A0649","","",""],["0A0A0648","","",""], //Not allowed to be vector encoded. "0B0C0643010A","0B0C0649010A" ], ["0A0406430101","0A0406490101","",""], ["0A0406430101","0A0406490101","",""], "","","","", "","","", "", "",//Three byte opcodes 0F38 "", "",//Three byte opcodes 0F3A "","","","","", "0B0E070E", [ ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" ], [ ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" ], [["0B0E070E0180","0A0F06FF","",""],"","",""], [ ["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""], ["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],"","" ], [ ["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], ["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" ], [ ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" ], [ ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" ], [["0B0E070E0180","0A0F06FF","",""],"","",""], [["0B0E070E0180","0A0F06FF","",""],"","",""], [ ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" ], [ ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], ["0B0E070E0180",["0A0F120F06FF","",""],"",""],"","" ], "0B0E070E","0B0E070E","0B0E070E","0B0E070E", ["",[["0B0C0648","0B0C0730","",""],["0B0C0648","0B0C0730","",""],"",""]], ["0B7007700142","0B7007700142","0A04120406430102","0A04120406490102"], [ ["0A040648","0A040648","",""],"", ["0A040643","0A0412040643","",""],"" ], [ ["0A040648","0A040648","",""],"", ["0A040643","0A0412040643","",""],"" ], ["0B70137007700140","0B70137007700140","",""], ["0B70137007700140","0B70137007700140","",""], ["0B70137007700140","0B70137007700140","",""], ["0B70137007700140","0B70137007700140","",""], [ ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], "0A04120406430102","0A04120406460102" ], [ ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], "0A04120406430102","0A04120406460102" ], [ ["0A040648","0B300718","0B7007380151","0A06065A0171"], ["0A040648","0B180730","0B3807700152","0A05066C0152"], "0A04120406430101","0A04120406460102" ], [["0B7007700142","","0B380770014A"],["0B700770014A","",""],"0B7007700141",""], [ ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], "0A04120406430102","0A04120406460102" ], ["0B70137007700141","0B70137007700141","0A04120406430101","0A04120406460101"], ["0B70137007700142","0B70137007700142","0A04120406430102","0A04120406460102"], ["0B70137007700141","0B70137007700141","0A04120406430101","0A04120406460101"], [["0A0A06A3","","",""],"0B70137007700108","",""], [["0A0A06A3","","",""],"0B70137007700108","",""], [["0A0A06A3","","",""],"0B701370077001400108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","0A0F137007700108",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","0A0F137007700108",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730",["0A0F137007700148","",""],["0A0F1206066C0148","",""]],"",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0B70137007700148","",""],"",""], [["0A0A06A9","","",""],["0B70137007700148","",""],"",""], ["","0B70137007700140","",""], ["","0B70137007700140","",""], [["0A0A070C","","",""],["0A04070C0108","","0A04070C0108"],"",""], [ [ ["0A0A06A9","", "",""], ["0B700770","0B700770",["0B7007700108","","0B700770"],["0A06066C0128","","0A06066C0120"]], ["0A040710","0B700770",["0B700770","","0B7007700108"],""], ["","",["0B7007700108","","0B700770"],""] ], [ ["0A0A06A9","", "",""], ["0B700770","0B700770",["0B7007700108","","0B700770"],["0A06066C0148","","0A06066C0140"]], ["0A040710","0B700770",["0B700770","","0B7007700108"],""], ["","",["0B7007700108","","0B700770"],""] ] ], [ ["0A0A06A90C00","","",""], ["0A0406480C00","0B3007300C00",["0B7007700C000108","",""],["0A06066C0C000108","",""]], "0B7007700C000108", "0B7007700C000108" ], [ "", [ "","", [["060A0C00","","",""],"137007700C000108","",""],"", [["060A0C00","","",""],"137007700C000108","",""],"", [["060A0C00","","",""],"137007700C000108","",""],"" ] ], [ ["",["","",["137007700C000148","","137007700C000140"],""],"",""], ["",["","",["137007700C000148","","137007700C000140"],""],"",""], [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","",""],["1206066C0C000148","",""]],"",""], "", [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","","137007700C000140"],["1206066C0C000148","",""]],"",""], "", [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","",""],["1206066C0C000148","",""]],"",""], "" ], [ "", [ "","", [["137007700C00","137007700C00","",""],"137007700C000140","",""],["","137007700C000108","",""], "","", [["137007700C00","137007700C00","",""],"137007100C000140","",""],["","137007700C000108","",""] ] ], [["0A0A06A9","","",""],["0A040710","13300B300730","0A0F137007700108",""],"",""], [["0A0A06A9","","",""],["0A040710","13300B300730","0A0F137007700108",""],"",""], [["0A0A06A9","","",""],["0A040710","13300B300730",["0A0F137007700148","",""],["0A0F1206066C0148","",""]],"",""], [["",["","",""],"",""],"","",""], [ ["07080B080180","",["0B7007700141","","0B3807700149"],""], ["064F0C000C00","",["0B7007380149","","0B7007700141"],""], ["","","0B0C06440109",""], ["0A04064F0C000C00","","0B0C06460109",""] ], [ ["0B0807080180","",["0B7007700142","","0B380770014A"],""], ["0A04064F","",["0B700738014A","","0B7007700142"],""], ["","","0B0C0644010A",""], ["0A04064F","","0B0C0646010A",""] ], [ "", ["","",["0B7007380149","","0B7007700141"],""], ["","",["0B7007380142","","0B700770014A"],"0A06065A0170"], ["","",["0B700770014A","","0B3807700142"],""] ], [ "", ["","",["0B700738014A","","0B7007700142"],""], ["","","0A041204070C010A",""], ["","","0A041204070C010A",""] ], [ "",["0A040604","0B7013700770","",""], "",["0A040604","0B7013700770","",""] ], [ "",["0A040604","0B7013700770","",""], "",["0A040604","0B7013700770","",""] ], [["070C0A0A","","",""],["06240A040108","","06360A040108"],["0A040646","0A040646",["","","0A0406460108"],""],""], [ ["06A90A0A","","",""], ["06480A04","07300B30",["07700B700108","","07700B70"],["066C0A060128","","066C0A060120"]], ["06480A04","07300B30",["07700B70","","07700B700108"],""], ["","",["07700B700108","","07700B70"],""] ], "1106000C","1106000C","1106000C","1106000C", [["1106000C","120F1002","",""],"","",""],[["1106000C","120F1002","",""],"","",""], "1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C", [ ["0600",["0A0F06F2","","0A0F06F6"],"",""], ["0600",["0A0F06F0","","0A0F06F4"],"",""],"","" ], [ ["0600",["06120A0F","","06360A0F"],"",""], ["0600",["06000A0F","","06240A0F"],"",""],"","" ], [ ["0600",["0A0F062F","",""],"",""], ["0600",["0A0F062F","",""],"",""],"", ["0600",["0A0F062F","","0A0F063F"],"",""] ], [ ["0600",["062F0A0F","",""],"",""], ["0600",["062F0A0F","",""],"",""],"", ["0600",["062F0A0F","","063F0A0F"],"",""] ], "0600",[["0600","0A03120F06FF","",""],"","",""], "0600",[["0600","0A03120F06FF","",""],"","",""], [ ["0600",["0A0F06FF","","0A0F06FF"],"",""], ["0600",["0A0F06FF","","0A0F06FF"],"",""],"","" ], [ ["0600",["0A0F06FF","","0A0F06FF"],"",""], ["0600",["0A0F06FF","","0A0F06FF"],"",""],"","" ], "0600","0600","0600","0600","0600","0600", "2608","2608", "", "070E0B0E0003", "070E0B0E0C00","070E0B0E1800", "0B0E070E","070E0B0E", "2808","2808", "", "070E0B0E0003", "070E0B0E0C00","070E0B0E1800", [ [ ["0601","","0601"],["0601","","0601"], "0603","0603", ["0601","","0601"],["0601","","0601"], ["0601","0601","0601"], ["0601","0601",""] ], [ ["","",["0602","","",""],""],["","",["0602","","",""],""], ["","",["0602","","",""],""],["","",["0602","","",""],""], "", ["","","","","","","",""], ["","","","","","","",""], ["","","","","","","",""] ] ], "0B0E070E", "06000A000003","070E0B0E0003", ["0B0E090E",""], "070E0B0E0003", ["0B0E090E",""], ["0B0E090E",""], "0B0E0600","0B0E0602", [ ["1002","","",""],"", ["0B060706","0A020602","",""],"" ],"", ["","","","","070E0C000003","070E0C000003","070E0C000003","070E0C000003"], "0B0E070E0003", [ ["0B0E070E0180","","",""],"", ["0B0E070E0180","0A020602","",""],["0B0E070E0180","0A020602","",""] ], [ ["0B0E070E0180","","",""],"", ["0B0E070E0180","0A020602","",""],["0B0E070E0180","","",""] ], "0B0E0600","0B0E0602", "06000A000003","070E0B0E0003", [ ["0A0406480C00","0B30133007300C00","0A0F137007700C000151","0A0F066C0C000151"], ["0A0406480C00","0B30133007300C00","0A0F137007700C000151","0A0F066C0C000151"], ["0A0406440C00","0A04120406480C00","0A0F120406440C000151",""], ["0A0406490C00","0A04120406480C00","0A0F120406460C000151",""] ], ["06030A02",""], [["0A0A06220C00","","",""],"0A04120406220C000108","",""], ["",[["06020A0A0C00","","",""],"06020A040C000108","",""]], ["0B70137007700C000140","0B70137007700C000140","",""], [ [ "", ["06060003","","060B0003"], "", ["0601","","0601"], ["0601","","0601"], ["0601","","0601"], ["0606","0606","0606",""],["0606","","",""] ], [ "", ["","","","","","","",""], "","","","", "070E","070E" ] ], "030E","030E","030E","030E","030E","030E","030E","030E", ["",["0A040648","0B3013300730","",""],"",["0A040648","0B3013300730","",""]], [["0A0A06A9","","",""],"0B70137006480108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300648",["0B70137006480108","",""],""],"",""], [["0A0A06A9","","",""],"0B70137006480100","",""], [["0A0A06A9","","",""],"0B70137007700140","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [ ["","06490A040100","",""], ["","06490A040100",["0A040649","","",""],["0A040649","","",""]] ], ["",[["0B0C06A0","","",""],["0B0C0640","0B0C0730","",""],"",""]], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [ [["0A0A06A9","","",""],["0A040648","0B3013300648","0B70137006480108",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","0B70137006480108",""],"",""] ], [["0A0A06A9","","",""],["0A040648","0B3013300648",["0B70137006480108","","0B7013700648"],""],"",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [ "", ["0A040648","0A040730","0B3807700141",""], ["0A040649","0B300738",["0A0406480140","0B7007380140","0B700770014A"],"0A06065A0170"], "0B3807700142" ], [[["06090A0A","","",""],["07700B700108","",""],"",""],""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], [["","","",["0A040648","0A040730","",""]],"0000"], [["0A0A06A9","","",""],"0B70137006480108","",""], [["0A0A06A9","","",""],["0B70137006480108","",""],"",""], [["0A0A06A9","","",""],"0B7013700648","",""], [["0A0A06A9","","",""],"0B70137007700140","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], ["",[["0A0A060A","","",""],["0B040648","0B040648","",""],"",""]], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","",""],["0A061206066C0148","",""]],"",""], [["0A0A06A9","","",""],"0B70137007700140","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","",""],["0A061206066C0148","",""]],"",""], "", /*------------------------------------------------------------------------------------------------------------------------ Three Byte operations 0F38. ------------------------------------------------------------------------------------------------------------------------*/ [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],"0B70137007700108","",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], [["0A0A06A9","","",""],"0B70137007700108","",""], ["",["","0B3013300730",["0B70137007700148","",""],""],"",""], ["",["","0B3013300730","0B70137007700140",""],"",""], ["",["","0B300730","",""],"",""], ["",["","0B300730","",""],"",""], ["",["0A0406482E00","0B30133007301530","0B7013700770",""],["","","07380B70",""],""], ["",["","","0B7013700770",""],["","","071C0B70",""],""], ["",["","","0B7013700770",""],["","","070E0B70",""],""], ["",["","0B300718",["0B7007380109","",""],""],["","","07380B70",""],""], ["",["0A0407102E00","0B30133007301530",["0B70137007700148","","0B70137007700140"],""],["","","071C0B70",""],""], ["",["0A0407102E00","0B30133007301530",["0B70137007700148","","0B70137007700140"],""],["","","07380B70",""],""], ["",["","0B3013300730",["0B70137007700148","","0B70137007700140"],""],"",""], ["",["0A040648","0B300730","",""],"",""], ["",["","0B300644",["0B7006440138","",""],["0A0606440138","",""]],"",""], ["",["","0A050646",["0B6806460108","","0B700646"],["","","0A060646"]],"",""], ["",["","0A050648",["0B6806480138","","0B680648"],["0A0606480138","",""]],"",""], ["",["","",["0A06065A0108","","0A06065A"],["","","0A06065A"]],"",""], [["0A0A06A9","","",""],"0B7007700108","",""], [["0A0A06A9","","",""],"0B7007700108","",""], [["0A0A06A9","","",""],["0B7007700148","",""],"",""], ["",["","","0B7007700140",""],"",""], ["","0B7007380108",["","","07380B70",""],""], ["","0B70071C0108",["","","071C0B70",""],""], ["","0B70070E0108",["","","070E0B70",""],""], ["","0B7007380108",["","","07380B70",""],""], ["","0B70071C0108",["","","071C0B70",""],""], ["","0B7007380108",["","","07380B70",""],""], ["",["","",["0A0F137007700108","","0A0F13700770"],""],["","",["0A0F13700770","","0A0F137007700108"],""],""], ["",["","",["0A0F137007700148","","0A0F137007700140"],["0A0F1206066C0148","",""]],["","",["0A0F137007700140","","0A0F137007700148"],""],""], ["","0B70137007700140",["","",["0B7006FF","","0B7006FF0108"],""],""], ["",["0A040648","0B3013300730","0A0F137007700140",""],["","",["0A0F0770","","0A0F07700108"],""],""], [["",["0B7007700108","",""],"",""],["","",["","",["","","0B7006FF0108"],""],""]], ["",["0B70137007700148","",""],"",""], ["",["","0B3013300730",["0B7013700770014A","","0B70137007700142"],""],"",""], ["",["","0B3013300730",["0A0412040644014A","","0A04120406480142"],""],"",""], ["",["","073013300B30","",""],"",""], ["",["","0B3013300730","",""],"",""], ["","0B7007380108",["","","07380B70",""],""], ["","0B70071C0108",["","","071C0B70",""],""], ["","0B70070E0108",["","","070E0B70",""],""], ["","0B7007380108",["","","07380B70",""],""], ["","0B70071C0108",["","","071C0B70",""],""], ["","0B7007380108",["","",["06480A04","07380B70",""],""],""], ["",["","0A051205065A",["0B70137007700148","","0B70137007700140"],["0A061206066C0108","",""]],"",""], ["",["0A040710","0B3013300730","0A0F137007700140",""],"",""], ["","0B70137007700108",["","",["0B7006FF","","0B7006FF0108"],""],""], ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],["","",["0A0F0770","","0A0F07700108"],""],""], ["","0B70137007700108",["","","0B7006FF0100",""],""], ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], ["","0B70137007700108","",""], ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], ["","0B70137007700108","",""], ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], ["",["0A040648",["0A040648","0A040648","",""],"",""],"",""], ["",["","",["0B7007700159","","0B7007700151"],["0A06066C0159","","0A06066C0151"]],"",""], ["",["","",["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["","",["0B7007700148","","0B7007700140"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], ["",["",["0B3013300730","",""],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], "","","","", ["",["","",["0B7007700148","","0B7007700140"],""],"",""], ["",["","",["0A04120406440108","","0A0412040646"],""],"",""], ["",["","",["0B7007700148","","0B7007700140"],""],"",""], ["",["","",["0A04120406440108","","0A0412040646"],""],"",""], ["",["","","",["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["","","",["0A061206066C0159","",""]],"",""], ["",["","","",["0A061206066C0159","","0A061206066C0151"]],"",""], ["",["","","",["0A061206066C0159","","0A061206066C0151"]],"",""], "", ["",["","","",["0A061206066C0149","","0A061206066C0141"]],"",""], "","", ["",["","0B300644",["0B7006440128","",""],["0A0606440128","",""]],"",""], ["",["","0B300646",["0B7006460128","","0B7006460120"],["","","0A0606460120"]],"",""], ["",["","0A050648",["0B6806480128","","0B6806480120"],["0A0606480128","",""]],"",""], ["",["","",["0A06065A0128","","0A06065A0120"],["","","0A06065A0120"]],"",""], ["",["","","",["0A06120F066C0148","",""]],"",""], ["",["","","",["0A06120F066C0148","",""]],"",""], ["",["","","",["0A06120F066C0148","",""]],"",""], ["",["","","",["0A06120F066C0148","",""]],"",""], "","","","", ["",["","",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], ["",["","",["0B70137007700158","","0B70137007700150"],["0A061206066C0158","","0A061206066C0150"]],"",""], ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], "","","","","", ["",["","","",["0A061206066C0148","",""]],"",""], ["",["","","",["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["","","",["0A06120F066C0148","",""]],"",""], ["",["","","",["0A06120F066C0148","",""]],"",""], "","","","", ["",["","","",["0A0F1206066C0148","",""]],"",""], ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], ["",["","0B300640",["0B7006400108","",""],""],"",""], ["",["","0B300642",["0B7006420108","",""],""],"",""], ["",["",["","",["0B7006000108","",""],""],"",""]], ["",["",["","",["0B7006100108","",""],""],"",""]], ["",["","",["0B70062F0108","","0B70063F"],""],"",""], ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], [["","0B0C060B0180","",""],""], [["","0B0C060B0180","",""],""], [["","0B0C060B0180","",""],""], ["",["","","0B70137007700140",""],"",""], ["",["","","",["0A061206066C014A","",""]],"",""], "", ["",["","","",["0A061206066C0148","",""]],"",""], ["",["","","",["0A061206066C0148","",""]],"",""], ["",["","",["0B7007700108","","0B700770"],""],"",""], ["",["","",["0B7007700108","","0B700770"],""],"",""], ["",["","",["07700B700108","","07700B70"],""],"",""], ["",["","",["07700B700108","","07700B70"],""],"",""], "", ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], "","", ["",["",["0B30073013300124","","0B30064813300124"],["0B700770012C","","0B7007380124"],["0A06066C012C","","0A06065A0124"]],"",""], ["",["",["0A04073012040104","","0B30073013300104"],["0B380770010C","","0B7007700104"],""],"",""], ["",["",["0B30073013300134","","0B30064813300134"],["0B700770013C","","0B7007380134"],["0A06066C013C","","0A06065A0104"]],"",""], ["",["",["0A04073012040104","","0B30073013300104"],["0B380770010C","","0B7007700104"],""],"",""], "","", ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["","",["07700B70010C","","07380B700104"],["066C0A06012C","","065A0A060124"]],"",""], ["",["","",["07700B38010C","","07700B700104"],""],"",""], ["",["","",["07700B70013C","","07380B700134"],["066C0A06013C","","065A0A060134"]],"",""], ["",["","",["07700B38010C","","07700B700104"],""],"",""], ["",["","","",["0A061206066C011A","",""]],"",""], "", ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], "","","","", ["",["","","0B70137007700140",["0A061206066C0118","",""]],"",""], ["",["","","0B70137007700140",["0A061206066C0148","",""]],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], "","","","", ["",["","",["0B7007700148","","0B7007700140"],""],"",""], "", [ [ ["",["","","",["060C013C","","060A0134"]],"",""], ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], ["",["","",["060C013C","","070A0134"],["060C013C","",""]],"",""], "", ["",["","","",["060C013C","","060A0134"]],"",""], ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], "" ],"" ], [ [ "", ["",["","",["060C010C","","060C0104"],""],"",""], ["",["","",["060C010C","","060C0104"],""],"",""], "","", ["",["","",["060C010C","","060C0104"],""],"",""], ["",["","",["060C010C","","060C0104"],""],"",""], "" ],"" ], [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C0109","",""]],"",""], [["0A040648","","",""],["","","",["0A06066C0109","",""]],"",""], [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C0109","",""]],"",""], [["0A0406482E00","","",""],["","",["0A04120406440109","","0A04120406460101"],["0A06066C0109","",""]],"",""], [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C015A","",""]],"",""], [["0A040648","","",""],["","",["0A04120406440109","","0A04120406460101"],["0A06066C0148","",""]],"",""], "","", [[["","","",["0A06060C0120","","0A06060C0128"]],["","","",["060C0A060128","","060C0A060120"]],"",""],""], [[["","","",["0A06060C0130","","0A06060C0138"]],["","","",["060C0A060138","","060C0A060130"]],"",""],""], "","", [[["","","",["0A06060C0120","","0A06060C0128"]],["","","",["060C0A060128","","060C0A060120"]],"",""],""], [[["","","",["0A06060C0130","","0A06060C0138"]],["","","",["060C0A060138","","060C0A060130"]],"",""],""], "","","","","", ["",["0A040648","0A040648","",""],"",""], ["",["0A040648","0A0412040648","",""],"",""], ["",["0A040648","0A0412040648","",""],"",""], ["",["0A040648","0A0412040648","",""],"",""], ["",["0A040648","0A0412040648","",""],"",""], "","","","","","","","","","","","","","","","", [ ["0B0E070E0180","","",""], ["0B0E070E0180","","",""],"", ["0B0C06000180","","",""] ], [ ["070E0B0E0180","","",""], ["070E0B0E0180","","",""],"", ["0B0C070E0180","","",""] ], ["",["","0B0C130C070C","",""],"",""], [ "", ["",["","130C070C","",""],"",""], ["",["","130C070C","",""],"",""], ["",["","130C070C","",""],"",""], "","","","" ],"", [ ["","0B0C070C130C","",""],"", ["","0B0C130C070C","",""], ["","0B0C130C070C","",""] ], [ "", ["0B0C070C","","",""], ["0B0C070C","","",""], ["","0B0C130C070C1B0C","",""] ], [ ["","0B0C130C070C","",""], ["","0B0C130C070C","",""], ["","0B0C130C070C","",""], ["","0B0C130C070C","",""] ], "","","","","","","","", /*------------------------------------------------------------------------------------------------------------------------ Three Byte operations 0F3A. ------------------------------------------------------------------------------------------------------------------------*/ ["",["","0A05065A0C00","0B7007700C000140",""],"",""], ["",["","0A05065A0C00","0B7007700C000140",""],"",""], ["",["",["0B30133007300C00","",""],"",""],"",""], ["",["","",["0B70137007700C000148","","0B70137007700C000140"],["0A061206066C0C000108","",""]],"",""], ["",["","0B3007300C00",["0B7007700C000148","",""],""],"",""], ["",["","0B3007300C00","0B7007700C000140",""],"",""], ["",["","0A051205065A0C00","",""],"",""], ["",["","","",["0A06066C0C000108","",""]],"",""], ["",["0A0406480C00","0B3007300C00",["0B7007700C000149","",""],""],"",""], ["",["0A0406480C00","0B3007300C00","0B7007700C000141",""],"",""], ["",["0A0406440C00","0A04120406440C00",["0A04120406440C000109","",""],""],"",""], ["",["0A0406460C00","0A04120406460C00","0A04120406460C000101",""],"",""], ["",["0A0406480C00","0B30133007300C00","",""],"",""], ["",["0A0406480C00","0B30133007300C00","",""],"",""], ["",["0A0406480C00","0B30133007300C00","",""],"",""], [["0A0A06A90C00","","",""],"0B70137007700C000108","",""], "","","","", [["","06000A040C000108","",""],["","070C0A040C000108","",""]], [["","06020A040C000108","",""],["","070C0A040C000108","",""]], ["",["06240A040C000108","","06360A040C00"],"",""], ["","070C0A040C000108","",""], ["",["","0A05120506480C00",["0B70137006480C000108","","0B70137006480C00"],""],"",""], ["",["","06480A050C00",["06480B700C000108","","06480B700C00"],""],"",""], ["",["","",["0A061206065A0C000108","","0A061206065A0C00"],""],"",""], ["",["","",["065A0A060C000108","","065A0A060C00"],""],"",""], "", ["",["","07180B300C00",["07380B700C000109","",""],""],"",""], ["",["","",["0A0F137007700C000148","","0A0F137007700C000140"],["0A0F1206066C0C000148","",""]],"",""], ["",["","",["0A0F137007700C000148","","0A0F137007700C000140"],["0A0F1206066C0C000148","",""]],"",""], ["","0A04120406200C000108","",""], ["",["0A04120406440C000108","",""],"",""], ["",["",["0A04120406240C00","","0A04120406360C00"],["0A04120406240C000108","","0A04120406360C00"],""],"",""], ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], "", ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], ["",["","",["0B7007700C000149","","0B7007700C000141"],["0A06066C0C000159","","0A06066C0C000151"]],"",""], ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], "","","","","","","","", ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], "","","","", ["",["","0A05120506480C00",["0B70137006480C000108","","0B70137006480C00"],""],"",""], ["",["","06480A050C00",["06480B700C000108","","06480B700C00"],""],"",""], ["",["","",["0A061206065A0C000108","","0A061206065A0C00"],""],"",""], ["",["","",["065A0A060C000108","","065A0A060C00"],""],"",""], "","", ["",["","0A0F063F0C00",["0A0F137007700C000108","","0A0F137007700C00"],""],"",""], ["",["","",["0A0F137007700C000108","","0A0F137007700C00"],""],"",""], ["",["0A0406480C00","0B30133007300C00","",""],"",""], ["",["0A0406480C00","0A04120406480C00","",""],"",""], ["",["0A0406480C00","0B30133007300C00",["0B70137007700C000108","",""],""],"",""], ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], ["",["0A0406480C00","0A04120406480C00","",""],"",""], "", ["",["","0A051205065A0C00","",""],"",""], "", ["",["",["0B301330073015300E00","","0B301330153007300E00"],"",""],"",""], ["",["",["0B301330073015300E00","","0B301330153007300E00"],"",""],"",""], ["",["","0B30133007301530","",""],"",""], ["",["","0B30133007301530","",""],"",""], ["",["","0A051205065A1505","",""],"",""], "","","", ["",["","",["0B70137007700C000149","","0B70137007700C000141"],""],"",""], ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], ["",["","","",["0A06066C0C000159","","0A06066C0C000151"]],"",""], "", ["",["","",["0B70137007700C000149","","0B70137007700C000141"],""],"",""], ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], ["",["","",["0B7007700C000149","","0B7007700C000141"],""],"",""], ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], "","","","", ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["0A0406480C00","0A0406480C00","",""],"",""], ["",["0A0406480C00","0A0406480C00","",""],"",""], ["",["0A0406480C00","0A0406480C00","",""],"",""], ["",["0A0406480C00","0A0406480C00","",""],"",""], "","", ["",["","",["0A0F07700C000148","","0A0F07700C000140"],""],"",""], ["",["","",["0A0F06440C000108","","0A0F06460C00"],""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], "","","","","","","","", ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","", [["","","","0A06066C0C000141"],["","","",["0A06066C0C000159","",""]],"",["","","","0A06066C0C000151"]], [["","","","0A06066C0C000141"],["","","",["0A06066C0C000159","",""]],"",""], "0A0406480C00","","","", "","","","","","","","","","","","","","","", ["",["0A0406480C00","0A0406480C00","",""],"",""], "","","","","","", ["","","",["","","","0A06066C0C000151"]], "","","","","","","","","", ["","","",["","0B0C070C0C00","",""]], "","","","","","","","","","","","","","","", /*------------------------------------------------------------------------------------------------------------------------ AMD XOP 8. ------------------------------------------------------------------------------------------------------------------------*/ "","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","", "0A04120406481404","0A04120406481404","0A04120406481404","","","","","","", "0A04120406481404","0A04120406481404","","","","","","0A04120406481404","0A04120406481404","0A04120406481404", "","","","","","","0A04120406481404","0A04120406481404", "","",["0B30133007301530","","0B30133015300730"],["0A04120406481404","","0A04120414040648"],"","","0A04120406481404", "","","","","","","","","","","","","","","", "0A04120406481404","","","","","","","","","","0A0406480C00","0A0406480C00","0A0406480C00","0A0406480C00", "","","","","","","","", "0A04120406480C00","0A04120406480C00","0A04120406480C00","0A04120406480C00", "","","","","","","","","","","","","","","","","","","","","","","","","","","","", "0A04120406480C00","0A04120406480C00","0A04120406480C00","0A04120406480C00", "","","","","","","","","","","","","","","","", /*------------------------------------------------------------------------------------------------------------------------ AMD XOP 9. ------------------------------------------------------------------------------------------------------------------------*/ "", ["","130C070C","130C070C","130C070C","130C070C","130C070C","130C070C","130C070C"], ["","130C070C","","","","","130C070C",""], "","","","","","","","","","","","","","","", ["",["070C","070C","","","","","",""]], "","","","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","","", "0B300730","0B300730","0B300730","0B300730", "","","","","","","","","","","","", ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], "","","","","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","","","", "0A040648","0A040648","0A040648","","","0A040648","0A040648","","","","0A040648","","","","","", "0A040648","0A040648","0A040648","","","0A040648","0A040648","","","","0A040648","","","","","", "0A040648","0A040648","0A040648","","","","","","","","","","","","","","", "","","","","","","","","","","","","","", /*------------------------------------------------------------------------------------------------------------------------ AMD XOP A. ------------------------------------------------------------------------------------------------------------------------*/ "","","","","","","","","","","","","","","","", "0B0C070C0C020180","",["130C06240C020180","130C06240C020180","","","","","",""], "","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", /*------------------------------------------------------------------------------------------------------------------------- L1OM Vector. -------------------------------------------------------------------------------------------------------------------------*/ "","","","","1206","","","","","","","","","","","", [["0A0606610120","0A0606610120","",""],""],"", [["0A0606610120","0A0606610120","",""],""], [["0A0606610120","0A0606610120","",""],""], [["0A0606610100","0A0606610100","",""],""],"", [["0A0606610100","0A0606610100","",""],""], [["0A0606610100","0A0606610100","",""],""], ["0A06066C0124",""],["066C0124",""],"",["066C0124",""], ["066C0A060104",""],["066C0104",""],"",["066C0104",""], ["0A0F120606610150","0A0F120606610150","",""],"0A0F120606610140","0A0F120606610140","", ["0A0F120606610150","0A0F120606610150","",""],"0A0F120606610140","0A0F120606610140","", "","","","","","","","", "0A0F120606610140","","","","","","","","","","","","","","","", ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","0A06120606610150","0A06120606610140", ["0A06120606610150","0A06120606610150","",""],"","","","","","","", ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","","", ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","","", ["0A06120606610150","0A06120606610150","",""],"0A06120606610140", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"", "0A06120606610150","0A06120606610140", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"","","", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"","","", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"", ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","0A06120606610140","","","0A06120606610150","0A06120606610140", ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","", ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","", ["","0A0606610152","",""],["0A0606610153","0A0606610152","",""],["0A0606610153","0A0606610152","",""],"", ["","0A0606610158","",""],["0A0606610141","0A0606610148","",""],["0A0606610141","0A0606610148","",""],"", "0A0606610153","","0A0606610150","0A0606610152","","0A0606610150","0A0606610150","", "0A06120606610140","0A06120606610140","0A06120606610140","", ["0A06120606610140","0A06120606610140","",""],["0A06120606610140","0A06120606610140","",""], ["0A06120606610140","0A06120606610140","",""],["0A06120606610140","0A06120606610140","",""], "0A06120606610140","0A06120606610140","","","","","","", "0A0606610140","0A0606610150","0A0606610150","","0A0606610150","","","", "0A06120606610140","","","","","","","", "0A0606610150","","0A06120606610150","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "0A0606610C010150","0A0606610C000C00","0A06120606610C010140","0A0606610C010140","","","","", "","","","","","","","", /*------------------------------------------------------------------------------------------------------------------------- L1OM Mask, Mem, and bit opcodes. -------------------------------------------------------------------------------------------------------------------------*/ ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], ["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"], ["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"], ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], "","","","", "06FF0A0F", [["0601","0601","0604","0604","","","",""],""], [["0601","0601","","","","","",""],""], [["0601","0601","","","","","",""],""], "06FF0A0F","06FF0B06","07060A0F","06FF0B06", "06FF0A0F","06FF0A0F","06FF0A0F","06FF0A0F", "06FF0A0F","06FF0A0F","06FF0A0F","06FF0A0F", "","06FF0A0F", ["",["0B07","0B07","","","","","",""]], ["",["0B07","0B07","","","","","",""]] ]; /*------------------------------------------------------------------------------------------------------------------------- 3DNow uses the byte after the operands as the select instruction code, so in the Mnemonics there is no instruction name, but in the Operands array the operation code 0F0F which is two byte opcode 0x10F (using the disassemblers opcode value system) automatically takes operands ModR/M, and MM register. Once the operands are decoded the byte value after the operands is the selected instruction code for 3DNow. The byte value is an 0 to 255 value so the listing is 0 to 255. --------------------------------------------------------------------------------------------------------------------------- At the very end of the function ^DecodeInstruction()^ an undefined instruction name with the operands MM, and MM/MMWORD is compared for if the operation code is 0x10F then the next byte is read and is used as the selected 3DNow instruction. -------------------------------------------------------------------------------------------------------------------------*/ const M3DNow = [ "","","","","","","","","","","","","PI2FW","PI2FD","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","PFNACC","","","","PFPNACC","", "PFCMPGE","","","","PFMIN","","PFRCP","PFRSQRT","","","FPSUB","","","","FPADD","", "PFCMPGT","","","","PFMAX","","PFRCPIT1","PFRSQIT1","","","PFSUBR","","","","PFACC","", "PFCMPEQ","","","","PFMUL","","PFRCPIT2","PMULHRW","","","","PSWAPD","","","","PAVGUSB", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","" ]; /*------------------------------------------------------------------------------------------------------------------------- Virtual machine synthetic operation codes is under two byte operation code 0FC7 which is opcode 0x1C7 using the disassemblers opcode value system. The operation code 0x1C7 is an group opcode containing 3 operation codes, but only one of the codes is used in the ModR/M grouped opcode for synthetic virtual machine operation codes. The ModR/M byte has to be in register mode using register code 001 for the virtual machine synthetic operation codes. The effective address has to be set 000 which uses the full ModR/M byte as an static opcode encoding under the group opcode 001. This makes the operation code 0F C7 C8. The resulting instruction name in the Mnemonics map is "SSS", and takes no Operands in the Operands array. The two bytes after 0F C7 C8 are used as the select synthetic operation code. Only the first 4 values of both bytes have an select operation code, so an 5x5 map is used to keep the mapping small. --------------------------------------------------------------------------------------------------------------------------- When the operation code is 0F C7 and takes the ModR/M byte value C8 the operation code is "SSS" with no operands. At the very end of the function ^DecodeInstruction()^ an instruction that is "SSS" is compared if it is instruction "SSS". If 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. --------------------------------------------------------------------------------------------------------------------------- link to the patent https://www.google.com/patents/US7552426 -------------------------------------------------------------------------------------------------------------------------*/ const MSynthetic = [ "VMGETINFO","VMSETINFO","VMDXDSBL","VMDXENBL","", "VMCPUID","VMHLT","VMSPLAF","","", "VMPUSHFD","VMPOPFD","VMCLI","VMSTI","VMIRETD", "VMSGDT","VMSIDT","VMSLDT","VMSTR","", "VMSDTE","","","","" ]; /*------------------------------------------------------------------------------------------------------------------------- Condition codes Note that the SSE, and MVEX versions are limited to the first 7 condition codes. XOP condition codes map differently. -------------------------------------------------------------------------------------------------------------------------*/ const ConditionCodes = [ "EQ","LT","LE","UNORD","NEQ","NLT","NLE","ORD", //SSE/L1OM/MVEX. "EQ_UQ","NGE","NGT","FALSE","NEQ_OQ","GE","GT","TRUE", //VEX/EVEX. "EQ_OS","LT_OQ","LE_OQ","UNORD_S","NEQ_US","NLT_UQ","NLE_UQ","ORD_S", //VEX/EVEX. "EQ_US","NGE_UQ","NGT_UQ","FALSE_OS","NEQ_OS","GE_OQ","GT_OQ","TRUE_US", //VEX/EVEX. "LT","LE","GT","GE","EQ","NEQ","FALSE","TRUE" //XOP. ]; /*------------------------------------------------------------------------------------------------------------------------- The Decoded operation name. -------------------------------------------------------------------------------------------------------------------------*/ var Instruction = ""; /*------------------------------------------------------------------------------------------------------------------------- The Instructions operands. -------------------------------------------------------------------------------------------------------------------------*/ var InsOperands = ""; /*------------------------------------------------------------------------------------------------------------------------- This object stores a single decoded Operand, and gives it an number in OperandNum (Operand Number) for the order they are read in the operand string. It also stores all of the Settings for the operand. --------------------------------------------------------------------------------------------------------------------------- Each Operand is sorted into an decoder array in the order they are decoded by the CPU in series. --------------------------------------------------------------------------------------------------------------------------- Used by function ^DecodeOperandString()^ Which sets the operands active and gives them there settings along the X86Decoder array. --------------------------------------------------------------------------------------------------------------------------- The following X86 patent link might help http://www.google.com/patents/US7640417 -------------------------------------------------------------------------------------------------------------------------*/ var Operand = function(){ return( { Type:0, //The operand type some operands have different formats like DecodeImmediate() which has a type input. BySizeAttrubute:false, //Effects how size is used depends on which operand type for which operand across the decoder array. /*------------------------------------------------------------------------------------------------------------------------- How Size is used depends on the operand it is along the decoder array for which function it uses to decode Like DecodeRegValue(), or Decode_ModRM_SIB_Address(), and lastly DecodeImmediate() as they all take the BySize. -------------------------------------------------------------------------------------------------------------------------*/ Size:0x00, //The Setting. OperandNum:0, //The operand number basically the order each operand is read in the operand string. Active:false, //This is set by the set function not all operand are used across the decoder array. //set the operands attributes then set it active in the decoder array. set:function(T, BySize, Settings, OperandNumber) { this.Type = T; this.BySizeAttrubute = BySize; this.Size = Settings; this.OpNum = OperandNumber; //Give the operand the number it was read in the operand string. this.Active = true; //set the operand active so it's settings are decoded by the ^DecodeOperands()^ function. }, //Deactivates the operand after they are decoded by the ^DecodeOperands()^ function. Deactivate:function(){ this.Active = false; } } ); }; /*------------------------------------------------------------------------------------------------------------------------- The Decoder array is the order each operand is decoded after the select opcode if used. They are set during the decoding of the operand string using the function ^DecodeOperandString()^ which also gives each operand an number for the order they are read in. Then they are decoded by the Function ^DecodeOperands()^ which decodes each set operand across the X86Decoder in order. The number the operands are set during the decoding of the operand string is the order they will be positioned after decoding. As the operands are decoded they are also Deactivated so the next instruction can be decoded using different operands. --------------------------------------------------------------------------------------------------------------------------- The following X86 patent link might help http://www.google.com/patents/US7640417 --------------------------------------------------------------------------------------------------------------------------- Used by functions ^DecodeOperandString()^, and ^DecodeOperands()^, after function ^DecodeOpcode()^. -------------------------------------------------------------------------------------------------------------------------*/ var X86Decoder = [ /*------------------------------------------------------------------------------------------------------------------------- First operand that is always decoded is "Reg Opcode" if used. Uses the function ^DecodeRegValue()^ the input RValue is the three first bits of the opcode. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //Reg Opcode if used. /*------------------------------------------------------------------------------------------------------------------------- The Second operand that is decoded in series is the ModR/M address if used. Reads a byte using function ^Decode_ModRM_SIB_Value()^ gives it to the function ^Decode_ModRM_SIB_Address()^ which only reads the Mode, and Base register for the address, and then decodes the SIB byte if base register is "100" binary in value. does not use the Register value in the ModR/M because the register can also be used as a group opcode used by the function ^DecodeOpcode()^, or uses a different register in single size with a different address pointer. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //ModR/M address if used. /*------------------------------------------------------------------------------------------------------------------------- The third operand that is decoded if used is for the ModR/M reg bits. Uses the already decoded byte from ^Decode_ModRM_SIB_Value()^ gives the three bit reg value to the function ^DecodeRegValue()^. The ModR/M address, and reg are usually used together, but can also change direction in the encoding string. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //ModR/M reg bits if used. /*------------------------------------------------------------------------------------------------------------------------- The fourth operand that is decoded in sequence is the first Immediate input if used. The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //First Immediate if used. /*------------------------------------------------------------------------------------------------------------------------- The fifth operand that is decoded in sequence is the second Immediate input if used. The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //Second Immediate if used (Note that the instruction "Enter" uses two immediate inputs). /*------------------------------------------------------------------------------------------------------------------------- The sixth operand that is decoded in sequence is the third Immediate input if used. The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //Third Immediate if used (Note that the Larrabee vector instructions can use three immediate inputs). /*------------------------------------------------------------------------------------------------------------------------- Vector adjustment codes allow the selection of the vector register value that is stored into variable VectorRegister that applies to the selected SSE instruction that is read after that uses it. The adjusted vector value is given to the function ^DecodeRegValue()^. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //Vector register if used. And if vector adjustments are applied to the SSE instruction. /*------------------------------------------------------------------------------------------------------------------------- Immediate Register encoding if used. During the decoding of the immediate operands the ^DecodeImmediate()^ function stores the read IMM into an variable called IMMValue. The upper four bits of IMMValue is given to the input RValue to the function ^DecodeRegValue()^. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //Immediate Register encoding if used. /*------------------------------------------------------------------------------------------------------------------------- It does not matter which order the explicit operands decode as they do not require reading another byte after the opcode. Explicit operands are selected internally in the cpu for instruction codes that only use one register, or pointer, or number input. -------------------------------------------------------------------------------------------------------------------------*/ new Operand(), //Explicit Operand one. new Operand(), //Explicit Operand two. new Operand(), //Explicit Operand three. new Operand() //Explicit Operand four. ]; /*------------------------------------------------------------------------------------------------------------------------- SizeAttrSelect controls the General arithmetic extended sizes "8/16/32/64", and SIMD Vector register extended sizes "128/256/512/1024". --------------------------------------------------------------------------------------------------------------------------- General arithmetic sizes "8/16/32/64" change by operand override which makes all operands go 16 bit. The width bit which is in the REX prefix makes operands go all 64 bits the changes depend on the instructions adjustable size. The value system goes as follows: 0=8, or 16, then 1=Default32, then 2=Max64. Smallest to largest in order. Changeable from prefixes. Code 66 hex is operand override, 48 hex is the REX.W setting. By default operands are 32 bit in 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. In the case of fewer size settings the size system aligns in order to the correct prefix settings. --------------------------------------------------------------------------------------------------------------------------- If 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. Also in 32 bit mode any size that is 64 changes to 32, but except for operands that do not use the BySize system. --------------------------------------------------------------------------------------------------------------------------- During Vector instructions size settings "128/256/512" use the SizeAttrSelect as the vector length setting as a 0 to 3 value from smallest to largest Note 1024 is Reserved the same system used for General arithmetic sizes "8/16/32/64" that go in order. If an operand is used that is 32/64 in size the Width bit allows to move between Sizes 32/64 separately. --------------------------------------------------------------------------------------------------------------------------- Used by the function ^GetOperandSize()^ which uses a fast base 2 logarithm system. The function ^DecodeOpcode()^ also uses the current size setting for operation names that change name by size, Or In vector instructions the select instruction by size is used to Add additional instructions between the width bit (W=0), and (W=1). -------------------------------------------------------------------------------------------------------------------------*/ var SizeAttrSelect = 1; /*------------------------------------------------------------------------------------------------------------------------- The Width bit is used in combination with SizeAttrSelect only with Vector instructions. -------------------------------------------------------------------------------------------------------------------------*/ var WidthBit = 0; /*------------------------------------------------------------------------------------------------------------------------- Pointer size plus 16 bit's used by FAR JUMP and other instructions. For example FAR JUMP is size attributes 16/32/64 normally 32 is the default size, but it is 32+16=48 FWORD PTR. In 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. Set by the function ^DecodeOperandString()^ for if the ModR/M operand type is far pointer address. --------------------------------------------------------------------------------------------------------------------------- Used by the function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ var FarPointer = 0; /*------------------------------------------------------------------------------------------------------------------------- AddressOverride is hex opcode 67 then when used with any operation that uses the ModR/M in address mode the ram address goes down one in bit mode. Switches 64 address mode to 32 bit address mode, and in 32 bit mode the address switches to 16 bit address mode which uses a completely different ModR/M format. When in 16 bit mode the address switches to 32 bit. Set true when Opcode 67 is read by ^DecodePrefixAdjustments()^ which effects the next opcode that is not a prefix opcode then is set false after instruction decodes. --------------------------------------------------------------------------------------------------------------------------- Used by the function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ var AddressOverride = false; /*------------------------------------------------------------------------------------------------------------------------- Extended Register value changes by the "R bit" in the REX prefix, or by the "Double R bit" settings in EVEX Extension which makes the Register operand reach to a max value of 32 registers along the register array. Normally the Register selection in ModR/M, is limited to three bits in binary 000 = 0 to 111 = 7. RegExtend stores the two binary bits that are added onto the three bit register selection. --------------------------------------------------------------------------------------------------------------------------- When RegExtend is 00,000 the added lower three bits is 00,000 = 0 to 00,111 = 7. When RegExtend is 01,000 the added lower three bits is 01,000 = 8 to 01,111 = 15. When RegExtend is 10,000 the added lower three bits is 10,000 = 16 to 10,111 = 23. When RegExtend is 11,000 the added lower three bits is 11,000 = 24 to 10,111 = 31. --------------------------------------------------------------------------------------------------------------------------- The Register expansion bits make the binary number from a 3 bit number to a 5 bit number by combining the EVEX.R'R bits. The REX opcode, and EVEX opcode 62 hex are decoded with function ^DecodePrefixAdjustments()^ which contain R bit settings. --------------------------------------------------------------------------------------------------------------------------- Used by function ^DecodeRegValue()^. -------------------------------------------------------------------------------------------------------------------------*/ var RegExtend = 0; /*------------------------------------------------------------------------------------------------------------------------- The base register is used in ModR/M address mode, and Register mode and can be extended to 8 using the "B bit" setting from the REX prefix, or VEX Extension, and EVEX Extension, however in EVEX the tow bits "X, and B" are used together to make the base register reach 32 in register value if the ModR/M is in Register mode. --------------------------------------------------------------------------------------------------------------------------- The highest the Base Register can be extended is from a 3 bit number to a 5 bit number. --------------------------------------------------------------------------------------------------------------------------- Used by the function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ var BaseExtend = 0; /*------------------------------------------------------------------------------------------------------------------------- The 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. The Index register can be extended to 8 using the "X bit" setting when the Index register is used. The X bit setting is used in the REX prefix settings, and also the VEX Extension, and EVEX Extension. --------------------------------------------------------------------------------------------------------------------------- The highest the Index Register can be extended is from a 3 bit number to a 4 bit number. --------------------------------------------------------------------------------------------------------------------------- Used by the function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ var IndexExtend = 0; /*------------------------------------------------------------------------------------------------------------------------- SegOverride is the bracket that is added onto the start of the decoded address it is designed this way so that if a segment Override Prefix is used it is stored with the segment. --------------------------------------------------------------------------------------------------------------------------- used by function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ var SegOverride = "["; /*------------------------------------------------------------------------------------------------------------------------- This may seem confusing, but the 8 bit high low registers are used all in "low order" when any REX prefix is used. Set RexActive true when the REX Prefix is used, for the High, and low Register separation. --------------------------------------------------------------------------------------------------------------------------- Used by function ^DecodeRegValue()^. -------------------------------------------------------------------------------------------------------------------------*/ var RexActive = 0; /*------------------------------------------------------------------------------------------------------------------------- The SIMD value is set according to SIMD MODE by prefixes (none, 66, F2, F3), or by the value of VEX.pp, and EVEX.pp. Changes the selected instruction in ^DecodeOpcode()^ only for SSE vector opcodes that have 4 possible instructions in one instruction for the 4 modes otherwise 66 is Operand override, and F2 is REPNE, and F3 is REP prefix adjustments. By reusing some of the already used Prefix adjustments more opcodes did not have to be sacrificed. --------------------------------------------------------------------------------------------------------------------------- SIMD is set 00 in binary by default, SIMD is set 01 in binary when opcode 66 is read by ^DecodePrefixAdjustments()^, SIMD is set 10 in binary when opcode F2 is read by ^DecodePrefixAdjustments()^, and SIMD is set 11 in binary when F3 is read by ^DecodePrefixAdjustments()^. --------------------------------------------------------------------------------------------------------------------------- The VEX, and EVEX adjustment codes contain SIMD mode adjustment bits in which each code that is used to change the mode go in the same order as SIMD. This allows SIMD to be set directly by the VEX.pp, and EVEX.pp bit value. --------------------------------------------------------------------------------------------------------------------------- VEX.pp = 00b (None), 01b (66h), 10b (F2h), 11b (F3h) EVEX.pp = 00b (None), 01b (66h), 10b (F2h), 11b (F3h) --------------------------------------------------------------------------------------------------------------------------- Used by the function ^DecodeOpcode()^. -------------------------------------------------------------------------------------------------------------------------*/ var SIMD = 0; /*------------------------------------------------------------------------------------------------------------------------- Vect is set true during the decoding of an instruction code. If the instruction is an Vector instruction 4 in length for the four modes then Vect is set true. When Vect is set true the Function ^Decode_ModRM_SIB_Address()^ Will decode the ModR/M as a Vector address. --------------------------------------------------------------------------------------------------------------------------- Set By function ^DecodeOpcode()^, and used by function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ var Vect = false; /*------------------------------------------------------------------------------------------------------------------------- In 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. Modes 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 most AVX512 instructions unless the width bit is ignored. Some AVX512 vectors can also broadcast round to there extend data size controlled by the width bit extend size and SIMD mode. -------------------------------------------------------------------------------------------------------------------------*/ var IgnoresWidthbit = false; /*------------------------------------------------------------------------------------------------------------------------- The VSIB setting is used for vectors that multiply the displacement by the Element size of the vectors, and use index as an vector pointer. -------------------------------------------------------------------------------------------------------------------------*/ var VSIB = false; /*------------------------------------------------------------------------------------------------------------------------- EVEX also has error suppression modes {ER} controlled by vector length, and if the broadcast round is active in register mode, or {SAE} suppresses all exceptions then it can not change rounding mode by vector length. MVEX also has error suppression modes {ER} controlled by conversion mode, and if the MVEX.E bit is set to round in register mode, or {SAE} suppresses all exceptions then it can not change rounding mode by vector length. L1OM vectors use {ER} as round control, and {SEA} as exponent adjustment. -------------------------------------------------------------------------------------------------------------------------*/ var RoundingSetting = 0; //1 = SAE, and 2 = ER. /*------------------------------------------------------------------------------------------------------------------------- The MVEX prefix can Integer convert, and Float convert, and Broadcast round using Swizzle. The EVEX prefix can only Broadcast round using an "b" control which sets the Broadcast round option for Swizzle. -------------------------------------------------------------------------------------------------------------------------*/ var Swizzle = false; //Swizzle based instruction. If false then Up, or Down conversion. var Up = false; //Only used if Swizzle is false. If set false then it is an down conversion. var Float = false; //If False Integer data is used. var VectS = 0x00; //Stores the three vector settings Swizzle, Up, and Float, for faster comparison to special cases. /*------------------------------------------------------------------------------------------------------------------------- The Extension is set 2 during opcode 62 hex for EVEX in which the ^DecodePrefixAdjustments()^ decodes the settings, but if the bit that must be set 0 for EVEX is set 1 then Extension is set 3 for MVEX. The Extension is set 1 during opcodes C4, and C5 hex in which the ^DecodePrefixAdjustments()^ decodes the settings for the VEX prefixes. --------------------------------------------------------------------------------------------------------------------------- An instruction that has 4 opcode combinations based on SIMD can use another 4 in length separator in the select SIMD mode which selects the opcode based on extension used. This is used to separate codes that can be Vector adjusted, and not. Some 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, also MMX instruction can not be used with vector adjustments. --------------------------------------------------------------------------------------------------------------------------- By default Extension is 0 for decoding instructions normally. --------------------------------------------------------------------------------------------------------------------------- Used by function ^DecodeOpcode()^ adds the letter "V" to the instruction name to show it uses Vector adjustments. When the Function ^DecodeOpcode()^ completes if Vect is not true and an Extension is active the instruction is invalid. Used By function ^DecodeOperandString()^ which allows the Vector operand to be used if existent in the operand string. -------------------------------------------------------------------------------------------------------------------------*/ var Extension = 0; /*------------------------------------------------------------------------------------------------------------------------- MVEX/EVEX conversion modes. MVEX can directly set the conversion mode between float, or integer, to broadcast round using option bits. The EVEX Extension only has the broadcast rounding control. In which some instructions support "]{1to16}" (B32), or "]{1to8}" (B64) Based on the data size using the width bit setting. EVEX only can use the 1ToX broadcast round control. --------------------------------------------------------------------------------------------------------------------------- Used by function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ var ConversionMode = 0; /*------------------------------------------------------------------------------------------------------------------------- MVEX/EVEX rounding modes. In EVEX if the ModR/M is used in register mode and Bround Is active. The EVEX Error Suppression type is set by the RoundingSetting {ER}, and {SAE} settings for if the instruction supports it. The MVEX version allows the use of both rounding modes. MVEX can select the rounding type using option bits if the "MVEX.E" control is set in an register to register operation. --------------------------------------------------------------------------------------------------------------------------- The function ^Decode_ModRM_SIB_Address()^ sets RoundMode. The function DecodeInstruction() adds the error Suppression to the end of the instruction. -------------------------------------------------------------------------------------------------------------------------*/ var RoundMode = 0; /*------------------------------------------------------------------------------------------------------------------------- MVEX/EVEX register round modes. --------------------------------------------------------------------------------------------------------------------------- Some instructions use SAE which suppresses all errors, but if an instruction uses {er} the 4 others are used by vector length. -------------------------------------------------------------------------------------------------------------------------*/ const RoundModes = [ "","","","","","","","", //First 8 No rounding mode. /*------------------------------------------------------------------------------------------------------------------------- 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. -------------------------------------------------------------------------------------------------------------------------*/ ", {Error}", ", {Error}", ", {Error}", ", {Error}", ", {SAE}", ", {SAE}", ", {SAE}", ", {SAE}", /*------------------------------------------------------------------------------------------------------------------------- L1OM/MVEX/EVEX round modes {ER}. L1OM uses the first 4, and EVEX uses the upper 4, while MVEX can use all 8. -------------------------------------------------------------------------------------------------------------------------*/ ", {RN}", ", {RD}", ", {RU}", ", {RZ}", ", {RN-SAE}", ", {RD-SAE}", ", {RU-SAE}", ", {RZ-SAE}", /*------------------------------------------------------------------------------------------------------------------------- MVEX/EVEX round modes {SAE}, {ER} Both rounding modes can not possibly be set both at the same time. -------------------------------------------------------------------------------------------------------------------------*/ "0B", "4B", "5B", "8B", "16B", "24B", "31B", "32B" //L1OM exponent adjustments. ]; /*------------------------------------------------------------------------------------------------------------------------- L1OM/MVEX register swizzle modes. When an swizzle operation is done register to register. Note L1OM skips swizzle type DACB thus the last swizzle type is an repeat of the DACB as the last L1OM swizzle. -------------------------------------------------------------------------------------------------------------------------*/ const RegSwizzleModes = [ "", "CDAB", "BADC", "DACB", "AAAA", "BBBB", "CCCC", "DDDD", "DACB" ]; /*------------------------------------------------------------------------------------------------------------------------- EVEX does not support conversion modes. Only broadcast round of 1To16, or 1To8 controlled by the data size. --------------------------------------------------------------------------------------------------------------------------- MVEX.sss permits the use of conversion types by value without relating to the Swizzle conversion type. However During Up, and Down conversion MVEX does not allow Broadcast round control. --------------------------------------------------------------------------------------------------------------------------- L1OM.CCCCC can only be used with Up, and Down conversion data types, and L1OM.sss can only be used with broadcast round. L1OM.SSS can only be used with swizzle conversions. --------------------------------------------------------------------------------------------------------------------------- The 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". The Width bit also relates to the Up conversion, and down conversion data size. Currently in K1OM, and L1OM there are no 64 bit Up, or Down conversions. --------------------------------------------------------------------------------------------------------------------------- Note 66 hex is used as data size 64 in L1OM. --------------------------------------------------------------------------------------------------------------------------- The element to grab from the array bellow is calculated mathematically. Note each element is an multiple of 2 in which the first element is the 32 size, and second element is 64 size. Lastly the elements are in order to the "CCCCC" value, and "SSS" value times 2, and plus 1 if 64 data size. -------------------------------------------------------------------------------------------------------------------------*/ const ConversionModes = [ //------------------------------------------------------------------------ "", "", //Not used. //------------------------------------------------------------------------ "1To16", "1To8", //Settable as L1OM.sss/MVEX.sss = 001. Settable using EVEX broadcast round. "4To16", "4To8", //Settable as L1OM.sss/MVEX.sss = 010. Settable using EVEX broadcast round. //------------------------------------------------------------------------ "Float16", "Error", //Settable as "MVEX.sss = 011", and "L1OM.sss = 110 , L1OM.CCCCC = 00001". //------------------------------------------------------------------------ "Float16RZ", "Error", //Settable only as L1OM.CCCCC = 00010. //------------------------------------------------------------------------ "SRGB8", "Error", //Settable only as L1OM.CCCCC = 00011. /*------------------------------------------------------------------------ MVEX/L1OM Up conversion, and down conversion types. ------------------------------------------------------------------------*/ "UInt8", "Error", //Settable as L1OM.sss/MVEX.sss = 100, and L1OM.CCCCC = 00100. "SInt8", "Error", //Settable as L1OM.sss/MVEX.sss = 101, and L1OM.CCCCC = 00101. //------------------------------------------------------------------------ "UNorm8", "Error", //Settable as L1OM.sss = 101, or L1OM.CCCCC = 00110. "SNorm8", "Error", //Settable as L1OM.CCCCC = 00111. //------------------------------------------------------------------------ "UInt16", "Error", //Settable as L1OM.sss/MVEX.sss = 110, and L1OM.CCCCC = 01000 "SInt16", "Error", //Settable as L1OM.sss/MVEX.sss = 111, and L1OM.CCCCC = 01001 //------------------------------------------------------------------------ "UNorm16", "Error", //Settable as L1OM.CCCCC = 01010. "SNorm16", "Error", //Settable as L1OM.CCCCC = 01011. "UInt8I", "Error", //Settable as L1OM.CCCCC = 01100. "SInt8I", "Error", //Settable as L1OM.CCCCC = 01101. "UInt16I", "Error", //Settable as L1OM.CCCCC = 01110. "SInt16I", "Error", //Settable as L1OM.CCCCC = 01111. /*------------------------------------------------------------------------ L1OM Up conversion, and field conversion. ------------------------------------------------------------------------*/ "UNorm10A", "Error", //Settable as L1OM.CCCCC = 10000. Also Usable as Integer Field control. "UNorm10B", "Error", //Settable as L1OM.CCCCC = 10001. Also Usable as Integer Field control. "UNorm10C", "Error", //Settable as L1OM.CCCCC = 10010. Also Usable as Integer Field control. "UNorm2D", "Error", //Settable as L1OM.CCCCC = 10011. Also Usable as Integer Field control. //------------------------------------------------------------------------ "Float11A", "Error", //Settable as L1OM.CCCCC = 10100. Also Usable as Float Field control. "Float11B", "Error", //Settable as L1OM.CCCCC = 10101. Also Usable as Float Field control. "Float10C", "Error", //Settable as L1OM.CCCCC = 10110. Also Usable as Float Field control. "Error", "Error", //Settable as L1OM.CCCCC = 10111. Also Usable as Float Field control. /*------------------------------------------------------------------------ Unused Conversion modes. ------------------------------------------------------------------------*/ "Error", "Error", //Settable as L1OM.CCCCC = 11000. "Error", "Error", //Settable as L1OM.CCCCC = 11001. "Error", "Error", //Settable as L1OM.CCCCC = 11010. "Error", "Error", //Settable as L1OM.CCCCC = 11011. "Error", "Error", //Settable as L1OM.CCCCC = 11100. "Error", "Error", //Settable as L1OM.CCCCC = 11101. "Error", "Error", //Settable as L1OM.CCCCC = 11110. "Error", "Error" //Settable as L1OM.CCCCC = 11111. ]; /*------------------------------------------------------------------------------------------------------------------------- The VEX Extension, and MVEX/EVEX Extension have an Vector register selection built in for Vector operation codes that use the vector register. This operand is only read in the "operand string" if an VEX, or EVEX prefix was decoded by the function ^DecodePrefixAdjustments()^, and making Extension 1 for VEX, or 2 for EVEX instead of 0 by default. During 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 in EVEX and MVEX to 32 by adding the EVEX.V, or MVEX.V bit to the vector register value. --------------------------------------------------------------------------------------------------------------------------- Used with the function ^DecodeRegValue()^ to decode the Register value. -------------------------------------------------------------------------------------------------------------------------*/ var VectorRegister = 0; /*------------------------------------------------------------------------------------------------------------------------- The MVEX/EVEX Extension has an mask Register value selection for {K0-K7} mask to destination operand. The K mask register is always displayed to the destination operand in any Vector instruction used with MVEX/EVEX settings. --------------------------------------------------------------------------------------------------------------------------- The {K} is added onto the first operand in OpNum before returning the decoded operands from the function ^DecodeOperands()^. -------------------------------------------------------------------------------------------------------------------------*/ var MaskRegister = 0; /*------------------------------------------------------------------------------------------------------------------------- The EVEX Extension has an zero mask bit setting for {z} zeroing off the registers. --------------------------------------------------------------------------------------------------------------------------- The {z} is added onto the first operand in OpNum before returning the decoded operands from the function ^DecodeOperands()^. --------------------------------------------------------------------------------------------------------------------------- In L1OM/MVEX this is used as the {NT}/{EH} control which when used with an memory address that supports it will prevent the data from going into the cache memory. Used as Hint control in the function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ var HInt_ZeroMerg = false; /*------------------------------------------------------------------------------------------------------------------------- Some operands use the value of the Immediate operand as an opcode, or upper 4 bits as Another register, or condition codes. The Immediate is decoded normally, but this variable stores the integer value of the first IMM byte for the other byte encodings if used. --------------------------------------------------------------------------------------------------------------------------- Used By the function ^DecodeOpcode()^ for condition codes, and by ^DecodeOperands()^ using the upper four bits as a register. -------------------------------------------------------------------------------------------------------------------------*/ var IMMValue = 0; /*------------------------------------------------------------------------------------------------------------------------- Prefix G1, and G2 are used with Intel HLE, and other prefix codes such as repeat the instruction Codes F2, F3 which can be applied to any instruction unless it is an SIMD instruction which uses F2, and F3 as the SIMD Mode. -------------------------------------------------------------------------------------------------------------------------*/ var PrefixG1 = "", PrefixG2 = ""; /*------------------------------------------------------------------------------------------------------------------------- Intel HLE is used with basic arithmetic instructions like Add, and subtract, and shift operations. Intel HLE instructions replace the Repeat F2, and F3, also lock F0 with XACQUIRE, and XRELEASE. --------------------------------------------------------------------------------------------------------------------------- This is used by function ^DecodeInstruction()^. -------------------------------------------------------------------------------------------------------------------------*/ var XRelease = false, XAcquire = false; /*------------------------------------------------------------------------------------------------------------------------- Intel HLE flip "G1 is used as = REP (XACQUIRE), or RENP (XRELEASE)", and "G2 is used as = LOCK" if the lock prefix was not read first then G1, and G2 flip. Also XACQUIRE, and XRELEASE replace REP, and REPNE if the LOCK prefix is used with REP, or REPNE if the instruction supports Intel HLE. --------------------------------------------------------------------------------------------------------------------------- This is used by function ^DecodeInstruction()^. -------------------------------------------------------------------------------------------------------------------------*/ var HLEFlipG1G2 = false; /*------------------------------------------------------------------------------------------------------------------------- Replaces segment overrides CS, and DS with HT, and HNT prefix for Branch taken and not taken used by jump instructions. --------------------------------------------------------------------------------------------------------------------------- This is used by functions ^Decode_ModRM_SIB_Address()^, and ^DecodeInstruction()^. -------------------------------------------------------------------------------------------------------------------------*/ var HT = false; /*------------------------------------------------------------------------------------------------------------------------- Instruction that support MPX replace the REPNE prefix with BND if operation is a MPX instruction. --------------------------------------------------------------------------------------------------------------------------- This is used by function ^DecodeInstruction()^. -------------------------------------------------------------------------------------------------------------------------*/ var BND = false; /*------------------------------------------------------------------------------------------------------------------------- The Invalid Instruction variable is very important as some bit settings in vector extensions create invalid operation codes. Also some opcodes are invalid in different cpu bit modes. --------------------------------------------------------------------------------------------------------------------------- Function ^DecodePrefixAdjustments()^ Set the Invalid Opcode if an instruction or prefix is compared that is invalid for CPU bit mode. The function ^DecodeInstruction()^ returns an invalid instruction if Invalid Operation is used for CPU bit mode. -------------------------------------------------------------------------------------------------------------------------*/ var InvalidOp = false; /*------------------------------------------------------------------------------------------------------------------------- The Register array holds arrays in order from 0 though 7 for the GetOperandSize function Which goes by Prefix size settings, and SIMD Vector length instructions using the adjusted variable SizeAttrSelect. --------------------------------------------------------------------------------------------------------------------------- Used by functions ^DecodeRegValue()^, ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ const REG = [ /*------------------------------------------------------------------------------------------------------------------------- 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 Arithmetic registers names. Note that these same registers can be made 16 bit across instead of using just the first 8 bit in size it depends on the instruction codes extension size. --------------------------------------------------------------------------------------------------------------------------- The function ^GetOperandSize()^ takes the size value the instruction uses for it's register selection by looking up binary bit positions in the size value in log 2. Different instructions can be adjusted to different sizes using the operand size override adjustment code, or width bit to adjust instructions to 64 in size introduced by AMD64, and EM64T in 64 bit computers. --------------------------------------------------------------------------------------------------------------------------- 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 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 used then the first 8 bit's is used by all general use arithmetic registers. Because of this the array is broken into two name listings that is used with the "RValue" number given to the function ^DecodeRegValue()^. -------------------------------------------------------------------------------------------------------------------------*/ [ /*------------------------------------------------------------------------------------------------------------------------- 8 bit registers without any rex prefix active is the normal low byte to high byte order of the first 4 general use registers "A, C, D, and B" using 8 bits. -------------------------------------------------------------------------------------------------------------------------*/ [ //Registers 8 bit names without any rex prefix index 0 to 7. "AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH" ], /*------------------------------------------------------------------------------------------------------------------------- 8 bit registers with any rex prefix active uses all 15 registers in low byte order. -------------------------------------------------------------------------------------------------------------------------*/ [ //Registers 8 bit names with any rex prefix index 0 to 7. "AL", "CL", "DL", "BL", "SPL", "BPL", "SIL", "DIL", /*------------------------------------------------------------------------------------------------------------------------- Registers 8 bit names Extended using the REX.R extend setting in the Rex prefix, or VEX.R bit, or EVEX.R. What ever RegExtend is set based on prefix settings is added to the select Reg Index -------------------------------------------------------------------------------------------------------------------------*/ "R8B", "R9B", "R10B", "R11B", "R12B", "R13B", "R14B", "R15B" ] ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 1 Is used only if the value returned from the GetOperandSize function is 1 in value in which bellow is the general use Arithmetic register names 16 in size. -------------------------------------------------------------------------------------------------------------------------*/ [ //Registers 16 bit names index 0 to 15. "AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI", "R8W", "R9W", "R10W", "R11W", "R12W", "R13W", "R14W", "R15W" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 2 Is used only if the value from the GetOperandSize function is 2 in value in which bellow is the general use Arithmetic register names 32 in size. -------------------------------------------------------------------------------------------------------------------------*/ [ //Registers 32 bit names index 0 to 15. "EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI", "R8D", "R9D", "R10D", "R11D", "R12D", "R13D", "R14D", "R15D" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 3 Is used only if the value returned from the GetOperandSize function is 3 in value in which bellow is the general use Arithmetic register names 64 in size. -------------------------------------------------------------------------------------------------------------------------*/ [ //general use Arithmetic registers 64 names index 0 to 15. "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 4 SIMD registers 128 across in size names. The SIMD registers are used by the SIMD Vector math unit. Used only if the value from the GetOperandSize function is 4 in value. -------------------------------------------------------------------------------------------------------------------------*/ [ //Register XMM names index 0 to 15. "XMM0", "XMM1", "XMM2", "XMM3", "XMM4", "XMM5", "XMM6", "XMM7", "XMM8", "XMM9", "XMM10", "XMM11", "XMM12", "XMM13", "XMM14", "XMM15", /*------------------------------------------------------------------------------------------------------------------------- Register XMM names index 16 to 31. Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. -------------------------------------------------------------------------------------------------------------------------*/ "XMM16", "XMM17", "XMM18", "XMM19", "XMM20", "XMM21", "XMM22", "XMM23", "XMM24", "XMM25", "XMM26", "XMM27", "XMM28", "XMM29", "XMM30", "XMM31" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 5 SIMD registers 256 across in size names. Used only if the value from the GetOperandSize function is 5 in value. Set by vector length setting. -------------------------------------------------------------------------------------------------------------------------*/ [ //Register YMM names index 0 to 15. "YMM0", "YMM1", "YMM2", "YMM3", "YMM4", "YMM5", "YMM6", "YMM7", "YMM8", "YMM9", "YMM10", "YMM11", "YMM12", "YMM13", "YMM14", "YMM15", /*------------------------------------------------------------------------------------------------------------------------- Register YMM names index 16 to 31. Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. -------------------------------------------------------------------------------------------------------------------------*/ "YMM16", "YMM17", "YMM18", "YMM19", "YMM20", "YMM21", "YMM22", "YMM23", "YMM24", "YMM25", "YMM26", "YMM27", "YMM28", "YMM29", "YMM30", "YMM31" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 6 SIMD registers 512 across in size names. Used only if the value from the GetOperandSize function is 6 in value. Set by Vector length setting. -------------------------------------------------------------------------------------------------------------------------*/ [ //Register ZMM names index 0 to 15. "ZMM0", "ZMM1", "ZMM2", "ZMM3", "ZMM4", "ZMM5", "ZMM6", "ZMM7", "ZMM8", "ZMM9", "ZMM10", "ZMM11", "ZMM12", "ZMM13", "ZMM14", "ZMM15", /*------------------------------------------------------------------------------------------------------------------------- Register ZMM names index 16 to 31. Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. -------------------------------------------------------------------------------------------------------------------------*/ "ZMM16", "ZMM17", "ZMM18", "ZMM19", "ZMM20", "ZMM21", "ZMM22", "ZMM23", "ZMM24", "ZMM25", "ZMM26", "ZMM27", "ZMM28", "ZMM29", "ZMM30", "ZMM31" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 7 SIMD registers 1024 bit. The SIMD registers have not been made this long yet. -------------------------------------------------------------------------------------------------------------------------*/ [ //Register unknowable names index 0 to 15. "?MM0", "?MM1", "?MM2", "?MM3", "?MM4", "?MM5", "?MM6", "?MM7", "?MM8", "?MM9", "?MM10", "?MM11", "?MM12", "?MM13", "?MM14", "?MM15", /*------------------------------------------------------------------------------------------------------------------------- Register unknowable names index 16 to 31. Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. -------------------------------------------------------------------------------------------------------------------------*/ "?MM16", "?MM17", "?MM18", "?MM19", "?MM20", "?MM21", "?MM22", "?MM23", "?MM24", "?MM25", "?MM26", "?MM27", "?MM28", "?MM29", "?MM30", "?MM31" ], /*------------------------------------------------------------------------------------------------------------------------- The Registers bellow do not change size they are completely separate, thus are used for special purposes. These registers are selected by using size as a value for the index instead instead of giving size to the function ^GetOperandSize()^. --------------------------------------------------------------------------------------------------------------------------- REG array Index 8 Segment Registers. -------------------------------------------------------------------------------------------------------------------------*/ [ //Segment Registers names index 0 to 7 "ES", "CS", "SS", "DS", "FS", "GS", "ST(-2)", "ST(-1)" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 9 Stack, and MM registers used by the X87 Float point unit. -------------------------------------------------------------------------------------------------------------------------*/ [ //ST registers Names index 0 to 7 //note these are used with the X87 FPU, but are aliased to MM in MMX SSE. "ST(0)", "ST(1)", "ST(2)", "ST(3)", "ST(4)", "ST(5)", "ST(6)", "ST(7)" ], /*------------------------------------------------------------------------------------------------------------------------- REG index 10 Intel MM qword technology MMX vector instructions. --------------------------------------------------------------------------------------------------------------------------- These can not be used with Vector length adjustment used in vector extensions. The MM register are the ST registers aliased to MM register. Instructions that use these registers use the SIMD vector unit registers (MM), these are called the old MMX vector instructions. When Intel added the SSE instructions to the SIMD math vector unit the new 128 bit XMM registers, are added into the SIMD unit then they ware made longer in size 256, then 512 across in length, with 1024 (?MM Reserved) In which the vector length setting was added to control there size though vector setting adjustment codes. Instruction that can be adjusted by vector length are separate from the MM registers, but still use the same SIMD unit. Because of this some Vector instruction codes can not be used with vector extension setting codes. -------------------------------------------------------------------------------------------------------------------------*/ [ //Register MM names index 0 to 7 "MM0", "MM1", "MM2", "MM3", "MM4", "MM5", "MM6", "MM7" ], /*------------------------------------------------------------------------------------------------------------------------- REG Array Index 11 bound registers introduced with MPX instructions. -------------------------------------------------------------------------------------------------------------------------*/ [ //BND0 to BND3,and CR0 to CR3 for two byte opcodes 0x0F1A,and 0x0F1B register index 0 to 7 "BND0", "BND1", "BND2", "BND3", "CR0", "CR1", "CR2", "CR3" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 12 control registers depending on the values they are set changes the modes of the CPU. -------------------------------------------------------------------------------------------------------------------------*/ [ //control Registers index 0 to 15 "CR0", "CR1", "CR2", "CR3", "CR4", "CR5", "CR6", "CR7", "CR8", "CR9", "CR10", "CR11", "CR12", "CR13", "CR14", "CR15" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 13 Debug mode registers. -------------------------------------------------------------------------------------------------------------------------*/ [ //debug registers index 0 to 15 "DR0", "DR1", "DR2", "DR3", "DR4", "DR5", "DR6", "DR7", "DR8", "DR9", "DR10", "DR11", "DR12", "DR13", "DR14", "DR15" ], /*------------------------------------------------------------------------------------------------------------------------- REG array Index 14 test registers. -------------------------------------------------------------------------------------------------------------------------*/ [ //TR registers index 0 to 7 "TR0", "TR1", "TR2", "TR3", "TR4", "TR5", "TR6", "TR7" ], /*------------------------------------------------------------------------------------------------------------------------- REG Array Index 15 SIMD vector mask registers. -------------------------------------------------------------------------------------------------------------------------*/ [ //K registers index 0 to 7, because of vector extensions it is repeated till last extension. "K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7","K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7", "K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7","K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7" ], /*------------------------------------------------------------------------------------------------------------------------- REG Array Index 16 SIMD L1OM vector registers. -------------------------------------------------------------------------------------------------------------------------*/ [ "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11", "V12", "V13", "V14", "V15", "V16", "V17", "V18", "V19", "V20", "V21", "V22", "V23", "V24", "V25", "V26", "V27", "V28", "V29", "V30", "V31" ] ]; /*------------------------------------------------------------------------------------------------------------------------- RAM Pointer sizes are controlled by the GetOperandSize function which uses the Size Setting attributes for the select pointer in the PTR array alignment. The REG array above uses the same alignment to the returned size attribute except address pointers have far address pointers which are 16 bits plus there (8, or 16)/32/64 size attribute. --------------------------------------------------------------------------------------------------------------------------- Far pointers add 16 bits to the default pointer sizes. 16 bits become 16+16=32 DWORD, 32 bits becomes 32+16=48 FWORD, and 64+16=80 TBYTE. The function GetOperandSize goes 0=8 bit, 1=16 bit, 2=32 bit, 3=64 bit, 4=128, 5=256, 6=512, 7=1024. --------------------------------------------------------------------------------------------------------------------------- The pointers are stored in doubles this is so every second position is each size setting. So the Returned size attribute has to be in multiples of 2 each size multiplied by 2 looks like this. (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. This is the same as moving by 2 this is why each pointer is in groups of two before the next line. When the 16 bit shift is used for far pointers only plus one is added for the 16 bit shifted name of the pointer. --------------------------------------------------------------------------------------------------------------------------- Used by the function ^Decode_ModRM_SIB_Address()^. -------------------------------------------------------------------------------------------------------------------------*/ const PTR = [ /*------------------------------------------------------------------------------------------------------------------------- Pointer array index 0 when GetOperandSize returns size 0 then times 2 for 8 bit pointer. In plus 16 bit shift array index 0 is added by 1 making 0+1=1 no pointer name is used. The blank pointer is used for instructions like LEA which loads the effective address. -------------------------------------------------------------------------------------------------------------------------*/ "BYTE PTR ","", /*------------------------------------------------------------------------------------------------------------------------- Pointer array index 2 when GetOperandSize returns size 1 then times 2 for 16 bit pointer alignment. 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). -------------------------------------------------------------------------------------------------------------------------*/ "WORD PTR ","DWORD PTR ", /*------------------------------------------------------------------------------------------------------------------------- Pointer array index 4 when GetOperandSize returns size 2 then multiply by 2 for index 4 for the 32 bit pointer. 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). -------------------------------------------------------------------------------------------------------------------------*/ "DWORD PTR ","FWORD PTR ", /*------------------------------------------------------------------------------------------------------------------------- Pointer array index 6 when GetOperandSize returns size 3 then multiply by 2 gives index 6 for the 64 bit pointer. The Non shifted 64 bit pointer has two types the 64 bit vector "MM", and regular "QWORD" the same as the REG array. 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). -------------------------------------------------------------------------------------------------------------------------*/ "QWORD PTR ","TBYTE PTR ", /*------------------------------------------------------------------------------------------------------------------------- Pointer array index 8 when GetOperandSize returns size 4 then multiply by 2 gives index 8 for the 128 bit Vector pointer. In far pointer shift the MMX vector pointer is used. MM is designed to be used when the by size system is false using index 9 for Pointer, and index 10 for Reg. -------------------------------------------------------------------------------------------------------------------------*/ "XMMWORD PTR ","MMWORD PTR ", /*------------------------------------------------------------------------------------------------------------------------- Pointer array index 10 when GetOperandSize returns size 5 then multiply by 2 gives index 10 for the 256 bit SIMD pointer. 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. -------------------------------------------------------------------------------------------------------------------------*/ "YMMWORD PTR ","OWORD PTR ", /*------------------------------------------------------------------------------------------------------------------------- Pointer array index 12 when GetOperandSize returns size 6 then multiply by 2 gives index 12 for the 512 bit pointer. 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). -------------------------------------------------------------------------------------------------------------------------*/ "ZMMWORD PTR ","ERROR PTR ", /*------------------------------------------------------------------------------------------------------------------------- Pointer array index 14 when GetOperandSize returns size 7 then multiply by 2 gives index 12 for the 1024 bit pointer. 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). -------------------------------------------------------------------------------------------------------------------------*/ "?MMWORD PTR ","ERROR PTR "]; /*------------------------------------------------------------------------------------------------------------------------- SIB byte scale Note the Scale bits value is the selected index of the array bellow only used under a Memory address that uses the SIB Address mode which uses another byte for the address selection. --------------------------------------------------------------------------------------------------------------------------- used by the ^Decode_ModRM_SIB_Address function()^. -------------------------------------------------------------------------------------------------------------------------*/ const scale = [ "", //when scale bits are 0 in value no scale multiple is used "*2", //when scale bits are 1 in value a scale multiple of times two is used "*4", //when scale bits are 2 in value a scale multiple of times four is used "*8" //when scale bits are 3 in value a scale multiple of times eight is used ]; /*------------------------------------------------------------------------------------------------------------------------- This function changes the Mnemonics array, for older instruction codes used by specific X86 cores that are under the same instruction codes. --------------------------------------------------------------------------------------------------------------------------- Input "type" can be any number 0 to 6. If the input is 0 it sets the mnemonics back to normal. If input "type" is set 1 it will adjust the few conflicting mask instructions to the K1OM instruction names used by the knights corner processor. If input "type" is set 2 it will adjust the mnemonic array to decode Larrabee instructions. If input "type" is set 3 it will adjust the mnemonic array to decode Cyrix instructions which are now deprecated from the architecture. If input "type" is set 4 it will adjust the mnemonic array to decode Geode instructions which are now deprecated from the architecture. If input "type" is set 5 it will adjust the mnemonic array to decode Centaur instructions which are now deprecated from the architecture. If 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. -------------------------------------------------------------------------------------------------------------------------*/ export function CompatibilityMode( type ) { //Reset the changeable sections of the Mnemonics array, and operand encoding array. Mnemonics[0x062] = ["BOUND","BOUND",""]; Mnemonics[0x110] = [["MOVUPS","MOVUPD","MOVSS","MOVSD"],["MOVUPS","MOVUPD","MOVSS","MOVSD"]]; Mnemonics[0x111] = [["MOVUPS","MOVUPD","MOVSS","MOVSD"],["MOVUPS","MOVUPD","MOVSS","MOVSD"]]; Mnemonics[0x112] = [["MOVLPS","MOVLPD","MOVSLDUP","MOVDDUP"],["MOVHLPS","???","MOVSLDUP","MOVDDUP"]]; Mnemonics[0x113] = [["MOVLPS","MOVLPD","???","???"],"???"]; Mnemonics[0x138] = ""; Mnemonics[0x139] = "???"; Mnemonics[0x13A] = ""; Mnemonics[0x13B] = "???"; Mnemonics[0x13C] = "???"; Mnemonics[0x13D] = "???"; Mnemonics[0x13F] = "???"; Mnemonics[0x141] = [["CMOVNO",["KANDW","","KANDQ"],"",""],["CMOVNO",["KANDB","","KANDD"],"",""],"",""]; Mnemonics[0x142] = [["CMOVB",["KANDNW","","KANDNQ"],"",""],["CMOVB",["KANDNB","","KANDND"],"",""],"",""]; Mnemonics[0x144] = [["CMOVE",["KNOTW","","KNOTQ"],"",""],["CMOVE",["KNOTB","","KNOTD"],"",""],"",""]; Mnemonics[0x145] = [["CMOVNE",["KORW","","KORQ"],"",""],["CMOVNE",["KORB","","KORD"],"",""],"",""]; Mnemonics[0x146] = [["CMOVBE",["KXNORW","","KXNORQ"],"",""],["CMOVBE",["KXNORB","","KXNORD"],"",""],"",""]; Mnemonics[0x147] = [["CMOVA",["KXORW","","KXORQ"],"",""],["CMOVA",["KXORB","","KXORD"],"",""],"",""]; Mnemonics[0x150] = ["???",[["MOVMSKPS","MOVMSKPS","",""],["MOVMSKPD","MOVMSKPD","",""],"???","???"]]; Mnemonics[0x151] = ["SQRTPS","SQRTPD","SQRTSS","SQRTSD"]; Mnemonics[0x152] = [["RSQRTPS","RSQRTPS","",""],"???",["RSQRTSS","RSQRTSS","",""],"???"]; Mnemonics[0x154] = ["ANDPS","ANDPD","???","???"]; Mnemonics[0x155] = ["ANDNPS","ANDNPD","???","???"]; Mnemonics[0x158] = [["ADDPS","ADDPS","ADDPS","ADDPS"],["ADDPD","ADDPD","ADDPD","ADDPD"],"ADDSS","ADDSD"]; Mnemonics[0x159] = [["MULPS","MULPS","MULPS","MULPS"],["MULPD","MULPD","MULPD","MULPD"],"MULSS","MULSD"]; Mnemonics[0x15A] = [["CVTPS2PD","CVTPS2PD","CVTPS2PD","CVTPS2PD"],["CVTPD2PS","CVTPD2PS","CVTPD2PS","CVTPD2PS"],"CVTSS2SD","CVTSD2SS"]; Mnemonics[0x15B] = [[["CVTDQ2PS","","CVTQQ2PS"],"CVTPS2DQ",""],"???","CVTTPS2DQ","???"]; Mnemonics[0x15C] = [["SUBPS","SUBPS","SUBPS","SUBPS"],["SUBPD","SUBPD","SUBPD","SUBPD"],"SUBSS","SUBSD"]; Mnemonics[0x15D] = ["MINPS","MINPD","MINSS","MINSD"]; Mnemonics[0x15E] = ["DIVPS","DIVPD","DIVSS","DIVSD"]; Mnemonics[0x178] = [["VMREAD","",["CVTTPS2UDQ","","CVTTPD2UDQ"],""],["EXTRQ","",["CVTTPS2UQQ","","CVTTPD2UQQ"],""],["???","","CVTTSS2USI",""],["INSERTQ","","CVTTSD2USI",""]]; Mnemonics[0x179] = [["VMWRITE","",["CVTPS2UDQ","","CVTPD2UDQ"],""],["EXTRQ","",["CVTPS2UQQ","","CVTPD2UQQ"],""],["???","","CVTSS2USI",""],["INSERTQ","","CVTSD2USI",""]]; Mnemonics[0x17A] = ["???",["","",["CVTTPS2QQ","","CVTTPD2QQ"],""],["","",["CVTUDQ2PD","","CVTUQQ2PD"],"CVTUDQ2PD"],["","",["CVTUDQ2PS","","CVTUQQ2PS"],""]]; Mnemonics[0x17B] = ["???",["","",["CVTPS2QQ","","CVTPD2QQ"],""],["","","CVTUSI2SS",""],["","","CVTUSI2SD",""]]; Mnemonics[0x17C] = ["???",["HADDPD","HADDPD","",""],"???",["HADDPS","HADDPS","",""]]; Mnemonics[0x17D] = ["???",["HSUBPD","HSUBPD","",""],"???",["HSUBPS","HSUBPS","",""]]; Mnemonics[0x17E] = [["MOVD","","",""],["MOVD","","MOVQ"],["MOVQ","MOVQ",["???","","MOVQ"],""],"???"], Mnemonics[0x190] = [["SETO",["KMOVW","","KMOVQ"],"",""],["SETO",["KMOVB","","KMOVD"],"",""],"",""]; Mnemonics[0x192] = [["SETB",["KMOVW","","???"],"",""],["SETB",["KMOVB","","???"],"",""],"",["SETB",["KMOVD","","KMOVQ"],"",""]]; Mnemonics[0x193] = [["SETAE",["KMOVW","","???"],"",""],["SETAE",["KMOVB","","???"],"",""],"",["SETAE",["KMOVD","","KMOVQ"],"",""]]; Mnemonics[0x198] = [["SETS",["KORTESTW","","KORTESTQ"],"",""],["SETS",["KORTESTB","","KORTESTD"],"",""],"",""]; Mnemonics[0x1A6] = "XBTS"; Mnemonics[0x1A7] = "IBTS"; Operands[0x110] = [["0B700770","0B700770","0A040603","0A040609"],["0B700770","0B700770","0A0412040604","0A0412040604"]]; Operands[0x111] = [["07700B70","07700B70","06030A04","06090A04"],["07700B70","07700B70","060412040A04","060412040A04"]]; Operands[0x112] = [["0A0412040606","0A0412040606","0B700770","0B700768"],["0A0412040604","","0B700770","0B700770"]]; Operands[0x113] = [["06060A04","06060A04","",""],""]; Operands[0x141] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; Operands[0x142] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; Operands[0x144] = [["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],"",""]; Operands[0x145] = [["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; Operands[0x146] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; Operands[0x147] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","",""],"",""],"",""]; Operands[0x150] = ["",[["0B0C0648","0B0C0730","",""],["0B0C0648","0B0C0730","",""],"",""]]; Operands[0x151] = ["0B7007700112","0B7007700112","0A04120406430102","0A04120406490102"]; Operands[0x152] = [["0A040648","0A040648","",""],"",["0A040643","0A0412040643","",""],""]; Operands[0x154] = ["0B70137007700110","0B70137007700110","",""]; Operands[0x155] = ["0B70137007700110","0B70137007700110","",""]; Operands[0x158] = [["0A040648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A040648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; Operands[0x159] = [["0A040648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A040648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; Operands[0x15A] = [["0A040648","0B300718","0B7007380111","0A06065A0111"],["0A040648","0B180730","0B3807700112","0A05066C0112"],"0A04120406430101","0A04120406460102"]; Operands[0x15B] = [[["0B7007700112","","0B380770011A"],"0B700770011A","",""],"","0B7007700111",""]; Operands[0x15C] = [["0A060648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A060648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; Operands[0x15D] = ["0B70137007700111","0B70137007700111","0A04120406430101","0A04120406460101"]; Operands[0x15E] = ["0B70137007700112","0B70137007700112","0A04120406430102","0A04120406460102"]; Operands[0x178] = [["07080B080180","",["0B7007700111","","0B3807700119"],""],["064F0C000C00","",["0B7007380119","","0B7007700111"],""],["","","0B0C06440109",""],["0A04064F0C000C00","","0B0C06460109",""]]; Operands[0x179] = [["0B0807080180","",["0B7007700112","","0B380770011A"],""],["0A04064F","",["0B700738011A","","0B7007700112"],""],["","","0B0C0644010A",""],["0A04064F","","0B0C0646010A",""]]; Operands[0x17A] = ["",["","",["0B7007380119","","0B7007700111"],""],["","",["0B7007380112","","0B700770011A"],"0A06065A0112"],["","",["0B700770011A","","0B3807700112"],""]]; Operands[0x17B] = ["",["","",["0B700738011A","","0B7007700112"],""],["","","0A041204070C010A",""],["","","0A041204070C010A",""]]; Operands[0x17C] = ["",["0A040604","0B7013700770","",""],"",["0A040604","0B7013700770","",""]]; Operands[0x17D] = ["",["0A040604","0B7013700770","",""],"",["0A040604","0B7013700770","",""]]; Operands[0x17E] = [["070C0A0A","","",""],["06240A040108","","06360A040108"],["0A040646","0A040646",["","","0A0406460108"],""],""]; Operands[0x190] = [["0600",["0A0F0612","","0A0F0636"],"",""],["0600",["0A0F0600","","0A0F0624"],"",""],"",""]; Operands[0x192] = [["0600",["0A0F06F4","",""],"",""],["0600",["0A0F06F4","",""],"",""],"",["0600",["0A0F06F6","","0A0F06F6"],"",""]]; Operands[0x193] = [["0600",["06F40A0F","",""],"",""],["0600",["06F40A0F","",""],"",""],"",["0600",["06F60A0F","","06F60A0F"],"",""]]; Operands[0x198] = [["0600",["0A0F06FF","","0A0F06FF"],"",""],["0600",["0A0F06FF","","0A0F06FF"],"",""],"",""]; Operands[0x1A6] = "0B0E070E"; Operands[0x1A7] = "070E0B0E"; //Adjust the VEX mask instructions for K1OM (Knights corner) which conflict with the enhanced AVX512 versions. if( type === 1 ) { Mnemonics[0x141] = [["CMOVNO","KAND","",""],"","",""]; Mnemonics[0x142] = [["CMOVB","KANDN","",""],"","",""]; Mnemonics[0x144] = [["CMOVE","KNOT","",""],"","",""]; Mnemonics[0x145] = [["CMOVNE","KOR","",""],"","",""]; Mnemonics[0x146] = [["CMOVBE","KXNOR","",""],"","",""]; Mnemonics[0x147] = [["CMOVA","KXOR","",""],"","",""]; Mnemonics[0x190] = [["SETO","KMOV","",""],"","",""]; Mnemonics[0x192] = [["SETB","KMOV","",""],"","",""]; Mnemonics[0x193] = [["SETAE","KMOV","",""],"","",""]; Mnemonics[0x198] = [["SETS","KORTEST","",""],"","",""]; Operands[0x141] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; Operands[0x142] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; Operands[0x144] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; Operands[0x145] = [["0A02070E0180","0A0F06FF","",""],"","",""]; Operands[0x146] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; Operands[0x147] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; Operands[0x190] = [["0600","0A0F06FF","",""],"","",""]; Operands[0x192] = [["0600","06FF0B06","",""],"","",""]; Operands[0x193] = [["0600","07060A0F","",""],"","",""]; Operands[0x198] = [["0600","0A0F06FF","",""],"","",""]; } //Disable Knights corner, and AVX512, for L1OM (Intel Larrabee). if( type === 2 ) { Mnemonics[0x62] = ""; } //Adjust the Mnemonics, and Operand encoding, for the Cyrix processors. if( type === 3 ) { Mnemonics[0x138] = "SMINT"; Mnemonics[0x13A] = "BB0_RESET"; Mnemonics[0x13B] = "BB1_RESET"; Mnemonics[0x13C] = "CPU_WRITE"; Mnemonics[0x13D] = "CPU_READ"; Mnemonics[0x150] = "PAVEB"; Mnemonics[0x151] = "PADDSIW"; Mnemonics[0x152] = "PMAGW"; Mnemonics[0x154] = "PDISTIB"; Mnemonics[0x155] = "PSUBSIW"; Mnemonics[0x158] = "PMVZB"; Mnemonics[0x159] = "PMULHRW"; Mnemonics[0x15A] = "PMVNZB"; Mnemonics[0x15B] = "PMVLZB"; Mnemonics[0x15C] = "PMVGEZB"; Mnemonics[0x15D] = "PMULHRIW"; Mnemonics[0x15E] = "PMACHRIW"; Mnemonics[0x178] = "SVDC"; Mnemonics[0x179] = "RSDC"; Mnemonics[0x17A] = "SVLDT"; Mnemonics[0x17B] = "RSLDT"; Mnemonics[0x17C] = "SVTS"; Mnemonics[0x17D] = "RSTS"; Mnemonics[0x17E] = "SMINT"; Operands[0x150] = "0A0A06A9"; Operands[0x151] = "0A0A06A9"; Mnemonics[0x152] = "0A0A06A9"; Operands[0x154] = "0A0A06AF"; Operands[0x155] = "0A0A06A9"; Operands[0x158] = "0A0A06AF"; Operands[0x159] = "0A0A06A9"; Mnemonics[0x15A] = "0A0A06AF"; Operands[0x15B] = "0A0A06AF"; Operands[0x15C] = "0A0A06AF"; Mnemonics[0x15D] = "0A0A06A9"; Operands[0x15E] = "0A0A06AF"; Operands[0x178] = "30000A08"; Operands[0x179] = "0A083000"; Operands[0x17A] = "3000"; Operands[0x17B] = "3000"; Operands[0x17C] = "3000"; Operands[0x17D] = "3000"; Operands[0x17E] = ""; } //Adjust the Mnemonics, and Operand encoding, for the Geode processor. if( type === 4 ) { Mnemonics[0x138] = "SMINT"; Mnemonics[0x139] = "DMINT"; Mnemonics[0x13A] = "RDM"; } //Adjust the Mnemonics, for the Centaur processor. if( type === 5 ) { Mnemonics[0x13F] = "ALTINST"; Mnemonics[0x1A6] = ["???",["MONTMUL","XSA1","XSA256","???","???","???","???","???"]]; Mnemonics[0x1A7] = [ "???", [ "XSTORE", ["???","???","XCRYPT-ECB","???"], ["???","???","XCRYPT-CBC","???"], ["???","???","XCRYPT-CTR","???"], ["???","???","XCRYPT-CFB","???"], ["???","???","XCRYPT-OFB","???"], "???", "???" ] ]; Operands[0x1A6] = ["",["","","","","","","",""]]; Operands[0x1A7] = [ "", [ "", ["","","",""], ["","","",""], ["","","",""], ["","","",""], ["","","",""], "", "" ] ]; } //Adjust the Mnemonics, for the X86/486 processor and older. if( type === 6 ) { Mnemonics[0x110] = "UMOV"; Mnemonics[0x111] = "UMOV"; Mnemonics[0x112] = "UMOV"; Mnemonics[0x113] = "UMOV"; Mnemonics[0x1A6] = "CMPXCHG"; Mnemonics[0x1A7] = "CMPXCHG"; Operands[0x110] = "06000A00"; Operands[0x111] = "070E0B0E"; Operands[0x112] = "0A000600"; Operands[0x113] = "0B0E070E"; Operands[0x1A6] = ""; Operands[0x1A7] = ""; } } /*------------------------------------------------------------------------------------------------------------------------- This function loads the BinCode array using an hex string as input, and Resets the Code position along the array, but does not reset the base address. This allows programs to be decoded in sections well maintaining the accurate 64 bit base address. --------------------------------------------------------------------------------------------------------------------------- The function "SetBasePosition()" sets the location that the Code is from in memory. The function "GotoPosition()" tests if the address is within rage of the current loaded binary. The function "GetPosition()" Gives back the current base address in it's proper format for the current BitMode. --------------------------------------------------------------------------------------------------------------------------- If the hex input is invalid returns false. -------------------------------------------------------------------------------------------------------------------------*/ export function LoadBinCode( HexStr ) { //Clear BinCode, and Reset Code Position in Bin Code array. BinCode = []; CodePos = 0; //Iterate though the hex string and covert to 0 to 255 byte values into the BinCode array. var len = HexStr.length; for( var i = 0, el = 0, Sign = 0, int32 = 0; i < len; i += 8 ) { //It is faster to read 8 hex digits at a time if possible. int32 = parseInt( HexStr.slice( i, i + 8 ), 16 ); //If input is invalid return false. if( isNaN( int32 ) ){ return ( false ); } //If the end of the Hex string is reached and is not 8 digits the number has to be lined up. ( ( len - i ) < 8 ) && ( int32 <<= ( 8 - len - i ) << 2 ); //The variable sing corrects the unusable sing bits during the 4 byte rotation algorithm. Sign = int32; //Remove the Sign bit value if active for when the number is changed to int32 during rotation. int32 ^= int32 & 0x80000000; //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. int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); BinCode[el++] = ( ( ( Sign >> 24 ) & 0x80 ) | int32 ) & 0xFF; int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); BinCode[el++] = ( ( ( Sign >> 16 ) & 0x80 ) | int32 ) & 0xFF; int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); BinCode[el++] = ( ( ( Sign >> 8 ) & 0x80 ) | int32 ) & 0xFF; int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); BinCode[el++] = ( ( Sign & 0x80 ) | int32 ) & 0xFF; } //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. len >>= 1; for(; len < BinCode.length; BinCode.pop() ); //Return true for that the binary code loaded properly. return ( true ); } /*------------------------------------------------------------------------------------------------------------------------- This function moves the address by one and caries to 64 section for the Base address. The BitMode settings limit how much of the 64 bit address is used in functions "GetPosition()", and "GotoPosition()", for the type of binary being disassemble. This function also moves the binary code array position CodePos by one basically this function is used to progress the disassembler as it is decoding a sequence of bytes. -------------------------------------------------------------------------------------------------------------------------*/ function NextByte() { //Add the current byte as hex to InstructionHex which will be displayed beside the decoded instruction. //After an instruction decodes InstructionHex is only added beside the instruction if ShowInstructionHex is active. var t; if ( CodePos < BinCode.length ) //If not out of bounds. { //Convert current byte to String, and pad. ( ( t = BinCode[CodePos++].toString(16) ).length === 1) && ( t = "0" + t ); //Add it to the current bytes used for the decode instruction. InstructionHex += t; //Continue the Base address. ( ( Pos32 += 1 ) > 0xFFFFFFFF ) && ( Pos32 = 0, ( ( Pos64 += 1 ) > 0xFFFFFFFF ) && ( Pos64 = 0 ) ); } } /*------------------------------------------------------------------------------------------------------------------------- Takes a 64/32/16 bit hex string and sets it as the address position depending on the address format it is split into an segment, and offset address. Note that the Code Segment is used in 16 bit code. Also code segment is also used in 32 bit if set 36, or higher. Effects instruction location in memory when decoding a program. -------------------------------------------------------------------------------------------------------------------------*/ export function SetBasePosition( Address ) { //Split the Segment:offset. var t = Address.split(":"); //Set the 16 bit code segment position if there is one. if ( typeof t[1] !== "undefined" ){ CodeSeg = parseInt( t[0].slice( t[0].length - 4 ), 16 ); Address = t[1]; } //Adjust the Instruction pointer 16(IP)/32(EIP)/64(RIP). Also varies based on Bit Mode. var Len = Address.length; if( Len >= 9 && BitMode == 2 ){ Pos64 = parseInt( Address.slice( Len - 16, Len - 8 ), 16 ); } if( Len >= 5 && BitMode >= 1 && !( BitMode == 1 & CodeSeg >= 36 ) ){ Pos32 = parseInt( Address.slice( Len - 8 ), 16 ); } else if( Len >= 1 && BitMode >= 0 ){ Pos32 = ( Pos32 & 0xFFFF0000 ) | ( parseInt( Address.slice( Len - 4 ), 16 ) ); } //Convert Pos32 to undignified integer. if ( Pos32 < 0 ) { Pos32 += 0x100000000; } } /*------------------------------------------------------------------------------------------------------------------------- Gives back the current Instruction address position. In 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. In 32 bit an instruction location uses the first 32 bit's of the instruction pointer. -------------------------------------------------------------------------------------------------------------------------*/ function GetPosition() { //If 16 bit Seg:Offset, or if 32 bit and CodeSeg is 36, or higher. if( BitMode === 0 | ( BitMode === 1 & CodeSeg >= 36 ) ) { for ( var S16 = ( Pos32 & 0xFFFF ).toString(16); S16.length < 4; S16 = "0" + S16 ); for ( var Seg = ( CodeSeg ).toString(16); Seg.length < 4; Seg = "0" + Seg ); return( ( Seg + ":" + S16 ).toUpperCase() ); } //32 bit, and 64 bit section. var S64="", S32=""; //If 32 bit or higher. if( BitMode >= 1 ) { for ( S32 = Pos32.toString(16); S32.length < 8; S32 = "0" + S32 ); } //If 64 bit. if( BitMode === 2 ) { for ( S64 = Pos64.toString(16); S64.length < 8; S64 = "0" + S64 ); } //Return the 32/64 address. return ( ( S64 + S32 ).toUpperCase() ); } /*------------------------------------------------------------------------------------------------------------------------- Moves the dissembler 64 bit address, and CodePos to correct address. Returns false if address location is out of bounds. -------------------------------------------------------------------------------------------------------------------------*/ function GotoPosition( Address ) { //Current address location. var LocPos32 = Pos32; var LocPos64 = Pos64; var LocCodeSeg = CodeSeg; //Split the by Segment:offset address format. var t = Address.split(":"); //Set the 16 bit code segment location if there is one. if ( typeof t[1] !== "undefined" ) { LocCodeSeg = parseInt(t[0].slice( t[0].length - 4 ), 16); Address = t[1]; } var len = Address.length; //If the address is 64 bit's long, and bit mode is 64 bit adjust the 64 bit location. if( len >= 9 && BitMode === 2 ) { LocPos64 = parseInt( Address.slice( len - 16, len - 8 ), 16 ); } //If the address is 32 bit's long, and bit mode is 32 bit, or higher adjust the 32 bit location. if( len >= 5 && BitMode >= 1 & !( BitMode === 1 & CodeSeg >= 36 ) ) { LocPos32 = parseInt( Address.slice( len - 8 ), 16 ); } //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. else if( len >= 1 && BitMode >= 0 ) { LocPos32 = ( LocPos32 - LocPos32 + parseInt( Address.slice( len - 4 ), 16 ) ); } //Find the difference between the current base address and the selected address location. var Dif32 = Pos32 - LocPos32, Dif64 = Pos64 - LocPos64; //Only calculate the Code Segment location if The program uses 16 bit address mode otherwise the //code segment does not affect the address location. if( ( BitMode === 1 & CodeSeg >= 36 ) || BitMode === 0 ) { Dif32 += ( CodeSeg - LocCodeSeg ) << 4; } //Before adjusting the Code Position Backup the Code Position in case that the address is out of bounds. t = CodePos; //Subtract the difference to the CodePos position. CodePos -= Dif64 * 4294967296 + Dif32; //If code position is out of bound for the loaded binary in the BinCode array, or //is a negative index return false and reset CodePos. if( CodePos < 0 || CodePos > BinCode.length ) { CodePos = t; return ( false ); } //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. CodeSeg = LocCodeSeg; Pos32 = LocPos32 Pos64 = LocPos64; //Return true for that the address Position is moved in range correctly. return ( true ); } /*------------------------------------------------------------------------------------------------------------------------- Finds bit positions to the Size attribute indexes in REG array, and the Pointer Array. For the Size Attribute variations. --------------------------------------------------------------------------------------------------------------------------- The SizeAttribute settings is 8 digits big consisting of 1, or 0 to specify the extended size that an operand can be made. In which an value of 01100100 is decoded as "0 = 1024, 1 = 512, 1 = 256, 0 = 128, 0 = 64, 1 = 32, 0 = 16, 0 = 8". In 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". In 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 from this function Lines up With the Pinter array, and Register array indexes for the register names by size, and Pointers. --------------------------------------------------------------------------------------------------------------------------- The variable SizeAttrSelect is separate from this function it is adjusted by prefixes that adjust Vector size, and General purpose registers. -------------------------------------------------------------------------------------------------------------------------*/ function GetOperandSize( SizeAttribute ) { /*---------------------------------------------------------------------------------------------------------------------------------------- Each S value goes in order to the vector length value in EVEX, and VEX Smallest to biggest in perfect alignment. 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. 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. ----------------------------------------------------------------------------------------------------------------------------------------*/ var S4 = 0, S3 = 0, S2 = 0, S1 = 0, S0 = -1; //Note S0 is Vector size 1024, which is unused. /*---------------------------------------------------------------------------------------------------------------------------------------- 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. In which this size attribute is only used when the extended size is active from the Rex prefix using the W (width) bit setting. 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 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. ----------------------------------------------------------------------------------------------------------------------------------------*/ S1 = SizeAttribute; S1 = ( ( S1 & 0xF0 ) !== 0 ? ( S1 >>= 4, 4 ) : 0 ) | ( ( S1 & 0xC ) !== 0 ? ( S1 >>= 2, 2 ) : 0 ) | ( ( S1 >>= 1 ) !== 0 ); /*---------------------------------------------------------------------------------------------------------------------------------------- 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. ----------------------------------------------------------------------------------------------------------------------------------------*/ if( SizeAttribute === 0 ) { S1 = -1; } /*---------------------------------------------------------------------------------------------------------------------------------------- Convert the Bit Position of S1 into it's value and remove it by subtracting it into the SizeAttribute settings. ----------------------------------------------------------------------------------------------------------------------------------------*/ SizeAttribute -= ( 1 << S1 ); /*---------------------------------------------------------------------------------------------------------------------------------------- Lookup the Highest Second active bit in the SizeAttribute value giving the position the bit is in the number. 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 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. In which 256 is the Mid vector size some vectors are smaller some go 64/128/256 in which the mid size is 128. ----------------------------------------------------------------------------------------------------------------------------------------*/ S2 = SizeAttribute; S2 = ( ( S2 & 0xF0 ) !== 0 ? ( S2 >>= 4, 4 ) : 0 ) | ( ( S2 & 0xC ) !== 0 ? ( S2 >>= 2, 2 ) : 0 ) | ( ( S2 >>= 1 ) !== 0 ); /*---------------------------------------------------------------------------------------------------------------------------------------- Convert the Bit Position of S2 into it's value and remove it by subtracting it if it is not 0. ----------------------------------------------------------------------------------------------------------------------------------------*/ if( S2 !== 0 ) { SizeAttribute -= ( 1 << S2 ); } /*---------------------------------------------------------------------------------------------------------------------------------------- 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. ----------------------------------------------------------------------------------------------------------------------------------------*/ else { S2 = S1; } /*---------------------------------------------------------------------------------------------------------------------------------------- Lookup the Highest third active bit in the SizeAttribute value giving the position the bit is in the number. The third Size is only used if the Operand override prefix is used setting SizeAttrSelect to 0, or if this is an vector the 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. ----------------------------------------------------------------------------------------------------------------------------------------*/ S3 = SizeAttribute; S3 = ( ( S3 & 0xF0 ) !== 0 ? ( S3 >>= 4, 4 ) : 0 ) | ( ( S3 & 0xC ) !== 0 ? ( S3 >>= 2, 2 ) : 0 ) | ( ( S3 >>= 1 ) !== 0 ); /*---------------------------------------------------------------------------------------------------------------------------------------- Convert the Bit Position of S3 into it's value and remove it by subtracting it if it is not 0. ----------------------------------------------------------------------------------------------------------------------------------------*/ if( S3 !== 0 ) { SizeAttribute -= ( 1 << S3 ); } /*---------------------------------------------------------------------------------------------------------------------------------------- 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. ----------------------------------------------------------------------------------------------------------------------------------------*/ else { S3 = S2; if( S2 !== 2 ) { S2 = S1; } }; //In 32/16 bit mode the operand size must never exceed 32. if ( BitMode <= 1 && S2 >= 3 && !Vect ) { if( ( S1 | S2 | S3 ) === S3 ){ S1 = 2; S3 = 2; } //If single size all adjust 32. S2 = 2; //Default operand size 32. } //In 16 bit mode The operand override is always active until used. This makes all operands 16 bit size. //When Operand override is used it is the default 32 size. Flip S3 with S2. if( BitMode === 0 && !Vect ) { var t = S3; S3 = S2; S2 = t; } //If an Vect is active, then EVEX.W, VEX.W, or XOP.W bit acts as 32/64. if( ( Vect || Extension > 0 ) && ( ( S1 + S2 + S3 ) === 7 | ( S1 + S2 + S3 ) === 5 ) ) { Vect = false; return( ( [ S2, S1 ] )[ WidthBit & 1 ] ); } //If it is an vector, and Bround is active vector goes max size. if( Vect && ConversionMode === 1 ) { S0 = S1; S3 = S1; S2 = S1; } //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. //The only time S0 is not negative one is if vector broadcast round is active. return( ( [ S3, S2, S1, S0 ] )[ SizeAttrSelect ] ); } /*------------------------------------------------------------------------------------------------------------------------- This function returns an array with three numbers. --------------------------------------------------------------------------------------------------------------------------- The first element is the two bits for the ModR/M byte for Register mode, Memory mode, and Displacement settings, or the SIB byte scale as a number value 0 to 3 if it is not an ModR/M byte since they both use the same bit grouping. The 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. The 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. -------------------------------------------------------------------------------------------------------------------------*/ function Decode_ModRM_SIB_Value() { //Get the current position byte value var v = BinCode[CodePos]; //The first tow binary digits of the read byte is the Mode bits of the ModR/M byte or //The first tow binary digits of the byte is the Scale bits of the SIB byte. var ModeScale = (v >> 6) & 0x03; //value 0 to 3 //The three binary digits of the read byte after the first two digits is the OpcodeReg Value of the ModR/M byte or //The three binary digits of the read byte after the first two digits is the Index Register value for the SIB byte. var OpcodeRegIndex = (v >> 3) & 0x07; //value 0 to 7 //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 //The three binary digits at the end of the read byte is the Base Register Value of the SIB byte. var RMBase = v & 0x07; //value 0 to 7 //Put the array together containing the three indexes with the value //Note both the ModR/M byte and SIB byte use the same bit value pattern var ByteValueArray = [ ModeScale,//Index 0 is the first tow bits for the Mode, or Scale Depending on what the byte value is used for. OpcodeRegIndex,//Index 1 is the three bits for the OpcodeReg, or Index Depending on what the byte value is used for. RMBase //Index 2 is the three bits for the RM, or BASE bits Depending on what the byte value is used for. ]; //Move the Decoders Position by one. NextByte(); //return the array containing the decoded values of the byte. return (ByteValueArray); } /*------------------------------------------------------------------------------------------------------------------------- When input type is value 0 decode the immediate input regularly to it's size setting for accumulator Arithmetic, and IO. When input type is value 1 decode the immediate input regularly, but zeros out the upper 4 bits for Register encoding. When input type is value 2 decode the immediate as a relative address used by jumps, and function calls. When input type is value 3 decode the immediate as a Integer Used by Displacements. --------------------------------------------------------------------------------------------------------------------------- The function argument SizeSetting is the size attributes of the IMM that is decoded using the GetOperandSize function. The Imm uses two size setting, the first 4 bits are used for the Immediate actual adjustable sizes 8,16,32,64. --------------------------------------------------------------------------------------------------------------------------- If BySize is false the SizeSetting is used numerically as a single size selection as 0=8,1=16,2=32,3=64 by size setting value. -------------------------------------------------------------------------------------------------------------------------*/ function DecodeImmediate( type, BySize, SizeSetting ) { /*------------------------------------------------------------------------------------------------------------------------- Initialize V32, and V64 which will store the Immediate value. JavaScript Float64 numbers can not accurately work with numbers 64 bit's long. So numbers are split into two numbers that should never exceed an 32 bit value though calculation. Numbers that are too big for the first 32 bit's are stored as the next 32 bit's in V64. -------------------------------------------------------------------------------------------------------------------------*/ var V32 = 0, V64 = 0; //*Initialize the Pad Size for V32, and V64 depending On the Immediate type Calculation they use. var Pad32 = 0, Pad64 = 0; //*Initialize the Sign value that is only set for Negative, or Positive Relative displacements. var Sign = 0; //*Initialize the Sign Extend variable size as 0 Some Immediate numbers Sign extend. var Extend = 0; //*The variable S is the size of the Immediate. var S = SizeSetting & 0x0F; //*Extend size. Extend = SizeSetting >> 4; //*If by Size attributes is set true. if ( BySize ) { S = GetOperandSize( S ); if ( Extend > 0 ) { Extend = GetOperandSize( Extend ); } } /*------------------------------------------------------------------------------------------------------------------------- The possible values of S (Calculated Size) are S=0 is IMM8, S=1 is IMM16, S=2 is IMM32, S=3 is IMM64. Calculate how many bytes that are going to have to be read based on the value of S. S=0 is 1 byte, S=1 is 2 bytes, S=2 is 4 bytes, S=3 is 8 bytes. The Number of bytes to read is 2 to the power of S. -------------------------------------------------------------------------------------------------------------------------*/ var n = 1 << S; //Adjust Pad32, and Pad64. Pad32 = Math.min( n, 4 ); ( n >= 8 ) && ( Pad64 = 8 ); //Store the first byte of the immediate because IMM8 can use different encodings. IMMValue = BinCode[CodePos]; //*Loop and Move the Decoder to the next byte Code position to the number of bytes to read for V32, and V64. for ( var i = 0, v = 1; i < Pad32; V32 += BinCode[CodePos] * v, i++, v *= 256, NextByte() ); for ( v = 1; i < Pad64; V64 += BinCode[CodePos] * v, i++, v *= 256, NextByte() ); //*Adjust Pad32 so it matches the length the Immediate should be in hex for number of bytes read. Pad32 <<= 1; Pad64 <<= 1; /*--------------------------------------------------------------------------------------------------------------------------- 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. ---------------------------------------------------------------------------------------------------------------------------*/ if( type === 1 ) { V32 &= ( 1 << ( ( n << 3 ) - 4 ) ) - 1; } /*--------------------------------------------------------------------------------------------------------------------------- If the Immediate is an relative address calculation. ---------------------------------------------------------------------------------------------------------------------------*/ if ( type === 2 ) { //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. Pad32 = ( Math.min( BitMode, 1 ) << 2 ) + 4; Pad64 = Math.max( Math.min( BitMode, 2 ), 1 ) << 3; //Carry bit to 64 bit section. var C64 = 0; //Relative size. var n = Math.min( 0x100000000, Math.pow( 2, 4 << ( S + 1 ) ) ); //Sign bit adjust. if( V32 >= ( n / 2 ) ) { V32 -= n; } //Add position. V32 += Pos32; //Remove carry bit and add it to C64. ( C64 = ( ( V32 ) >= 0x100000000 ) ) && ( V32 -= 0x100000000 ); //Do not carry to 64 if address is 32, and below. if ( S <= 2 ) { C64 = false; } //Add the 64 bit position plus carry. ( ( V64 += Pos64 + C64 ) > 0xFFFFFFFF ) && ( V64 -= 0x100000000 ); } /*--------------------------------------------------------------------------------------------------------------------------- If the Immediate is an displacement calculation. ---------------------------------------------------------------------------------------------------------------------------*/ if ( type === 3 ) { /*------------------------------------------------------------------------------------------------------------------------- Calculate the displacement center point based on Immediate size. -------------------------------------------------------------------------------------------------------------------------*/ //An displacement can not be bigger than 32 bit's, so Pad64 is set 0. Pad64 = 0; //Now calculate the Center Point. var Center = 2 * ( 1 << ( n << 3 ) - 2 ); //By default the Sign is Positive. Sign = 1; /*------------------------------------------------------------------------------------------------------------------------- Calculate the VSIB displacement size if it is a VSIB Disp8. -------------------------------------------------------------------------------------------------------------------------*/ if ( VSIB && S === 0 ) { var VScale = WidthBit | 2; Center <<= VScale; V32 <<= VScale; } //When the value is higher than the center it is negative. if ( V32 >= Center ) { //Convert the number to the negative side of the center point. V32 = Center * 2 - V32; //The Sign is negative. Sign = 2; } } /*--------------------------------------------------------------------------------------------------------------------------- Pad Imm based on the calculated Immediate size, because when an value is converted to an number as text that can be displayed 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, but is displayed as 179, because the unused digits are not displayed, but they still exist in the memory. ---------------------------------------------------------------------------------------------------------------------------*/ for( var Imm = V32.toString(16), L = Pad32; Imm.length < L; Imm = "0" + Imm ); if( Pad64 > 8 ) { for( Imm = V64.toString(16) + Imm, L = Pad64; Imm.length < L; Imm = "0" + Imm ); } /*--------------------------------------------------------------------------------------------------------------------------- Extend Imm if it's extend size is bigger than the Current Imm size. ---------------------------------------------------------------------------------------------------------------------------*/ if ( Extend !== S ) { //Calculate number of bytes to Extend till by size. Extend = Math.pow( 2, Extend ) * 2; //Setup the Signified pad value. var spd = "00"; ( ( ( parseInt( Imm.substring(0, 1), 16) & 8 ) >> 3 ) ) && ( spd = "FF" ); //Start padding. for (; Imm.length < Extend; Imm = spd + Imm); } //*Return the Imm. return ( ( Sign > 0 ? ( Sign > 1 ? "-" : "+" ) : "" ) + Imm.toUpperCase() ); } /*------------------------------------------------------------------------------------------------------------------------- Decode registers by Size attributes, or a select register index. -------------------------------------------------------------------------------------------------------------------------*/ function DecodeRegValue( RValue, BySize, Setting ) { //If the instruction is a Vector instruction, and no extension is active like EVEX, VEX Make sure Size attribute uses the default vector size. if( Vect && Extension === 0 ) { SizeAttrSelect = 0; } //If By size is true Use the Setting with the GetOperandSize if ( BySize ) { Setting = GetOperandSize( Setting ); //get decoded size value. //Any Vector register smaller than 128 has to XMM because XMM is the smallest SIMD Vector register. if( Vect && Setting < 4 ) { Setting = 4; } } //If XOP only vector 0 to 15 are usable. if( Opcode >= 0x400 ) { RValue &= 15; } //Else If 16/32 bit mode in VEX/EVEX/MVEX vctor register can only go 0 though 7. else if( BitMode <= 1 && Extension >= 1 ) { RValue &= 7; } //If L1OM ZMM to V reg. if ( Opcode >= 0x700 && Setting === 6 ) { Setting = 16; } //Else if 8 bit high/low Registers else if ( Setting === 0 ) { return (REG[0][RexActive][ RValue ]); } //Return the Register. return (REG[Setting][ RValue ]); } /*------------------------------------------------------------------------------------------------------------------------- Decode the ModR/M pointer, and Optional SIB if used. Note if by size attributes is false the lower four bits is the selected Memory pointer, and the higher four bits is the selected register. -------------------------------------------------------------------------------------------------------------------------*/ function Decode_ModRM_SIB_Address( ModRM, BySize, Setting ) { var out = ""; //the variable out is what stores the decoded address pointer, or Register if Register mode. var S_C = "{"; //L1OM, and K1OM {SSS,CCCCC} setting decoding, or EVEX broadcast round. //------------------------------------------------------------------------------------------------------------------------- //If the ModR/M is not in register mode decode it as an Effective address. //------------------------------------------------------------------------------------------------------------------------- if( ModRM[0] !== 3 ) { //If the instruction is a Vector instruction, and no extension is active like EVEX, VEX Make sure Size attribute uses the default vector size. if( Vect && Extension === 0 ) { SizeAttrSelect = 0; } //------------------------------------------------------------------------------------------------------------------------- //The Selected Size is setting unless BySize attribute is true. //------------------------------------------------------------------------------------------------------------------------- if ( BySize ) { //------------------------------------------------------------------------------------------------------------------------- //Check if it is not the non vectorized 128 which uses "Oword ptr". //------------------------------------------------------------------------------------------------------------------------- if ( Setting !== 16 || Vect ) { Setting = ( GetOperandSize( Setting ) << 1 ) | FarPointer; } //------------------------------------------------------------------------------------------------------------------------- //Non vectorized 128 uses "Oword ptr" alaises to "QWord ptr" in 32 bit mode, or lower. //------------------------------------------------------------------------------------------------------------------------- else if ( !Vect ) { Setting = 11 - ( ( BitMode <= 1 ) * 5 ); } } //------------------------------------------------------------------------------------------------------------------------- //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. //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. //------------------------------------------------------------------------------------------------------------------------- Setting = Setting & 0x0F; //If Vector extended then MM is changed to QWORD. if( Extension !== 0 && Setting === 9 ){ Setting = 6; } //Bround control, or 32/64 VSIB. if ( ConversionMode === 1 || ConversionMode === 2 || VSIB ) { out += PTR[WidthBit > 0 ? 6 : 4]; } //------------------------------------------------------------------------------------------------------------------------- //Get the pointer size by Size setting. //------------------------------------------------------------------------------------------------------------------------- else{ out = PTR[Setting]; } //Add the Segment override left address bracket if any segment override was used otherwise the SegOverride string should be just a normal left bracket. out += SegOverride; //------------------------------------------------------------------------------------------------------------------------- //calculate the actual address size according to the Address override and the CPU bit mode. //------------------------------------------------------------------------------------------------------------------------- //AddressSize 1 is 16, AddressSize 2 is 32, AddressSize 3 is 64. //The Bit mode is the address size except AddressOverride reacts differently in different bit modes. //In 16 bit AddressOverride switches to the 32 bit ModR/M effective address system. //In both 32/64 the Address size goes down by one is size. //------------------------------------------------------------------------------------------------------------------------- var AddressSize = BitMode + 1; if (AddressOverride) { AddressSize = AddressSize - 1; //the only time the address size is 0 is if the BitMode is 16 bit's and is subtracted by one resulting in 0. if(AddressSize === 0) { AddressSize = 2; //set the address size to 32 bit from the 16 bit address mode. } } /*------------------------------------------------------------------------------------------------------------------------- The displacement size calculation. --------------------------------------------------------------------------------------------------------------------------- In 16/32/64 the mode setting 1 will always add a Displacement of 8 to the address. In 16 the Mode setting 2 adds a displacement of 16 to the address. In 32/64 the Mode Setting 2 for the effective address adds an displacement of 32 to the effective address. -------------------------------------------------------------------------------------------------------------------------*/ var Disp = ModRM[0] - 1; //Let disp relate size to mode value of the ModR/M. //if 32 bit and above, and if Mode is 2 then disp size is disp32. if(AddressSize >= 2 && ModRM[0] === 2) { Disp += 1; //Only one more higher in size is 32. } /*------------------------------------------------------------------------------------------------------------------------- End of calculation. -------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------- Normally the displacement type is an relative Immediate that is added ("+"), or subtracted ("-") from the center point to the selected base register, and the size depends on mode settings 1, and 2, and also Address bit mode (Displacement calculation). Because the normal ModR/M format was limited to Relative addresses, and unfixed locations, so some modes, and registers combinations where used for different Immediate displacements. -------------------------------------------------------------------------------------------------------------------------*/ var DispType = 3; //by default the displacement size is added to the selected base register, or Index register if SIB byte combination is used. //-------------------------------------------16 Bit ModR/M address decode logic------------------------------------------- if( AddressSize === 1 ) { //if ModR/M mode bits 0, and Base Register value is 6 then disp16 with DispType mode 0. if(AddressSize === 1 && ModRM[0] === 0 && ModRM[2] === 6) { Disp = 1; DispType = 0; } //BX , BP switch based on bit 2 of the Register value if( ModRM[2] < 4 ){ out += REG[ AddressSize ][ 3 + ( ModRM[2] & 2 ) ] + "+"; } //The first bit switches between Destination index, and source index if( ModRM[2] < 6 ){ out += REG[ AddressSize ][ 6 + ( ModRM[2] & 1 ) ]; } //[BP], and [BX] as long as Mode is not 0, and Register is not 6 which sets DispType 0. else if ( DispType !== 0 ) { out += REG[ AddressSize ][ 17 - ( ModRM[2] << 1 ) ]; } } //End of 16 bit ModR/M decode logic. //-------------------------------------------Else 32/64 ModR/M------------------------------------------- else { //if Mode is 0 and Base Register value is 5 then it uses an Relative (RIP) disp32. if( ModRM[0] === 0 && ModRM[2] === 5 ) { Disp = 2; DispType = 2; } //check if Base Register is 4 which goes into the SIB address system if( ModRM[2] === 4 ) { //Decode the SIB byte. var SIB = Decode_ModRM_SIB_Value(); //Calculate the Index register with it's Extended value because the index register will only cancel out if 4 in value. var IndexReg = IndexExtend | SIB[1]; //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 if ( ModRM[0] === 0 && SIB[2] === 5 && !VSIB ) { Disp = 2; //Set Disp32 //check if the Index register is canceled out as well if (IndexReg === 4) //if the Index is canceled out then { DispType = 0; //a regular IMM32 is used as the address. //*if the Address size is 64 then the 32 bit Immediate must pad to the full 64 bit address. if( AddressSize === 3 ) { Disp = 50; } } } //Else Base register is not 5, and the Mode is not 0 then decode the base register normally. else { out += REG[ AddressSize ][ BaseExtend & 8 | SIB[2] ]; //If the Index Register is not Canceled out (Note this is only reachable if base register was decoded and not canceled out) if ( IndexReg !== 4 || VSIB ) { out = out + "+"; //Then add the Plus in front of the Base register to add the index register } } //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. if ( IndexReg !== 4 && !VSIB ) { out += REG[ AddressSize ][ IndexExtend | IndexReg ]; //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. out = out + scale[SIB[0]]; } //Else if it is an vector register. else if ( VSIB ) { Setting = ( Setting < 8 ) ? 4 : Setting >> 1; if( Opcode < 0x700 ) { IndexReg |= ( VectorRegister & 0x10 ); } out += DecodeRegValue( IndexExtend | IndexReg, false, Setting ); //Decode Vector register by length setting and the V' extension. //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. out = out + scale[SIB[0]]; } } //END OF THE SIB BYTE ADDRESS DECODE. //else Base register is not 4 and does not go into the SIB ADDRESS. //Decode the Base register regularly plus it's Extended value if relative (RIP) disp32 is not used. else if(DispType !== 2) { out += REG[ AddressSize ][ BaseExtend & 8 | ModRM[2] ]; } } //Finally the Immediate displacement is put into the Address last. if( Disp >= 0 ) { out += DecodeImmediate( DispType, false, Disp ); } //Put the right bracket on the address. out += "]"; //----------------------L1OM/MVEX/EVEX memory conversion mode, or broadcast round----------------------- if( ( ConversionMode !== 0 ) && //Not used if set 0. !( ( ConversionMode === 3 && ( Opcode >= 0x700 || !( Opcode >= 0x700 ) && !Float ) ) || //If bad L1OM/K1OM float conversion. ( !( Opcode >= 0x700 ) && ( VectS === 0 || ( ConversionMode === 5 && VectS === 5 ) || //If K1OM UNorm conversion L1OM only. ( ConversionMode !== 1 && VectS === 1 ) ^ ( ConversionMode < 3 && !Swizzle ) ) ) //Or K1OM broadcast Swizzle, and special case {4to16} only. ) ) { //Calculate Conversion. if( ConversionMode >= 4 ){ ConversionMode += 2; } if( ConversionMode >= 8 ){ ConversionMode += 2; } //If L1OM. if( Opcode >= 0x700 ) { //If L1OM without Swizzle. if ( !Swizzle && ConversionMode > 2 ) { ConversionMode = 31; } //L1OM Float adjust. else if( Float ) { if( ConversionMode === 7 ) { ConversionMode++; } if( ConversionMode === 10 ) { ConversionMode = 3; } } } //Set conversion. Note K1OM special case inverts width bit. out += S_C + ConversionModes[ ( ConversionMode << 1 ) | ( WidthBit ^ ( !( Opcode >= 0x700 ) & VectS === 7 ) ) & 1 ]; S_C = ","; } //Else bad Conversion setting. else if( ConversionMode !== 0 ) { out += S_C + "Error"; S_C = ","; } //--------------------------------END of memory Conversion control logic-------------------------------- } //End of Memory address Modes 00, 01, 10 decode. //-----------------------------else the ModR/M mode bits are 11 register Mode----------------------------- else { //------------------------------------------------------------------------------------------------------------------------- //The Selected Size is setting unless BySize attribute is true. //------------------------------------------------------------------------------------------------------------------------- //MVEX/EVEX round mode. if ( ( Extension === 3 && HInt_ZeroMerg ) || ( Extension === 2 && ConversionMode === 1 ) ) { RoundMode |= RoundingSetting; } //If the upper 4 bits are defined and by size is false then the upper four bits is the selected register. if( ( ( Setting & 0xF0 ) > 0 ) && !BySize ) { Setting >>= 4; } //Decode the register with Base expansion. out = DecodeRegValue( BaseExtend | ModRM[2], BySize, Setting ); //L1OM/K1OM Register swizzle modes. if( Opcode >= 0x700 || ( Extension === 3 && !HInt_ZeroMerg && Swizzle ) ) { if( Opcode >= 0x700 && ConversionMode >= 3 ){ ConversionMode++; } //L1OM skips swizzle type DACB. if( ConversionMode !== 0 ){ out += S_C + RegSwizzleModes[ ConversionMode ]; S_C = ","; } } if( Extension !== 2 ){ HInt_ZeroMerg = false; } //Cache memory control is not possible in Register mode. } //--------------------------------------------------L1OM.CCCCC conversions------------------------------------------------- if( Opcode >= 0x700 ) { //Swizzle Field control Int/Float, or Exponent adjustment. if(Swizzle) { if( Opcode === 0x79A ) { out += S_C + ConversionModes[ ( 18 | ( VectorRegister & 3 ) ) << 1 ]; S_C = "}"; } else if( Opcode === 0x79B ) { out += S_C + ConversionModes[ ( 22 + ( VectorRegister & 3 ) ) << 1 ]; S_C = "}"; } else if( ( RoundingSetting & 8 ) === 8 ) { out += S_C + RoundModes [ 24 | ( VectorRegister & 7 ) ]; S_C = "}"; } } //Up/Down Conversion. else if( VectorRegister !== 0 ) { if( ( ( Up && VectorRegister !== 2 ) || //Up conversion "float16rz" is bad. ( !Up && VectorRegister !== 3 && VectorRegister <= 15 ) ) //Down conversion "srgb8", and field control is bad. ) { out += S_C + ConversionModes[ ( ( VectorRegister + 2 ) << 1 ) | WidthBit ]; S_C = "}"; } else { out += S_C + "Error"; S_C = "}"; } //Else Invalid setting. } } if ( S_C === "," ) { S_C = "}"; } //Right bracket if any SSS,CCCCC conversion mode setting. if( S_C === "}" ) { out += S_C; } //------------------------------------------L1OM/K1OM Hint cache memory control-------------------------------------------- if( HInt_ZeroMerg ) { if ( Extension === 3 ) { out += "{EH}"; } else if ( Opcode >= 0x700 ) { out += "{NT}"; } } //-------------------------------------------Return the Register/Memory address-------------------------------------------- return (out); } /*------------------------------------------------------------------------------------------------------------------------- Decode Prefix Mnemonic codes. Prefixes are instruction codes that do not do an operation instead adjust controls in the CPU to be applied to an select instruction code that is not an Prefix instruction. --------------------------------------------------------------------------------------------------------------------------- At the end of this function "Opcode" should not hold any prefix code, so then Opcode contains an operation code. -------------------------------------------------------------------------------------------------------------------------*/ function DecodePrefixAdjustments() { //------------------------------------------------------------------------------------------------------------------------- Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //Add 8 bit opcode while bits 9, and 10 are used for opcode map. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- //if 0F hex start at 256 for Opcode. if(Opcode === 0x0F) { Opcode = 0x100; //By starting at 0x100 with binary bit 9 set one then adding the 8 bit opcode then Opcode goes 256 to 511. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //if 38 hex while using two byte opcode. else if(Opcode === 0x138 && Mnemonics[0x138] === "") { Opcode = 0x200; //By starting at 0x200 with binary bit 10 set one then adding the 8 bit opcode then Opcode goes 512 to 767. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //if 3A hex while using two byte opcode go three byte opcodes. else if(Opcode === 0x13A && Mnemonics[0x13A] === "") { Opcode = 0x300; //By starting at 0x300 hex and adding the 8 bit opcode then Opcode goes 768 to 1023. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //Rex prefix decodes only in 64 bit mode. if( Opcode >= 0x40 & Opcode <= 0x4F && BitMode === 2 ) { RexActive = 1; //Set Rex active uses 8 bit registers in lower order as 0 to 15. BaseExtend = ( Opcode & 0x01 ) << 3; //Base Register extend setting. IndexExtend = ( Opcode & 0x02 ) << 2; //Index Register extend setting. RegExtend = ( Opcode & 0x04 ) << 1; //Register Extend Setting. WidthBit = ( Opcode & 0x08 ) >> 3; //Set The Width Bit setting if active. SizeAttrSelect = WidthBit ? 2 : 1; //The width Bit open all 64 bits. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //The VEX2 Operation code Extension to SSE settings decoding. if( Opcode === 0xC5 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) { Extension = 1; //------------------------------------------------------------------------------------------------------------------------- Opcode = BinCode[CodePos]; //read VEX2 byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- //some bits are inverted, so uninvert them arithmetically. Opcode ^= 0xF8; //Decode bit settings. if( BitMode === 2 ) { RegExtend = ( Opcode & 0x80 ) >> 4; //Register Extend. VectorRegister = ( Opcode & 0x78 ) >> 3; //The added in Vector register to SSE. } SizeAttrSelect = ( Opcode & 0x04 ) >> 2; //The L bit for 256 vector size. SIMD = Opcode & 0x03; //The SIMD mode. //Automatically uses the two byte opcode map starts at 256 goes to 511. Opcode = 0x100; //------------------------------------------------------------------------------------------------------------------------- Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the opcode. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- //Stop decoding prefixes. return(null); } //The VEX3 prefix settings decoding. if( Opcode === 0xC4 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) { Extension = 1; //------------------------------------------------------------------------------------------------------------------------- Opcode = BinCode[CodePos]; //read VEX3 byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- Opcode |= ( BinCode[CodePos] << 8 ); //Read next VEX3 byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- //Some bits are inverted, so uninvert them arithmetically. Opcode ^= 0x78E0; //Decode bit settings. if( BitMode === 2 ) { RegExtend = ( Opcode & 0x0080 ) >> 4; //Extend Register Setting. IndexExtend = ( Opcode & 0x0040 ) >> 3; //Extend Index register setting. BaseExtend = ( Opcode & 0x0020 ) >> 2; //Extend base Register setting. } WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit works as a separator. VectorRegister = ( Opcode & 0x7800 ) >> 11; //The added in Vector register to SSE. SizeAttrSelect = ( Opcode & 0x0400 ) >> 10; //Vector length for 256 setting. SIMD = ( Opcode & 0x0300 ) >> 8; //The SIMD mode. Opcode = ( Opcode & 0x001F ) << 8; //Change Operation code map. //------------------------------------------------------------------------------------------------------------------------- Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map bit's. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- return(null); } //The AMD XOP prefix. if( Opcode === 0x8F ) { //If XOP var Code = BinCode[ CodePos ] & 0x0F; if( Code >= 8 && Code <= 10 ) { Extension = 1; //------------------------------------------------------------------------------------------------------------------------- Opcode = BinCode[CodePos]; //read XOP byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- Opcode |= ( BinCode[CodePos] << 8 ); //Read next XOP byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- //Some bits are inverted, so uninvert them arithmetically. Opcode ^= 0x78E0; //Decode bit settings. RegExtend = ( Opcode & 0x0080 ) >> 4; //Extend Register Setting. IndexExtend = ( Opcode & 0x0040 ) >> 3; //Extend Index register setting. BaseExtend = ( Opcode & 0x0020 ) >> 2; //Extend base Register setting. WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit works as a separator. VectorRegister = ( Opcode & 0x7800 ) >> 11; //The added in Vector register to SSE. SizeAttrSelect = ( Opcode & 0x0400 ) >> 10; //Vector length for 256 setting. SIMD = ( Opcode & 0x0300 ) >> 8; //The SIMD mode. if( SIMD > 0 ) { InvalidOp = true; } //If SIMD MODE is set anything other than 0 the instruction is invalid. Opcode = 0x400 | ( ( Opcode & 0x0003 ) << 8 ); //Change Operation code map. //------------------------------------------------------------------------------------------------------------------------- Opcode = ( Opcode & 0x700 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map bit's. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- return(null); } } //The L1OM vector prefix settings decoding. if( Opcode === 0xD6 ) { //------------------------------------------------------------------------------------------------------------------------- Opcode = BinCode[CodePos]; //read L1OM byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- Opcode |= ( BinCode[CodePos] << 8 ); //Read next L1OM byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- WidthBit = SIMD & 1; VectorRegister = ( Opcode & 0xF800 ) >> 11; RoundMode = VectorRegister >> 3; MaskRegister = ( Opcode & 0x0700 ) >> 8; HInt_ZeroMerg = ( Opcode & 0x0080 ) >> 7; ConversionMode = ( Opcode & 0x0070 ) >> 4; RegExtend = ( Opcode & 0x000C ) << 1; BaseExtend = ( Opcode & 0x0003 ) << 3; IndexExtend = ( Opcode & 0x0002 ) << 2; //------------------------------------------------------------------------------------------------------------------------- Opcode = 0x700 | BinCode[CodePos]; //read the 8 bit opcode. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- return(null); } //Only decode L1OM instead of MVEX/EVEX if L1OM compatibility mode is set. if( Mnemonics[0x62] === "" && Opcode === 0x62 ) { //------------------------------------------------------------------------------------------------------------------------- Opcode = BinCode[CodePos]; //read L1OM byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- Opcode ^= 0xF0; IndexExtend = ( Opcode & 0x80 ) >> 4; BaseExtend = ( Opcode & 0x40 ) >> 3; RegExtend = ( Opcode & 0x20 ) >> 2; if ( SIMD !== 1 ) { SizeAttrSelect = ( ( Opcode & 0x10 ) === 0x10 ) ? 2 : 1; } else { SIMD = 0; } Opcode = 0x800 | ( ( Opcode & 0x30 ) >> 4 ) | ( ( Opcode & 0x0F ) << 2 ); return(null); } //The MVEX/EVEX prefix settings decoding. if ( Opcode === 0x62 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) { Extension = 2; //------------------------------------------------------------------------------------------------------------------------- Opcode = BinCode[CodePos]; //read MVEX/EVEX byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- Opcode |= ( BinCode[CodePos] << 8 ); //read next MVEX/EVEX byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- Opcode |= ( BinCode[CodePos] << 16 ); //read next MVEX/EVEX byte settings. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- //Some bits are inverted, so uninvert them arithmetically. Opcode ^= 0x0878F0; //Check if Reserved bits are 0 if they are not 0 the MVEX/EVEX instruction is invalid. InvalidOp = ( Opcode & 0x00000C ) > 0; //Decode bit settings. if( BitMode === 2 ) { RegExtend = ( ( Opcode & 0x80 ) >> 4 ) | ( Opcode & 0x10 ); //The Double R'R bit decode for Register Extension 0 to 32. BaseExtend = ( Opcode & 0x60 ) >> 2; //The X bit, and B Bit base register extend combination 0 to 32. IndexExtend = ( Opcode & 0x40 ) >> 3; //The X extends the SIB Index by 8. } VectorRegister = ( ( Opcode & 0x7800 ) >> 11 ) | ( ( Opcode & 0x080000 ) >> 15 ); //The Added in Vector Register for SSE under MVEX/EVEX. WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit separator for MVEX/EVEX. SIMD = ( Opcode & 0x0300 ) >> 8; //decode the SIMD mode setting. HInt_ZeroMerg = ( Opcode & 0x800000 ) >> 23; //Zero Merge to destination control, or MVEX EH control. //EVEX option bits take the place of Vector length control. if ( ( Opcode & 0x0400 ) > 0 ) { SizeAttrSelect = ( Opcode & 0x600000 ) >> 21; //The EVEX.L'L Size combination. RoundMode = SizeAttrSelect | 4; //Rounding mode is Vector length if used. ConversionMode = (Opcode & 0x100000 ) >> 20; //Broadcast Round Memory address system. } //MVEX Vector Length, and Broadcast round. else { SizeAttrSelect = 2; //Max Size by default. ConversionMode = ( Opcode & 0x700000 ) >> 20; //"MVEX.sss" Option bits. RoundMode = ConversionMode; //Rounding mode selection is ConversionMode if used. Extension = 3; } MaskRegister = ( Opcode & 0x070000 ) >> 16; //Mask to destination. Opcode = ( Opcode & 0x03 ) << 8; //Change Operation code map. //------------------------------------------------------------------------------------------------------------------------- Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map extend bit's. NextByte(); //Move to the next byte. //------------------------------------------------------------------------------------------------------------------------- //Stop decoding prefixes. return(null); } //Segment overrides if ( ( Opcode & 0x7E7 ) === 0x26 || ( Opcode & 0x7FE ) === 0x64 ) { SegOverride = Mnemonics[ Opcode ]; //Set the Left Bracket for the ModR/M memory address. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //Operand override Prefix if(Opcode === 0x66) { SIMD = 1; //sets SIMD mode 1 in case of SSE instruction opcode. SizeAttrSelect = 0; //Adjust the size attribute setting for the size adjustment to the next instruction. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //Ram address size override. if(Opcode === 0x67) { AddressOverride = true; //Set the setting active for the ModR/M address size. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //if repeat Prefixes F2 hex REP,or F3 hex RENP if (Opcode === 0xF2 || Opcode === 0xF3) { SIMD = (Opcode & 0x02 ) | ( 1 - Opcode & 0x01 ); //F2, and F3 change the SIMD mode during SSE instructions. PrefixG1 = Mnemonics[ Opcode ]; //set the Prefix string. HLEFlipG1G2 = true; //set Filp HLE in case this is the last prefix read, and LOCK was set in string G2 first for HLE. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //if the lock prefix note the lock prefix is separate if (Opcode === 0xF0) { PrefixG2 = Mnemonics[ Opcode ]; //set the Prefix string 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. return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. } //Before ending the function "DecodePrefixAdjustments()" some opcode combinations are invalid in 64 bit mode. if ( BitMode === 2 ) { InvalidOp |= ( ( ( Opcode & 0x07 ) >= 0x06 ) & ( Opcode <= 0x40 ) ); InvalidOp |= ( Opcode === 0x60 | Opcode === 0x61 ); InvalidOp |= ( Opcode === 0xD4 | Opcode === 0xD5 ); InvalidOp |= ( Opcode === 0x9A | Opcode === 0xEA ); InvalidOp |= ( Opcode === 0x82 ); } } /*------------------------------------------------------------------------------------------------------------------------- The Decode opcode function gives back the operation name, and what it uses for input. The input types are for example which registers it uses with the ModR/M, or which Immediate type is used. The input types are stored into an operand string. This function gives back the instruction name, And what the operands use. --------------------------------------------------------------------------------------------------------------------------- This function is designed to be used after the Decode prefix adjustments function because the Opcode should contain an real instruction code. This 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. However DecodePrefixAdjustments can also prevent this function from being called next if the prefix settings are bad or an invalid instruction is used for the bit mode the CPU is in as it will set InvalidOp true. -------------------------------------------------------------------------------------------------------------------------*/ function DecodeOpcode() { //get the Operation name by the operations opcode. Instruction = Mnemonics[Opcode]; //get the Operands for this opcode it follows the same array structure as Mnemonics array InsOperands = Operands[Opcode]; //Some Opcodes use the next byte automatically for extended opcode selection. Or current SIMD mode. var ModRMByte = BinCode[CodePos]; //Read the byte but do not move to the next byte. //If the current Mnemonic is an array two in size then Register Mode, and memory mode are separate from each other. //Used in combination with Grouped opcodes, and Static opcodes. if(Instruction instanceof Array && Instruction.length == 2) { var bits = ( ModRMByte >> 6 ) & ( ModRMByte >> 7 ); Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; } //Arithmetic unit 8x8 combinational logic array combinations. //If the current Mnemonic is an array 8 in length It is a group opcode instruction may repeat previous instructions in different forums. if(Instruction instanceof Array && Instruction.length == 8) { var bits = ( ModRMByte & 0x38 ) >> 3; Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; //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. if(Instruction instanceof Array && Instruction.length == 8) { var bits = ( ModRMByte & 0x07 ); Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; NextByte(); } } //Vector unit 4x4 combinational array logic. //if the current Mnemonic is an array 4 in size it is an SIMD instruction with four possible modes N/A, 66, F3, F2. //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. if(Instruction instanceof Array && Instruction.length == 4) { Vect = true; //Set Vector Encoding true. //Reset the prefix string G1 because prefix codes F2, and F3 are used with SSE which forum the repeat prefix. //Some SSE instructions can use the REP, RENP prefixes. //The Vectors that do support the repeat prefix uses Packed Single format. if(Instruction[2] !== "" && Instruction[3] !== "") { PrefixG1 = ""; } else { SIMD = ( SIMD === 1 ) & 1; } Instruction = Instruction[SIMD]; InsOperands = InsOperands[SIMD]; //If the SIMD instruction uses another array 4 in length in the Selected SIMD vector Instruction. //Then each vector Extension is separate. The first extension is used if no extension is active for Regular instructions, and vector instruction septation. //0=None. 1=VEX only. 2=EVEX only. 3=??? unused. if(Instruction instanceof Array && Instruction.length == 4) { //Get the correct Instruction for the Active Extension type. if(Instruction[Extension] !== "") { Instruction = Instruction[Extension]; InsOperands = InsOperands[Extension]; } else{ Instruction = "???"; InsOperands = ""; } } else if( Extension === 3 ){ Instruction = "???"; InsOperands = ""; } } else if( Opcode >= 0x700 && SIMD > 0 ){ Instruction = "???"; InsOperands = ""; } //if Any Mnemonic is an array 3 in size the instruction name goes by size. if(Instruction instanceof Array && Instruction.length == 3) { var bits = ( Extension === 0 & BitMode !== 0 ) ^ ( SizeAttrSelect >= 1 ); //The first bit in SizeAttrSelect for size 32/16 Flips if 16 bit mode. ( WidthBit ) && ( bits = 2 ); //Goes 64 using the Width bit. ( 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. if (Instruction[bits] !== "") { Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; } //else no size prefix name then use the default size Mnemonic name. else { Instruction = Instruction[0]; InsOperands = InsOperands[0]; } } //If Extension is not 0 then add the vector extend "V" to the instruction. //During the decoding of the operands the instruction can be returned as invalid if it is an Arithmetic, or MM, ST instruction. //Vector mask instructions start with K instead of V any instruction that starts with K and is an //vector mask Instruction which starts with K instead of V. if( Opcode <= 0x400 && Extension > 0 && Instruction.charAt(0) !== "K" && Instruction !== "???" ) { Instruction = "V" + Instruction; } //In 32 bit mode, or bellow only one instruction MOVSXD is replaced with ARPL. if( BitMode <= 1 && Instruction === "MOVSXD" ) { Instruction = "ARPL"; InsOperands = "06020A01"; } } /*------------------------------------------------------------------------------------------------------------------------- Read each operand in the Operand String then set the correct operand in the X86 decoder array. OpNum is the order the operands are read in the operand string. The Operand type is which operand will be set active along the X86Decoder. The OpNum is the order the decoded operands will be positioned after they are decoded in order along the X86 decoder. The order the operands display is different than the order they decode in sequence. --------------------------------------------------------------------------------------------------------------------------- This function is used after the function ^DecodeOpcode()^ because the function ^DecodeOpcode()^ gives back the operand string for what the instruction takes as input. -------------------------------------------------------------------------------------------------------------------------*/ function DecodeOperandString() { //Variables that are used for decoding one operands across the operand string. var OperandValue = 0, Code = 0, BySize = 0, Setting = 0; //It does not matter which order the explicit operands decode as they do not require reading another byte. //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. var ExplicitOp = 8, ImmOp = 3; //Each operand is 4 hex digits, and OpNum is added by one for each operand that is read per Iteration. for( var i = 0, OpNum = 0; i < InsOperands.length; i+=4 ) //Iterate though operand string. { OperandValue = parseInt( InsOperands.substring(i, (i + 4) ), 16 ); //Convert the four hex digits to a 16 bit number value. Code = ( OperandValue & 0xFE00 ) >> 9; //Get the operand Code. BySize = ( OperandValue & 0x0100 ) >> 8; //Get it's by size attributes setting for if Setting is used as size attributes. Setting = ( OperandValue & 0x00FF ); //Get the 8 bit Size setting. //If code is 0 the next 8 bit value specifies which type of of prefix settings are active. if( Code === 0 ) { if(BySize) //Vector adjustment settings. { RoundingSetting = ( Setting & 0x03 ) << 3; if( Opcode >= 0x700 && RoundingSetting >= 0x10 ){ RoundMode |= 0x10; } VSIB = ( Setting >> 2 ) & 1; IgnoresWidthbit = ( Setting >> 3 ) & 1; VectS = ( Setting >> 4 ) & 7; Swizzle = ( VectS >> 2 ) & 1; Up = ( VectS >> 1 ) & 1; Float = VectS & 1; if( ( Setting & 0x80 ) == 0x80 ) { Vect = false; } //If Non vector instruction set Vect false. } else //Instruction Prefix types. { XRelease = Setting & 0x01; XAcquire = ( Setting & 0x02 ) >> 1; HT = ( Setting & 0x04 ) >> 2; BND = ( Setting & 0x08 ) >> 3; } } //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 //byte for modR/M. else if( Code === 1 ) { X86Decoder[0].set( 0, BySize, Setting, OpNum++ ); } //if it is a ModR/M, or Far pointer ModR/M, or Moffs address then second decoder element is set. else if( Code >= 2 && Code <= 4 ) { X86Decoder[1].set( ( Code - 2 ), BySize, Setting, OpNum++ ); if( Code == 4 ){ FarPointer = 1; } //If code is 4 it is a far pointer. } //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 //different address pointer. The Reg bits of the ModR/M decode next as it would be inefficient to read the register value if the //decoder moves to the immediate. else if( Code === 5 ) { X86Decoder[2].set( 0, BySize, Setting, OpNum++ ); } //Immediate input one. The immediate input is just a number input it is decoded last unless the instruction does not use a //ModR/M encoding, or Reg Opcode. else if( Code >= 6 && Code <= 8 && ImmOp <= 5 ) { X86Decoder[ImmOp++].set( ( Code - 6 ), BySize, Setting, OpNum++ ); } //Vector register. If the instruction uses this register it will not be decoded or displayed unless one of the vector extension codes are //decoded by the function ^DecodePrefixAdjustments()^. The Vector extension codes also have a Vector register value that is stored into //the variable VectorRegister. The variable VectorRegister is given to the function ^DecodeRegValue()^. else if( Code === 9 && ( Extension > 0 || Opcode >= 0x700 ) ) { X86Decoder[6].set( 0, BySize, Setting, OpNum++ ); } //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()^. //The upper four bits of the IMM is given to the function ^DecodeRegValue()^. else if( Code === 10 ) { X86Decoder[7].set( 0, BySize, Setting, OpNum++ ); } //Else any other encoding type higher than 13 is an explicit operand selection. //And also there can only be an max of four explicit operands. else if( Code >= 11 && ExplicitOp <= 11) { X86Decoder[ExplicitOp].set( ( Code - 11 ), BySize, Setting, OpNum++ ); ExplicitOp++; //move to the next Explicit operand. } } } /*------------------------------------------------------------------------------------------------------------------------- Decode each of the operands along the X86Decoder and deactivate them. This function is used after ^DecodeOperandString()^ which sets up the X86 Decoder for the instructions operands. -------------------------------------------------------------------------------------------------------------------------*/ function DecodeOperands() { //The Operands array is a string array in which the operand number is the element the decoded operand is positioned. var out = []; //This holds the decoded ModR/M byte from the "Decode_ModRM_SIB_Value()" function because the Register, and Address can decode differently. var ModRMByte = [ -1, //Mode is set negative one used to check if the ModR/M has been decoded. 0, //The register value is decoded separately if used. 0 //the base register for the address location. ]; //If no Immediate operand is used then the Immediate register encoding forces an IMM8 for the register even if the immediate is not used. 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. //If reg opcode is active. if( X86Decoder[0].Active ) { out[ X86Decoder[0].OpNum ] = DecodeRegValue( ( RegExtend | ( Opcode & 0x07 ) ), //Register value. X86Decoder[0].BySizeAttrubute, //By size attribute or not. X86Decoder[0].Size //Size settings. ); } //If ModR/M Address is active. if( X86Decoder[1].Active ) { //Decode the ModR/M byte Address which can end up reading another byte for SIB address, and including displacements. if(X86Decoder[1].Type !== 0) { ModRMByte = Decode_ModRM_SIB_Value(); //Decode the ModR/M byte. out[ X86Decoder[1].OpNum ] = Decode_ModRM_SIB_Address( ModRMByte, //The ModR/M byte. X86Decoder[1].BySizeAttrubute, //By size attribute or not. X86Decoder[1].Size //Size settings. ); } //Else If the ModR/M type is 0 then it is a moffs address. else { var s=0, AddrsSize = 0; if( X86Decoder[1].BySizeAttrubute ) { AddrsSize = ( Math.pow( 2, BitMode ) << 1 ); s = GetOperandSize( X86Decoder[1].Size, true ) << 1; } else { AddrsSize = BitMode + 1; s = X86Decoder[1].Size; } out[ X86Decoder[1].OpNum ] = PTR[ s ]; out[ X86Decoder[1].OpNum ] += SegOverride + DecodeImmediate( 0, X86Decoder[1].BySizeAttrubute, AddrsSize ) + "]"; } } //Decode the Register value of the ModR/M byte. if( X86Decoder[2].Active ) { //If the ModR/M address is not used, and ModR/M byte was not previously decoded then decode it. if( ModRMByte[0] === -1 ){ ModRMByte = Decode_ModRM_SIB_Value(); } //Decode only the Register Section of the ModR/M byte values. out[ X86Decoder[2].OpNum ] = DecodeRegValue( ( RegExtend | ( ModRMByte[1] & 0x07 ) ), //Register value. X86Decoder[2].BySizeAttrubute, //By size attribute or not. X86Decoder[2].Size //Size settings. ); } //First Immediate if used. if( X86Decoder[3].Active ) { var t = DecodeImmediate( X86Decoder[3].Type, //Immediate input type. X86Decoder[3].BySizeAttrubute, //By size attribute or not. X86Decoder[3].Size //Size settings. ); //Check if Instruction uses condition codes. if( Instruction.slice(-1) === "," ) { Instruction = Instruction.split(","); if( ( Extension >= 1 && Extension <= 2 && Opcode <= 0x400 && IMMValue < 0x20 ) || IMMValue < 0x08 ) { IMMValue |= ( ( ( Opcode > 0x400 ) & 1 ) << 5 ); //XOP adjust. Instruction = Instruction[0] + ConditionCodes[ IMMValue ] + Instruction[1]; } else { Instruction = Instruction[0] + Instruction[1]; out[ X86Decoder[3].OpNum ] = t; } } //else add the Immediate byte encoding to the decoded instruction operands. else { out[ X86Decoder[3].OpNum ] = t; } IMM_Used = true; //Immediate byte is read. } //Second Immediate if used. if( X86Decoder[4].Active ) { out[ X86Decoder[4].OpNum ] = DecodeImmediate( X86Decoder[4].Type, //Immediate input type. X86Decoder[4].BySizeAttrubute, //By size attribute or not. X86Decoder[4].Size //Size settings. ); } //Third Immediate if used. if( X86Decoder[5].Active ) { out[ X86Decoder[5].OpNum ] = DecodeImmediate( X86Decoder[5].Type, //Immediate input type. X86Decoder[5].BySizeAttrubute, //By size attribute or not. X86Decoder[5].Size //Size settings. ); } //Vector register if used from an SIMD vector extended instruction. if( X86Decoder[6].Active ) { out[ X86Decoder[6].OpNum ] = DecodeRegValue( VectorRegister, //Register value. X86Decoder[6].BySizeAttrubute, //By size attribute or not. X86Decoder[6].Size //Size settings. ); } //Immediate register encoding. if( X86Decoder[7].Active ) { if( !IMM_Used ) { DecodeImmediate(0, false, 0); } //forces IMM8 if no Immediate has been used. out[ X86Decoder[7].OpNum ] = DecodeRegValue( ( ( ( IMMValue & 0xF0 ) >> 4 ) | ( ( IMMValue & 0x08 ) << 1 ) ), //Register value. X86Decoder[7].BySizeAttrubute, //By size attribute or not. X86Decoder[7].Size //Size settings. ); } //------------------------------------------------------------------------------------------------------------------------- //Iterate though the 4 possible Explicit operands The first operands that is not active ends the Iteration. //------------------------------------------------------------------------------------------------------------------------- for( var i = 8; i < 11; i++ ) { //------------------------------------------------------------------------------------------------------------------------- //if Active Type is used as which Explicit operand. //------------------------------------------------------------------------------------------------------------------------- if( X86Decoder[i].Active ) { //General use registers value 0 though 4 there size can change by size setting but can not be extended or changed. if( X86Decoder[i].Type <= 3 ) { out[ X86Decoder[i].OpNum ] = DecodeRegValue( X86Decoder[i].Type, //register by value for Explicit Registers A, C, D, B. X86Decoder[i].BySizeAttrubute, //By size attribute or not. X86Decoder[i].Size //Size attribute. ); } //RBX address Explicit Operands prefixes can extend the registers and change pointer size RegMode 0. else if( X86Decoder[i].Type === 4 ) { s = 3; //If 32, or 64 bit ModR/M. if( ( BitMode === 0 && !AddressOverride ) || ( BitMode === 1 && AddressOverride ) ){ s = 7; } //If 16 bit ModR/M. out[ X86Decoder[i].OpNum ] = Decode_ModRM_SIB_Address( [ 0, 0, s ], //the RBX register only for the pointer. X86Decoder[i].BySizeAttrubute, //By size attribute or not. X86Decoder[i].Size //size attributes. ); } //source and destination address Explicit Operands prefixes can extend the registers and change pointer size. else if( X86Decoder[i].Type === 5 | X86Decoder[i].Type === 6 ) { s = 1; //If 32, or 64 bit ModR/M. if( ( BitMode === 0 && !AddressOverride ) || ( BitMode === 1 & AddressOverride ) ) { s = -1; } //If 16 bit ModR/M. out[ X86Decoder[i].OpNum ] = Decode_ModRM_SIB_Address( [ 0, 0, ( X86Decoder[i].Type + s ) ], //source and destination pointer register by type value. X86Decoder[i].BySizeAttrubute, //By size attribute or not. X86Decoder[i].Size //size attributes. ); } //The ST only Operand, and FS, GS. else if( X86Decoder[i].Type >= 7 ) { out[ X86Decoder[i].OpNum ] = ["ST", "FS", "GS", "1", "3", "XMM0", "M10"][ ( X86Decoder[i].Type - 7 ) ]; } } //------------------------------------------------------------------------------------------------------------------------- //else inactive end iteration. //------------------------------------------------------------------------------------------------------------------------- else { break; } } /*------------------------------------------------------------------------------------------------------------------------- If the EVEX vector extension is active the Mask, and Zero merge control are inserted into operand 0 (Destination operand). -------------------------------------------------------------------------------------------------------------------------*/ //Mask Register is used if it is not 0 in value. if( MaskRegister !== 0 ){ out[0] += "{K" + MaskRegister + "}"; } //EVEX Zero Merge control. if( Extension === 2 && HInt_ZeroMerg ) { out[0] += "{Z}"; } //convert the operand array to a string and return it. InsOperands = out.toString(); } /*------------------------------------------------------------------------------------------------------------------------- The main Instruction decode function plugs everything in together for the steps required to decode a full X86 instruction. -------------------------------------------------------------------------------------------------------------------------*/ function DecodeInstruction() { //Reset Prefix adjustments, and vector setting adjustments. Reset(); var out = ""; //The instruction code that will be returned back from this function. //Record the starting position. InstructionPos = GetPosition(); //First read any opcodes (prefix) that act as adjustments to the main three operand decode functions ^DecodeRegValue()^, //^Decode_ModRM_SIB_Address()^, and ^DecodeImmediate()^. DecodePrefixAdjustments(); //Only continue if an invalid opcode is not read by DecodePrefixAdjustments() for cpu bit mode setting. if( !InvalidOp ) { //Decode the instruction. DecodeOpcode(); //------------------------------------------------------------------------------------------------------------------------- //Intel Larrabee CCCCC condition codes. //------------------------------------------------------------------------------------------------------------------------- if( Opcode >= 0x700 && Instruction.slice(-1) === "," ) { Instruction = Instruction.split(","); //CMP conditions. if( Opcode >= 0x720 && Opcode <= 0x72F ) { IMMValue = VectorRegister >> 2; if( Float || ( IMMValue !== 3 && IMMValue !== 7 ) ) { Instruction = Instruction[0] + ConditionCodes[IMMValue] + Instruction[1]; } else { Instruction = Instruction[0] + Instruction[1]; } IMMValue = 0; VectorRegister &= 0x03; } //Else High/Low. else { Instruction = Instruction[0] + ( ( ( VectorRegister & 1 ) === 1 ) ? "H" : "L" ) + Instruction[1]; } } //Setup the X86 Decoder for which operands the instruction uses. DecodeOperandString(); //Now only some instructions can vector extend, and that is only if the instruction is an SIMD Vector format instruction. if( !Vect && Extension > 0 && Opcode <= 0x400 ) { InvalidOp = true; } //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. if( Vect && !IgnoresWidthbit && Extension >= 2 ) { InvalidOp = ( ( SIMD & 1 ) !== ( WidthBit & 1 ) ); //Note use, and ignore width bit pastern EVEX. } if( Opcode >= 0x700 ) { WidthBit ^= IgnoresWidthbit; } //L1OM Width bit invert. } //If the instruction is invalid then set the instruction to "???" if( InvalidOp ) { out = "???" //set the returned instruction to invalid } //Else finish decoding the valid instruction. else { //Decode each operand along the Decoder array in order, and deactivate them. DecodeOperands(); /*------------------------------------------------------------------------------------------------------------------------- 3DNow Instruction name is encoded by the next byte after the ModR/M, and Reg operands. -------------------------------------------------------------------------------------------------------------------------*/ if( Opcode === 0x10F ) { //Lookup operation code. Instruction = M3DNow[ BinCode[CodePos] ]; NextByte(); //If Invalid instruction. if( Instruction === "" || Instruction == null ) { Instruction = "???"; InsOperands = ""; } } /*------------------------------------------------------------------------------------------------------------------------- Synthetic virtual machine operation codes. -------------------------------------------------------------------------------------------------------------------------*/ else if( Instruction === "SSS" ) { //The Next two bytes after the static opcode is the select synthetic virtual machine operation code. var Code1 = BinCode[CodePos]; NextByte(); var Code2 = BinCode[CodePos]; NextByte(); //No operations exist past 4 in value for both bytes that combine to the operation code. if( Code1 >= 5 || Code2 >= 5 ) { Instruction = "???"; } //Else calculate the operation code in the 5x5 map. else { Instruction = MSynthetic[ ( Code1 * 5 ) + Code2 ]; //If Invalid instruction. if( Instruction === "" || Instruction == null ) { Instruction = "???"; } } } //32/16 bit instructions 9A, and EA use Segment, and offset with Immediate format. if( Opcode === 0x9A || Opcode === 0xEA ) { var t = InsOperands.split(","); InsOperands = t[1] + ":" +t[0]; } //**Depending on the operation different prefixes replace others for HLE, or MPX, and branch prediction. //if REP prefix, and LOCK prefix are used together, and the current decoded operation allows HLE XRELEASE. if(PrefixG1 === Mnemonics[0xF3] && PrefixG2 === Mnemonics[0xF0] && XRelease) { PrefixG1 = "XRELEASE"; //Then change REP to XRELEASE. } //if REPNE prefix, and LOCK prefix are used together, and the current decoded operation allows HLE XACQUIRE. if(PrefixG1 === Mnemonics[0xF2] && PrefixG2 === Mnemonics[0xF0] && XAcquire) { PrefixG1 = "XACQUIRE"; //Then change REP to XACQUIRE } //Depending on the order that the Repeat prefix, and Lock prefix is used flip Prefix G1, and G2 if HLEFlipG1G2 it is true. if((PrefixG1 === "XRELEASE" || PrefixG1 === "XACQUIRE") && HLEFlipG1G2) { t = PrefixG1; PrefixG1 = PrefixG2; PrefixG2 = t; } //if HT is active then it is a jump instruction check and adjust for the HT,and HNT prefix. if(HT) { if (SegOverride === Mnemonics[0x2E]) { PrefixG1 = "HNT"; } else if (SegOverride === Mnemonics[0x3E]) { PrefixG1 = "HT"; } } //else if Prefix is REPNE switch it to BND if operation is a MPX instruction. if(PrefixG1 === Mnemonics[0xF2] && BND) { PrefixG1 = "BND"; } //Before the Instruction is put together check the length of the instruction if it is longer than 15 bytes the instruction is undefined. if ( InstructionHex.length > 30 ) { //Calculate how many bytes over. var Dif32 = ( ( InstructionHex.length - 30 ) >> 1 ); //Limit the instruction hex output to 15 bytes. InstructionHex = InstructionHex.substring( 0, 30 ); //Calculate the Difference between the Disassembler current position. Dif32 = Pos32 - Dif32; //Convert Dif to unsignified numbers. if( Dif32 < 0 ) { Dif32 += 0x100000000; } //Convert to strings. for (var S32 = Dif32.toString(16) ; S32.length < 8; S32 = "0" + S32); for (var S64 = Pos64.toString(16) ; S64.length < 8; S64 = "0" + S64); //Go to the Calculated address right after the Instruction UD. GotoPosition( S64 + S32 ); //Set prefixes, and operands to empty strings, and set Instruction to UD. PrefixG1 = "";PrefixG2 = ""; Instruction = "???"; InsOperands = ""; } //Put the Instruction sequence together. out = PrefixG1 + " " + PrefixG2 + " " + Instruction + " " + InsOperands; //Remove any trailing spaces because of unused prefixes. out = out.replace(/^[ ]+|[ ]+$/g,''); //Add error suppression if used. if( Opcode >= 0x700 || RoundMode !== 0 ) { out += RoundModes[ RoundMode ]; } //Return the instruction. } return( out ); } /*------------------------------------------------------------------------------------------------------------------------- This function Resets the Decoder in case of error, or an full instruction has been decoded. -------------------------------------------------------------------------------------------------------------------------*/ function Reset() { //Reset Opcode, and Size attribute selector. Opcode = 0; SizeAttrSelect = 1; //Reset Operands and instruction. Instruction = ""; InsOperands = ""; //Reset ModR/M. RexActive = 0; RegExtend = 0; BaseExtend = 0; IndexExtend = 0; SegOverride = "["; AddressOverride = false; FarPointer = 0; //Reset Vector extensions controls. Extension = 0; SIMD = 0; Vect = false; ConversionMode = 0; WidthBit = false; VectorRegister = 0; MaskRegister = 0; HInt_ZeroMerg = false; RoundMode = 0x00; //Reset vector format settings. IgnoresWidthbit = false; VSIB = false; RoundingSetting = 0; Swizzle = false; Up = false; Float = false; VectS = 0x00; //Reset IMMValue used for Imm register encoding, and Condition codes. IMMValue = 0; //Reset instruction Prefixes. PrefixG1 = "", PrefixG2 = ""; XRelease = false; XAcquire = false; HLEFlipG1G2 = false; HT = false; BND = false; //Reset Invalid operation code. InvalidOp = false; //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. InstructionHex = ""; //Deactivate all operands along the X86Decoder. for( var i = 0; i < X86Decoder.length; X86Decoder[i++].Deactivate() ); } /*------------------------------------------------------------------------------------------------------------------------- do an linear disassemble. -------------------------------------------------------------------------------------------------------------------------*/ export function LDisassemble() { var Instruction = ""; //Stores the Decoded instruction. var Out = ""; //The Disassemble output //Disassemble binary code using an linear pass. var len = BinCode.length; //Backup the base address. var BPos64 = Pos64, BPos32 = Pos32; while( CodePos < len ) { Instruction = DecodeInstruction(); //Add the 64 bit address of the output if ShowInstructionPos decoding is active. if( ShowInstructionPos ) { Out += InstructionPos + " "; } //Show Each byte that was read to decode the instruction if ShowInstructionHex decoding is active. if(ShowInstructionHex) { InstructionHex = InstructionHex.toUpperCase(); for(; InstructionHex.length < 32; InstructionHex = InstructionHex + " " ); Out += InstructionHex + ""; } //Put the decoded instruction into the output and make a new line. Out += Instruction + "\r\n"; //Reset instruction Pos and Hex. InstructionPos = ""; InstructionHex = ""; } CodePos = 0; //Reset the Code position Pos32 = BPos32; Pos64 = BPos64; //Reset Base address. //return the decoded binary code return(Out); } //////////////////////////////////////////////////////////////////////////////////////////////// /* * The following code has been added to expose public methods for use in CyberChef */ export function setBitMode (val) { BitMode = val; }; export function setShowInstructionHex (val) { ShowInstructionHex = val; }; export function setShowInstructionPos (val) { ShowInstructionPos = val; }; ================================================ FILE: src/core/vendor/gost/gostCipher.mjs ================================================ /** * GOST 28147-89/GOST R 34.12-2015/GOST R 32.13-2015 Encryption Algorithm * 1.76 * 2014-2016, Rudolf Nickolaev. All rights reserved. * * Exported for CyberChef by mshwed [m@ttshwed.com] */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ import GostRandom from './gostRandom.mjs'; import crypto from 'crypto' /* * Initial parameters and common algortithms of GOST 28147-89 * * http://tools.ietf.org/html/rfc5830 * */ // var root = {}; var rootCrypto = crypto; var CryptoOperationData = ArrayBuffer; var SyntaxError = Error, DataError = Error, NotSupportedError = Error; /* * Check supported * This implementation support only Little Endian arhitecture */ var littleEndian = (function () { var buffer = new CryptoOperationData(2); new DataView(buffer).setInt16(0, 256, true); return new Int16Array(buffer)[0] === 256; })(); // Default initial vector var defaultIV = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]); // Predefined sBox collection var sBoxes = { 'E-TEST': [ 0x4, 0x2, 0xF, 0x5, 0x9, 0x1, 0x0, 0x8, 0xE, 0x3, 0xB, 0xC, 0xD, 0x7, 0xA, 0x6, 0xC, 0x9, 0xF, 0xE, 0x8, 0x1, 0x3, 0xA, 0x2, 0x7, 0x4, 0xD, 0x6, 0x0, 0xB, 0x5, 0xD, 0x8, 0xE, 0xC, 0x7, 0x3, 0x9, 0xA, 0x1, 0x5, 0x2, 0x4, 0x6, 0xF, 0x0, 0xB, 0xE, 0x9, 0xB, 0x2, 0x5, 0xF, 0x7, 0x1, 0x0, 0xD, 0xC, 0x6, 0xA, 0x4, 0x3, 0x8, 0x3, 0xE, 0x5, 0x9, 0x6, 0x8, 0x0, 0xD, 0xA, 0xB, 0x7, 0xC, 0x2, 0x1, 0xF, 0x4, 0x8, 0xF, 0x6, 0xB, 0x1, 0x9, 0xC, 0x5, 0xD, 0x3, 0x7, 0xA, 0x0, 0xE, 0x2, 0x4, 0x9, 0xB, 0xC, 0x0, 0x3, 0x6, 0x7, 0x5, 0x4, 0x8, 0xE, 0xF, 0x1, 0xA, 0x2, 0xD, 0xC, 0x6, 0x5, 0x2, 0xB, 0x0, 0x9, 0xD, 0x3, 0xE, 0x7, 0xA, 0xF, 0x4, 0x1, 0x8 ], 'E-A': [ 0x9, 0x6, 0x3, 0x2, 0x8, 0xB, 0x1, 0x7, 0xA, 0x4, 0xE, 0xF, 0xC, 0x0, 0xD, 0x5, 0x3, 0x7, 0xE, 0x9, 0x8, 0xA, 0xF, 0x0, 0x5, 0x2, 0x6, 0xC, 0xB, 0x4, 0xD, 0x1, 0xE, 0x4, 0x6, 0x2, 0xB, 0x3, 0xD, 0x8, 0xC, 0xF, 0x5, 0xA, 0x0, 0x7, 0x1, 0x9, 0xE, 0x7, 0xA, 0xC, 0xD, 0x1, 0x3, 0x9, 0x0, 0x2, 0xB, 0x4, 0xF, 0x8, 0x5, 0x6, 0xB, 0x5, 0x1, 0x9, 0x8, 0xD, 0xF, 0x0, 0xE, 0x4, 0x2, 0x3, 0xC, 0x7, 0xA, 0x6, 0x3, 0xA, 0xD, 0xC, 0x1, 0x2, 0x0, 0xB, 0x7, 0x5, 0x9, 0x4, 0x8, 0xF, 0xE, 0x6, 0x1, 0xD, 0x2, 0x9, 0x7, 0xA, 0x6, 0x0, 0x8, 0xC, 0x4, 0x5, 0xF, 0x3, 0xB, 0xE, 0xB, 0xA, 0xF, 0x5, 0x0, 0xC, 0xE, 0x8, 0x6, 0x2, 0x3, 0x9, 0x1, 0x7, 0xD, 0x4 ], 'E-B': [ 0x8, 0x4, 0xB, 0x1, 0x3, 0x5, 0x0, 0x9, 0x2, 0xE, 0xA, 0xC, 0xD, 0x6, 0x7, 0xF, 0x0, 0x1, 0x2, 0xA, 0x4, 0xD, 0x5, 0xC, 0x9, 0x7, 0x3, 0xF, 0xB, 0x8, 0x6, 0xE, 0xE, 0xC, 0x0, 0xA, 0x9, 0x2, 0xD, 0xB, 0x7, 0x5, 0x8, 0xF, 0x3, 0x6, 0x1, 0x4, 0x7, 0x5, 0x0, 0xD, 0xB, 0x6, 0x1, 0x2, 0x3, 0xA, 0xC, 0xF, 0x4, 0xE, 0x9, 0x8, 0x2, 0x7, 0xC, 0xF, 0x9, 0x5, 0xA, 0xB, 0x1, 0x4, 0x0, 0xD, 0x6, 0x8, 0xE, 0x3, 0x8, 0x3, 0x2, 0x6, 0x4, 0xD, 0xE, 0xB, 0xC, 0x1, 0x7, 0xF, 0xA, 0x0, 0x9, 0x5, 0x5, 0x2, 0xA, 0xB, 0x9, 0x1, 0xC, 0x3, 0x7, 0x4, 0xD, 0x0, 0x6, 0xF, 0x8, 0xE, 0x0, 0x4, 0xB, 0xE, 0x8, 0x3, 0x7, 0x1, 0xA, 0x2, 0x9, 0x6, 0xF, 0xD, 0x5, 0xC ], 'E-C': [ 0x1, 0xB, 0xC, 0x2, 0x9, 0xD, 0x0, 0xF, 0x4, 0x5, 0x8, 0xE, 0xA, 0x7, 0x6, 0x3, 0x0, 0x1, 0x7, 0xD, 0xB, 0x4, 0x5, 0x2, 0x8, 0xE, 0xF, 0xC, 0x9, 0xA, 0x6, 0x3, 0x8, 0x2, 0x5, 0x0, 0x4, 0x9, 0xF, 0xA, 0x3, 0x7, 0xC, 0xD, 0x6, 0xE, 0x1, 0xB, 0x3, 0x6, 0x0, 0x1, 0x5, 0xD, 0xA, 0x8, 0xB, 0x2, 0x9, 0x7, 0xE, 0xF, 0xC, 0x4, 0x8, 0xD, 0xB, 0x0, 0x4, 0x5, 0x1, 0x2, 0x9, 0x3, 0xC, 0xE, 0x6, 0xF, 0xA, 0x7, 0xC, 0x9, 0xB, 0x1, 0x8, 0xE, 0x2, 0x4, 0x7, 0x3, 0x6, 0x5, 0xA, 0x0, 0xF, 0xD, 0xA, 0x9, 0x6, 0x8, 0xD, 0xE, 0x2, 0x0, 0xF, 0x3, 0x5, 0xB, 0x4, 0x1, 0xC, 0x7, 0x7, 0x4, 0x0, 0x5, 0xA, 0x2, 0xF, 0xE, 0xC, 0x6, 0x1, 0xB, 0xD, 0x9, 0x3, 0x8 ], 'E-D': [ 0xF, 0xC, 0x2, 0xA, 0x6, 0x4, 0x5, 0x0, 0x7, 0x9, 0xE, 0xD, 0x1, 0xB, 0x8, 0x3, 0xB, 0x6, 0x3, 0x4, 0xC, 0xF, 0xE, 0x2, 0x7, 0xD, 0x8, 0x0, 0x5, 0xA, 0x9, 0x1, 0x1, 0xC, 0xB, 0x0, 0xF, 0xE, 0x6, 0x5, 0xA, 0xD, 0x4, 0x8, 0x9, 0x3, 0x7, 0x2, 0x1, 0x5, 0xE, 0xC, 0xA, 0x7, 0x0, 0xD, 0x6, 0x2, 0xB, 0x4, 0x9, 0x3, 0xF, 0x8, 0x0, 0xC, 0x8, 0x9, 0xD, 0x2, 0xA, 0xB, 0x7, 0x3, 0x6, 0x5, 0x4, 0xE, 0xF, 0x1, 0x8, 0x0, 0xF, 0x3, 0x2, 0x5, 0xE, 0xB, 0x1, 0xA, 0x4, 0x7, 0xC, 0x9, 0xD, 0x6, 0x3, 0x0, 0x6, 0xF, 0x1, 0xE, 0x9, 0x2, 0xD, 0x8, 0xC, 0x4, 0xB, 0xA, 0x5, 0x7, 0x1, 0xA, 0x6, 0x8, 0xF, 0xB, 0x0, 0x4, 0xC, 0x3, 0x5, 0x9, 0x7, 0xD, 0x2, 0xE ], 'E-SC': [ 0x3, 0x6, 0x1, 0x0, 0x5, 0x7, 0xd, 0x9, 0x4, 0xb, 0x8, 0xc, 0xe, 0xf, 0x2, 0xa, 0x7, 0x1, 0x5, 0x2, 0x8, 0xb, 0x9, 0xc, 0xd, 0x0, 0x3, 0xa, 0xf, 0xe, 0x4, 0x6, 0xf, 0x1, 0x4, 0x6, 0xc, 0x8, 0x9, 0x2, 0xe, 0x3, 0x7, 0xa, 0xb, 0xd, 0x5, 0x0, 0x3, 0x4, 0xf, 0xc, 0x5, 0x9, 0xe, 0x0, 0x6, 0x8, 0x7, 0xa, 0x1, 0xb, 0xd, 0x2, 0x6, 0x9, 0x0, 0x7, 0xb, 0x8, 0x4, 0xc, 0x2, 0xe, 0xa, 0xf, 0x1, 0xd, 0x5, 0x3, 0x6, 0x1, 0x2, 0xf, 0x0, 0xb, 0x9, 0xc, 0x7, 0xd, 0xa, 0x5, 0x8, 0x4, 0xe, 0x3, 0x0, 0x2, 0xe, 0xc, 0x9, 0x1, 0x4, 0x7, 0x3, 0xf, 0x6, 0x8, 0xa, 0xd, 0xb, 0x5, 0x5, 0x2, 0xb, 0x8, 0x4, 0xc, 0x7, 0x1, 0xa, 0x6, 0xe, 0x0, 0x9, 0x3, 0xd, 0xf ], 'E-Z': [// This is default S-box in according to draft of new standard 0xc, 0x4, 0x6, 0x2, 0xa, 0x5, 0xb, 0x9, 0xe, 0x8, 0xd, 0x7, 0x0, 0x3, 0xf, 0x1, 0x6, 0x8, 0x2, 0x3, 0x9, 0xa, 0x5, 0xc, 0x1, 0xe, 0x4, 0x7, 0xb, 0xd, 0x0, 0xf, 0xb, 0x3, 0x5, 0x8, 0x2, 0xf, 0xa, 0xd, 0xe, 0x1, 0x7, 0x4, 0xc, 0x9, 0x6, 0x0, 0xc, 0x8, 0x2, 0x1, 0xd, 0x4, 0xf, 0x6, 0x7, 0x0, 0xa, 0x5, 0x3, 0xe, 0x9, 0xb, 0x7, 0xf, 0x5, 0xa, 0x8, 0x1, 0x6, 0xd, 0x0, 0x9, 0x3, 0xe, 0xb, 0x4, 0x2, 0xc, 0x5, 0xd, 0xf, 0x6, 0x9, 0x2, 0xc, 0xa, 0xb, 0x7, 0x8, 0x1, 0x4, 0x3, 0xe, 0x0, 0x8, 0xe, 0x2, 0x5, 0x6, 0x9, 0x1, 0xc, 0xf, 0x4, 0xb, 0x0, 0xd, 0xa, 0x3, 0x7, 0x1, 0x7, 0xe, 0xd, 0x0, 0x5, 0x8, 0x3, 0x4, 0xf, 0xa, 0x6, 0x9, 0xc, 0xb, 0x2 ], //S-box for digest 'D-TEST': [ 0x4, 0xA, 0x9, 0x2, 0xD, 0x8, 0x0, 0xE, 0x6, 0xB, 0x1, 0xC, 0x7, 0xF, 0x5, 0x3, 0xE, 0xB, 0x4, 0xC, 0x6, 0xD, 0xF, 0xA, 0x2, 0x3, 0x8, 0x1, 0x0, 0x7, 0x5, 0x9, 0x5, 0x8, 0x1, 0xD, 0xA, 0x3, 0x4, 0x2, 0xE, 0xF, 0xC, 0x7, 0x6, 0x0, 0x9, 0xB, 0x7, 0xD, 0xA, 0x1, 0x0, 0x8, 0x9, 0xF, 0xE, 0x4, 0x6, 0xC, 0xB, 0x2, 0x5, 0x3, 0x6, 0xC, 0x7, 0x1, 0x5, 0xF, 0xD, 0x8, 0x4, 0xA, 0x9, 0xE, 0x0, 0x3, 0xB, 0x2, 0x4, 0xB, 0xA, 0x0, 0x7, 0x2, 0x1, 0xD, 0x3, 0x6, 0x8, 0x5, 0x9, 0xC, 0xF, 0xE, 0xD, 0xB, 0x4, 0x1, 0x3, 0xF, 0x5, 0x9, 0x0, 0xA, 0xE, 0x7, 0x6, 0x8, 0x2, 0xC, 0x1, 0xF, 0xD, 0x0, 0x5, 0x7, 0xA, 0x4, 0x9, 0x2, 0x3, 0xE, 0x6, 0xB, 0x8, 0xC ], 'D-A': [ 0xA, 0x4, 0x5, 0x6, 0x8, 0x1, 0x3, 0x7, 0xD, 0xC, 0xE, 0x0, 0x9, 0x2, 0xB, 0xF, 0x5, 0xF, 0x4, 0x0, 0x2, 0xD, 0xB, 0x9, 0x1, 0x7, 0x6, 0x3, 0xC, 0xE, 0xA, 0x8, 0x7, 0xF, 0xC, 0xE, 0x9, 0x4, 0x1, 0x0, 0x3, 0xB, 0x5, 0x2, 0x6, 0xA, 0x8, 0xD, 0x4, 0xA, 0x7, 0xC, 0x0, 0xF, 0x2, 0x8, 0xE, 0x1, 0x6, 0x5, 0xD, 0xB, 0x9, 0x3, 0x7, 0x6, 0x4, 0xB, 0x9, 0xC, 0x2, 0xA, 0x1, 0x8, 0x0, 0xE, 0xF, 0xD, 0x3, 0x5, 0x7, 0x6, 0x2, 0x4, 0xD, 0x9, 0xF, 0x0, 0xA, 0x1, 0x5, 0xB, 0x8, 0xE, 0xC, 0x3, 0xD, 0xE, 0x4, 0x1, 0x7, 0x0, 0x5, 0xA, 0x3, 0xC, 0x8, 0xF, 0x6, 0x2, 0x9, 0xB, 0x1, 0x3, 0xA, 0x9, 0x5, 0xB, 0x4, 0xF, 0x8, 0x6, 0x7, 0xE, 0xD, 0x0, 0x2, 0xC ], 'D-SC': [ 0xb, 0xd, 0x7, 0x0, 0x5, 0x4, 0x1, 0xf, 0x9, 0xe, 0x6, 0xa, 0x3, 0xc, 0x8, 0x2, 0x1, 0x2, 0x7, 0x9, 0xd, 0xb, 0xf, 0x8, 0xe, 0xc, 0x4, 0x0, 0x5, 0x6, 0xa, 0x3, 0x5, 0x1, 0xd, 0x3, 0xf, 0x6, 0xc, 0x7, 0x9, 0x8, 0xb, 0x2, 0x4, 0xe, 0x0, 0xa, 0xd, 0x1, 0xb, 0x4, 0x9, 0xc, 0xe, 0x0, 0x7, 0x5, 0x8, 0xf, 0x6, 0x2, 0xa, 0x3, 0x2, 0xd, 0xa, 0xf, 0x9, 0xb, 0x3, 0x7, 0x8, 0xc, 0x5, 0xe, 0x6, 0x0, 0x1, 0x4, 0x0, 0x4, 0x6, 0xc, 0x5, 0x3, 0x8, 0xd, 0xa, 0xb, 0xf, 0x2, 0x1, 0x9, 0x7, 0xe, 0x1, 0x3, 0xc, 0x8, 0xa, 0x6, 0xb, 0x0, 0x2, 0xe, 0x7, 0x9, 0xf, 0x4, 0x5, 0xd, 0xa, 0xb, 0x6, 0x0, 0x1, 0x3, 0x4, 0x7, 0xe, 0xd, 0x5, 0xf, 0x8, 0x2, 0x9, 0xc ] }; var C = new Uint8Array([ 0x69, 0x00, 0x72, 0x22, 0x64, 0xC9, 0x04, 0x23, 0x8D, 0x3A, 0xDB, 0x96, 0x46, 0xE9, 0x2A, 0xC4, 0x18, 0xFE, 0xAC, 0x94, 0x00, 0xED, 0x07, 0x12, 0xC0, 0x86, 0xDC, 0xC2, 0xEF, 0x4C, 0xA9, 0x2B ]); function signed(x) { return x >= 0x80000000 ? x - 0x100000000 : x; } function unsigned(x) { return x < 0 ? x + 0x100000000 : x; } // Set random values into Uint8Arry // Random generator function randomSeed(e) { GostRandom = GostRandom || root.GostRandom; var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; if (randomSource.getRandomValues) randomSource.getRandomValues(e); else throw new NotSupportedError('Random generator not found'); } // Get buffer function buffer(d) { if (d instanceof CryptoOperationData) return d; else if (d && d?.buffer instanceof CryptoOperationData) return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; else throw new DataError('CryptoOperationData required'); } // Get byte array function byteArray(d) { return new Uint8Array(buffer(d)); } // Clone byte array function cloneArray(d) { return new Uint8Array(byteArray(d)); } // Get int32 array function intArray(d) { return new Int32Array(buffer(d)); } // Swap bytes for version 2015 function swap32(b) { return ((b & 0xff) << 24) | ((b & 0xff00) << 8) | ((b >> 8) & 0xff00) | ((b >> 24) & 0xff); } // /* * Initial parameters and common algortithms of GOST R 34.12-15 * Algorithm "Kuznechik" 128bit * */ // // Default initial vector var defaultIV128 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); // Mult table for R function var multTable = (function () { // Multiply two numbers in the GF(2^8) finite field defined // by the polynomial x^8 + x^7 + x^6 + x + 1 = 0 */ function gmul(a, b) { var p = 0, counter, carry; for (counter = 0; counter < 8; counter++) { if (b & 1) p ^= a; carry = a & 0x80; // detect if x^8 term is about to be generated a = (a << 1) & 0xff; if (carry) a ^= 0xc3; // replace x^8 with x^7 + x^6 + x + 1 b >>= 1; } return p & 0xff; } // It is required only this values for R function // 0 1 2 3 4 5 6 7 var x = [1, 16, 32, 133, 148, 192, 194, 251]; var m = []; for (var i = 0; i < 8; i++) { m[i] = []; for (var j = 0; j < 256; j++) m[i][j] = gmul(x[i], j); } return m; })(); // 148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148, 1 var kB = [4, 2, 3, 1, 6, 5, 0, 7, 0, 5, 6, 1, 3, 2, 4, 0]; // R - function function funcR(d) { var sum = 0; for (var i = 0; i < 16; i++) sum ^= multTable[kB[i]][d[i]]; for (var i = 16; i > 0; --i) d[i] = d[i - 1]; d[0] = sum; } function funcReverseR(d) { var tmp = d[0]; for (var i = 0; i < 15; i++) d[i] = d[i + 1]; d[15] = tmp; var sum = 0; for (i = 0; i < 16; i++) sum ^= multTable[kB[i]][d[i]]; d[15] = sum; } // Nonlinear transformation var kPi = [ 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77, 233, 119, 240, 219, 147, 46, 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, 249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, 139, 1, 142, 79, 5, 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31, 235, 52, 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204, 181, 112, 14, 86, 8, 12, 118, 18, 191, 114, 19, 71, 156, 183, 93, 135, 21, 161, 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, 178, 177, 50, 117, 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87, 223, 245, 36, 169, 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3, 224, 15, 236, 222, 122, 148, 176, 188, 220, 232, 40, 80, 78, 51, 10, 74, 167, 151, 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, 173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59, 7, 88, 179, 64, 134, 172, 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, 225, 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, 202, 216, 133, 97, 32, 113, 103, 164, 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82, 89, 166, 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182 ]; var kReversePi = (function () { var m = []; for (var i = 0, n = kPi.length; i < n; i++) m[kPi[i]] = i; return m; })(); function funcS(d) { for (var i = 0; i < 16; ++i) d[i] = kPi[d[i]]; } function funcReverseS(d) { for (var i = 0; i < 16; ++i) d[i] = kReversePi[d[i]]; } function funcX(a, b) { for (var i = 0; i < 16; ++i) a[i] ^= b[i]; } function funcL(d) { for (var i = 0; i < 16; ++i) funcR(d); } function funcReverseL(d) { for (var i = 0; i < 16; ++i) funcReverseR(d); } function funcLSX(a, b) { funcX(a, b); funcS(a); funcL(a); } function funcReverseLSX(a, b) { funcX(a, b); funcReverseL(a); funcReverseS(a); } function funcF(inputKey, inputKeySecond, iterationConst) { var tmp = new Uint8Array(inputKey); funcLSX(inputKey, iterationConst); funcX(inputKey, inputKeySecond); inputKeySecond.set(tmp); } function funcC(number, d) { for (var i = 0; i < 15; i++) d[i] = 0; d[15] = number; funcL(d); } // /** * Key schedule for GOST R 34.12-15 128bits * * @memberOf GostCipher * @private * @instance * @method keySchedule * @param {type} k * @returns {Uint8Array} */ function keySchedule128(k) // { var keys = new Uint8Array(160), c = new Uint8Array(16); keys.set(byteArray(k)); for (var j = 0; j < 4; j++) { var j0 = 32 * j, j1 = 32 * (j + 1); keys.set(new Uint8Array(keys.buffer, j0, 32), j1); for (var i = 1; i < 9; i++) { funcC(j * 8 + i, c); funcF(new Uint8Array(keys.buffer, j1, 16), new Uint8Array(keys.buffer, j1 + 16, 16), c); } } return keys; } // /** * GOST R 34.12-15 128 bits encrypt/decrypt process * * @memberOf GostCipher * @private * @instance * @method round * @param {Uint8Array} k Scheduled key * @param {Uint8Array} d Data * @param {number} ofs Offsec * @param {number} e true - decrypt */ function process128(k, d, ofs, e) // { ofs = ofs || d.byteOffset; var r = new Uint8Array(d.buffer, ofs, 16); if (e) { for (var i = 0; i < 9; i++) funcReverseLSX(r, new Uint8Array(k.buffer, (9 - i) * 16, 16)); funcX(r, new Uint8Array(k.buffer, 0, 16)); } else { for (var i = 0; i < 9; i++) funcLSX(r, new Uint8Array(k.buffer, 16 * i, 16)); funcX(r, new Uint8Array(k.buffer, 16 * 9, 16)); } } // /** * One GOST encryption round * * @memberOf GostCipher * @private * @instance * @method round * @param {Int8Array} S sBox * @param {Int32Array} m 2x32 bits cipher block * @param {Int32Array} k 32 bits key[i] */ function round(S, m, k) // { var cm = (m[0] + k) & 0xffffffff; var om = S[ 0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4); om |= S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4); om |= S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4); om |= S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4); om |= S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4); om |= S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4); om |= S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4); om |= S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4); cm = om << 11 | om >>> (32 - 11); cm ^= m[1]; m[1] = m[0]; m[0] = cm; } // /** * Process encrypt/decrypt block with key K using GOST 28147-89 * * @memberOf GostCipher * @private * @instance * @method process * @param k {Int32Array} 8x32 bits key * @param d {Int32Array} 8x8 bits cipher block * @param ofs {number} offset */ function process89(k, d, ofs) // { ofs = ofs || d.byteOffset; var s = this.sBox, m = new Int32Array(d.buffer, ofs, 2); for (var i = 0; i < 32; i++) round(s, m, k[i]); var r = m[0]; m[0] = m[1]; m[1] = r; } // /** * Process encrypt/decrypt block with key K using GOST R 34.12-15 64bit block * * @memberOf GostCipher * @private * @instance * @method process * @param k {Int32Array} 8x32 bits key * @param d {Int32Array} 8x8 bits cipher block * @param ofs {number} offset */ function process15(k, d, ofs) // { ofs = ofs || d.byteOffset; var s = this.sBox, m = new Int32Array(d.buffer, ofs, 2), r = swap32(m[0]); m[0] = swap32(m[1]); m[1] = r; for (var i = 0; i < 32; i++) round(s, m, k[i]); m[0] = swap32(m[0]); m[1] = swap32(m[1]); } // /** * Key keySchedule algorithm for GOST 28147-89 64bit cipher * * @memberOf GostCipher * @private * @instance * @method process * @param k {Uint8Array} 8 bit key array * @param e {boolean} true - decrypt * @returns {Int32Array} keyScheduled 32-bit key */ function keySchedule89(k, e) // { var sch = new Int32Array(32), key = new Int32Array(buffer(k)); for (var i = 0; i < 8; i++) sch[i] = key[i]; if (e) { for (var i = 0; i < 8; i++) sch[i + 8] = sch[7 - i]; for (var i = 0; i < 8; i++) sch[i + 16] = sch[7 - i]; } else { for (var i = 0; i < 8; i++) sch[i + 8] = sch[i]; for (var i = 0; i < 8; i++) sch[i + 16] = sch[i]; } for (var i = 0; i < 8; i++) sch[i + 24] = sch[7 - i]; return sch; } // /** * Key keySchedule algorithm for GOST R 34.12-15 64bit cipher * * @memberOf GostCipher * @private * @instance * @method process * @param k {Uint8Array} 8 bit key array * @param e {boolean} true - decrypt * @returns {Int32Array} keyScheduled 32-bit key */ function keySchedule15(k, e) // { var sch = new Int32Array(32), key = new Int32Array(buffer(k)); for (var i = 0; i < 8; i++) sch[i] = swap32(key[i]); if (e) { for (var i = 0; i < 8; i++) sch[i + 8] = sch[7 - i]; for (var i = 0; i < 8; i++) sch[i + 16] = sch[7 - i]; } else { for (var i = 0; i < 8; i++) sch[i + 8] = sch[i]; for (var i = 0; i < 8; i++) sch[i + 16] = sch[i]; } for (var i = 0; i < 8; i++) sch[i + 24] = sch[7 - i]; return sch; } // /** * Key schedule for RC2 * * https://tools.ietf.org/html/rfc2268 * * @memberOf GostCipher * @private * @instance * @method keySchedule * @param {Uint8Array} k * @returns {Uint16Array} */ var keyScheduleRC2 = (function () // { // an array of "random" bytes based on the digits of PI = 3.14159... var PITABLE = new Uint8Array([ 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d, 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2, 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32, 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82, 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc, 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26, 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03, 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7, 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a, 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec, 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39, 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31, 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9, 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9, 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e, 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad ]); return function (k) { var key = new Uint8Array(buffer(k)), T = Math.min(key.length, 128), T1 = this.effectiveLength, T8 = Math.floor((T1 + 7) / 8), TM = 0xff % Math.pow(2, 8 + T1 - 8 * T8); var L = new Uint8Array(128), K = new Uint16Array(L.buffer); for (var i = 0; i < T; i++) L[i] = key[i]; for (var i = T; i < 128; i++) L[i] = PITABLE[(L[i - 1] + L[i - T]) % 256]; L[128 - T8] = PITABLE[L[128 - T8] & TM]; for (var i = 127 - T8; i >= 0; --i) L[i] = PITABLE[L[i + 1] ^ L[i + T8]]; return K; }; } // )(); /** * RC2 encrypt/decrypt process * * https://tools.ietf.org/html/rfc2268 * * @memberOf GostCipher * @private * @instance * @method round * @param {CryptoOperationData} k Scheduled key * @param {CryptoOperationData} d Data * @param {number} ofs Offsec * @param {number} e true - decrypt */ var processRC2 = (function () // { var K, j, R = new Uint16Array(4), s = new Uint16Array([1, 2, 3, 5]), reverse; function rol(R, s) { return (R << s | R >>> (16 - s)) & 0xffff; } function ror(R, s) { return (R >>> s | R << (16 - s)) & 0xffff; } function mix(i) { if (reverse) { R[i] = ror(R[i], s[i]); R[i] = R[i] - K[j] - (R[(i + 3) % 4] & R[(i + 2) % 4]) - ((~R[(i + 3) % 4]) & R[(i + 1) % 4]); j = j - 1; } else { R[i] = R[i] + K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) + ((~R[(i + 3) % 4]) & R[(i + 1) % 4]); j = j + 1; R[i] = rol(R[i], s[i]); } } function mash(i) { if (reverse) { R[i] = R[i] - K[R[(i + 3) % 4] & 63]; } else { R[i] = R[i] + K[R[(i + 3) % 4] & 63]; } } function perform(method, count) { count = count || 1; for (var j = 0; j < count; j++) { if (reverse) { for (var i = 3; i >= 0; --i) method(i); } else { for (var i = 0; i < 4; i++) method(i); } } } return function (k, d, ofs, e) { reverse = e; // 1. Initialize words R[0], ..., R[3] to contain the 64-bit // ciphertext value. R = new Uint16Array(d.buffer, ofs || d.byteOffset, 4); // 2. Expand the key, so that words K[0], ..., K[63] become // defined. K = k; // 3. Initialize j to zero (enc) j to 63 (dec). j = e ? 63 : 0; // 4. Perform five mixing rounds. perform(mix, 5); // 5. Perform one mashing round. perform(mash); // 6. Perform six mixing rounds. perform(mix, 6); // 7. Perform one mashing round. perform(mash); // 8. Perform five mixing rounds. perform(mix, 5); }; } // )(); /** * Algorithm name GOST 28147-ECB

* * encryptECB (K, D) is D, encrypted with key k using GOST 28147/GOST R 34.13 in * "prostaya zamena" (Electronic Codebook, ECB) mode. * @memberOf GostCipher * @method encrypt * @instance * @param k {CryptoOperationData} 8x32 bit key * @param d {CryptoOperationData} 8 bits message * @return {CryptoOperationData} result */ function encryptECB(k, d) // { var p = this.pad(byteArray(d)), n = this.blockSize, b = p.byteLength / n, key = this.keySchedule(k); for (var i = 0; i < b; i++) this.process(key, p, n * i); return p.buffer; } // /** * Algorithm name GOST 28147-ECB

* * decryptECB (K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 in * "prostaya zamena" (Electronic Codebook, ECB) mode. * * @memberOf GostCipher * @method decrypt * @instance * @param k {CryptoOperationData} 8x32 bits key * @param d {CryptoOperationData} 8 bits message * @return {CryptoOperationData} result */ function decryptECB(k, d) // { var p = cloneArray(d), n = this.blockSize, b = p.byteLength / n, key = this.keySchedule(k, 1); for (var i = 0; i < b; i++) this.process(key, p, n * i, 1); return this.unpad(p).buffer; } // /** * Algorithm name GOST 28147-CFB

* * encryptCFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 * in "gammirovanie s obratnoj svyaziyu" (Cipher Feedback, CFB) mode, and IV is * used as the initialization vector. * * @memberOf GostCipher * @method encrypt * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv initial vector * @return {CryptoOperationData} result */ function encryptCFB(k, d, iv) // { var s = new Uint8Array(iv || this.iv), c = cloneArray(d), m = s.length, t = new Uint8Array(m), b = this.shiftBits >> 3, cb = c.length, r = cb % b, q = (cb - r) / b, key = this.keySchedule(k); for (var i = 0; i < q; i++) { for (var j = 0; j < m; j++) t[j] = s[j]; this.process(key, s); for (var j = 0; j < b; j++) c[i * b + j] ^= s[j]; for (var j = 0; j < m - b; j++) s[j] = t[b + j]; for (var j = 0; j < b; j++) s[m - b + j] = c[i * b + j]; k = this.keyMeshing(k, s, i, key); } if (r > 0) { this.process(key, s); for (var i = 0; i < r; i++) c[q * b + i] ^= s[i]; } return c.buffer; } // /** * Algorithm name GOST 28147-CFB

* * decryptCFB (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 * in "gammirovanie s obratnoj svyaziyu po shifrotekstu" (Cipher Feedback, CFB) mode, and IV is * used as the initialization vector. * * @memberOf GostCipher * @method decrypt * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv initial vector * @return {CryptoOperationData} result */ function decryptCFB(k, d, iv) // { var s = new Uint8Array(iv || this.iv), c = cloneArray(d), m = s.length, t = new Uint8Array(m), b = this.shiftBits >> 3, cb = c.length, r = cb % b, q = (cb - r) / b, key = this.keySchedule(k); for (var i = 0; i < q; i++) { for (var j = 0; j < m; j++) t[j] = s[j]; this.process(key, s); for (var j = 0; j < b; j++) { t[j] = c[i * b + j]; c[i * b + j] ^= s[j]; } for (var j = 0; j < m - b; j++) s[j] = t[b + j]; for (var j = 0; j < b; j++) s[m - b + j] = t[j]; k = this.keyMeshing(k, s, i, key); } if (r > 0) { this.process(key, s); for (var i = 0; i < r; i++) c[q * b + i] ^= s[i]; } return c.buffer; } // /** * Algorithm name GOST 28147-OFB

* * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 * in "gammirovanie s obratnoj svyaziyu po vyhodu" (Output Feedback, OFB) mode, and IV is * used as the initialization vector. * * @memberOf GostCipher * @method encrypt * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv 8x8 optional bits initial vector * @return {CryptoOperationData} result */ /** * Algorithm name GOST 28147-OFB

* * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 * in "gammirovanie s obratnoj svyaziyu po vyhodu" (Output Feedback, OFB) mode, and IV is * used as the initialization vector. * * @memberOf GostCipher * @method decrypt * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv initial vector * @return {CryptoOperationData} result */ function processOFB(k, d, iv) // { var s = new Uint8Array(iv || this.iv), c = cloneArray(d), m = s.length, t = new Uint8Array(m), b = this.shiftBits >> 3, p = new Uint8Array(b), cb = c.length, r = cb % b, q = (cb - r) / b, key = this.keySchedule(k); for (var i = 0; i < q; i++) { for (var j = 0; j < m; j++) t[j] = s[j]; this.process(key, s); for (var j = 0; j < b; j++) p[j] = s[j]; for (var j = 0; j < b; j++) c[i * b + j] ^= s[j]; for (var j = 0; j < m - b; j++) s[j] = t[b + j]; for (var j = 0; j < b; j++) s[m - b + j] = p[j]; k = this.keyMeshing(k, s, i, key); } if (r > 0) { this.process(key, s); for (var i = 0; i < r; i++) c[q * b + i] ^= s[i]; } return c.buffer; } // /** * Algorithm name GOST 28147-CTR

* * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 * in "gammirovanie" (Counter Mode-CTR) mode, and IV is used as the * initialization vector. * @memberOf GostCipher * @method encrypt * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv 8x8 optional bits initial vector * @return {CryptoOperationData} result */ /** * Algorithm name GOST 28147-CTR

* * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 * in "gammirovanie" (Counter Mode-CTR) mode, and IV is used as the * initialization vector. * @memberOf GostCipher * @method decrypt * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv initial vector * @return {CryptoOperationData} result */ function processCTR89(k, d, iv) // { var s = new Uint8Array(iv || this.iv), c = cloneArray(d), b = this.blockSize, t = new Int8Array(b), cb = c.length, r = cb % b, q = (cb - r) / b, key = this.keySchedule(k), syn = new Int32Array(s.buffer); this.process(key, s); for (var i = 0; i < q; i++) { syn[0] = (syn[0] + 0x1010101) & 0xffffffff; // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff); var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff); for (var j = 0; j < b; j++) t[j] = s[j]; this.process(key, syn); for (var j = 0; j < b; j++) c[i * b + j] ^= s[j]; for (var j = 0; j < b; j++) s[j] = t[j]; k = this.keyMeshing(k, s, i, key); } if (r > 0) { syn[0] = (syn[0] + 0x1010101) & 0xffffffff; // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff); var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff); this.process(key, syn); for (var i = 0; i < r; i++) c[q * b + i] ^= s[i]; } return c.buffer; } // function processCTR15(k, d, iv) // { var c = cloneArray(d), n = this.blockSize, b = this.shiftBits >> 3, cb = c.length, r = cb % b, q = (cb - r) / b, s = new Uint8Array(n), t = new Int32Array(n), key = this.keySchedule(k); s.set(iv || this.iv); for (var i = 0; i < q; i++) { for (var j = 0; j < n; j++) t[j] = s[j]; this.process(key, s); for (var j = 0; j < b; j++) c[b * i + j] ^= s[j]; for (var j = 0; j < n; j++) s[j] = t[j]; for (var j = n - 1; i >= 0; --i) { if (s[j] > 0xfe) { s[j] -= 0xfe; } else { s[j]++; break; } } } if (r > 0) { this.process(key, s); for (var j = 0; j < r; j++) c[b * q + j] ^= s[j]; } return c.buffer; } // /** * Algorithm name GOST 28147-CBC

* * encryptCBC (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 * in "Prostaya zamena s zatsepleniem" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization * vector. * * @memberOf GostCipher * @method encrypt * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv initial vector * @return {CryptoOperationData} result */ function encryptCBC(k, d, iv) // { var s = new Uint8Array(iv || this.iv), n = this.blockSize, m = s.length, c = this.pad(byteArray(d)), key = this.keySchedule(k); for (var i = 0, b = c.length / n; i < b; i++) { for (var j = 0; j < n; j++) s[j] ^= c[i * n + j]; this.process(key, s); for (var j = 0; j < n; j++) c[i * n + j] = s[j]; if (m !== n) { for (var j = 0; j < m - n; j++) s[j] = s[n + j]; for (var j = 0; j < n; j++) s[j + m - n] = c[i * n + j]; } k = this.keyMeshing(k, s, i, key); } return c.buffer; } // /** * Algorithm name GOST 28147-CBC

* * decryptCBC (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 * in "Prostaya zamena s zatsepleniem" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization * vector. * * @memberOf GostCipher * @method decrypt * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv initial vector * @return {CryptoOperationData} result */ function decryptCBC(k, d, iv) // { var s = new Uint8Array(iv || this.iv), n = this.blockSize, m = s.length, c = cloneArray(d), next = new Uint8Array(n), key = this.keySchedule(k, 1); for (var i = 0, b = c.length / n; i < b; i++) { for (var j = 0; j < n; j++) next[j] = c[i * n + j]; this.process(key, c, i * n, 1); for (var j = 0; j < n; j++) c[i * n + j] ^= s[j]; if (m !== n) { for (var j = 0; j < m - n; j++) s[j] = s[n + j]; } for (var j = 0; j < n; j++) s[j + m - n] = next[j]; k = this.keyMeshing(k, s, i, key, 1); } return this.unpad(c).buffer; } // /** * The generateKey method returns a new generated key. * * @memberOf GostCipher * @method generateKey * @instance * @return {CryptoOperationData} result */ function generateKey() // { // Simple generate 256 bit random seed var k = new Uint8Array(this.keySize); randomSeed(k); return k.buffer; } // /** * makeIMIT (K, D) is the 32-bit result of the GOST 28147/GOST R 34.13 in * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV * as initialization vector. Note that the standard specifies its use * in this mode only with an initialization vector of zero. * * @memberOf GostCipher * @method processMAC * @private * @instance * @param {Int32Array} key 8x32 bits key * @param {Int32Array} s 8x8 sum array * @param {Uint8Array} d 8 bits array with data * @return {Uint8Array} result */ function processMAC89(key, s, d) // { var c = zeroPad.call(this, byteArray(d)), n = this.blockSize, q = c.length / n, sBox = this.sBox, sum = new Int32Array(s.buffer); for (var i = 0; i < q; i++) { for (var j = 0; j < n; j++) s[j] ^= c[i * n + j]; for (var j = 0; j < 16; j++) // 1-16 steps round(sBox, sum, key[j]); } } // function processKeyMAC15(s) // { var t = 0, n = s.length; for (var i = n - 1; i >= 0; --i) { var t1 = s[i] >>> 7; s[i] = (s[i] << 1) & 0xff | t; t = t1; } if (t !== 0) { if (n === 16) s[15] ^= 0x87; else s[7] ^= 0x1b; } } // function processMAC15(key, s, d) // { var n = this.blockSize, sBox = this.sBox, c = byteArray(d), r = new Uint8Array(n); // R this.process(key, r); // K1 processKeyMAC15(r); if (d.byteLength % n !== 0) { c = bitPad.call(this, byteArray(d)); // K2 processKeyMAC15(r); } for (var i = 0, q = c.length / n; i < q; i++) { for (var j = 0; j < n; j++) s[j] ^= c[i * n + j]; if (i === q - 1) {// Last block for (var j = 0; j < n; j++) s[j] ^= r[j]; } this.process(key, s); } } // /** * signMAC (K, D, IV) is the 32-bit result of the GOST 28147/GOST R 34.13 in * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV * as initialization vector. Note that the standard specifies its use * in this mode only with an initialization vector of zero. * * @memberOf GostCipher * @method sign * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv initial vector * @return {CryptoOperationData} result */ function signMAC(k, d, iv) // { var key = this.keySchedule(k), s = new Uint8Array(iv || this.iv), m = Math.ceil(this.macLength >> 3) || this.blockSize >> 1; this.processMAC(key, s, d); var mac = new Uint8Array(m); // mac size mac.set(new Uint8Array(s.buffer, 0, m)); return mac.buffer; } // /** * verifyMAC (K, M, D, IV) the 32-bit result verification of the GOST 28147/GOST R 34.13 in * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV * as initialization vector. Note that the standard specifies its use * in this mode only with an initialization vector of zero. * * @memberOf GostCipher * @method verify * @instance * @param {CryptoOperationData} k 8x32 bits key * @param {CryptoOperationData} m 8 bits array with signature * @param {CryptoOperationData} d 8 bits array with data * @param {CryptoOperationData} iv 8x8 optional bits initial vector * @return {boolen} MAC verified = true */ function verifyMAC(k, m, d, iv) // { var mac = new Uint8Array(signMAC.call(this, k, d, iv)), test = byteArray(m); if (mac.length !== test.length) return false; for (var i = 0, n = mac.length; i < n; i++) if (mac[i] !== test[i]) return false; return true; } // /** * Algorithm name GOST 28147-KW

* * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147/GOST R 34.13 KEK. * Ref. rfc4357 6.1 GOST 28147-89 Key Wrap * Note: This algorithm MUST NOT be used with a KEK produced by VKO GOST * R 34.10-94, because such a KEK is constant for every sender-recipient * pair. Encrypting many different content encryption keys on the same * constant KEK may reveal that KEK. * * @memberOf GostCipher * @method wrapKey * @instance * @param {CryptoOperationData} kek Key encryption key * @param {CryptoOperationData} cek Content encryption key * @returns {CryptoOperationData} Encrypted cek */ function wrapKeyGOST(kek, cek) // { var n = this.blockSize, k = this.keySize, len = k + (n >> 1); // 1) For a unique symmetric KEK, generate 8 octets at random and call // the result UKM. For a KEK, produced by VKO GOST R 34.10-2001, use // the UKM that was used for key derivation. if (!this.ukm) throw new DataError('UKM must be defined'); var ukm = new Uint8Array(this.ukm); // 2) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK). // Call the result CEK_MAC. var mac = signMAC.call(this, kek, cek, ukm); // 3) Encrypt the CEK in ECB mode using the KEK. Call the ciphertext CEK_ENC. var enc = encryptECB.call(this, kek, cek); // 4) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC). var r = new Uint8Array(len); r.set(new Uint8Array(enc), 0); r.set(new Uint8Array(mac), k); return r.buffer; } // /** * Algorithm name GOST 28147-KW

* * This algorithm decrypts GOST 28147-89 CEK with a GOST 28147 KEK. * Ref. rfc4357 6.2 GOST 28147-89 Key Unwrap * * @memberOf GostCipher * @method unwrapKey * @instance * @param {type} kek Key encryption key * @param {type} data Content encryption key * @return {CryptoOperationData} result */ function unwrapKeyGOST(kek, data) // { var n = this.blockSize, k = this.keySize, len = k + (n >> 1); // 1) If the wrapped content-encryption key is not 44 octets, then error. var d = buffer(data); if (d.byteLength !== len) throw new DataError('Wrapping key size must be ' + len + ' bytes'); // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC, and CEK_MAC. // UKM is the most significant (first) 8 octets. CEK_ENC is next 32 octets, // and CEK_MAC is the least significant (last) 4 octets. if (!this.ukm) throw new DataError('UKM must be defined'); var ukm = new Uint8Array(this.ukm), enc = new Uint8Array(d, 0, k), mac = new Uint8Array(d, k, n >> 1); // 3) Decrypt CEK_ENC in ECB mode using the KEK. Call the output CEK. var cek = decryptECB.call(this, kek, enc); // 4) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK), // compare the result with CEK_MAC. If they are not equal, then error. var check = verifyMAC.call(this, kek, mac, cek, ukm); if (!check) throw new DataError('Error verify MAC of wrapping key'); return cek; } // /** * Algorithm name GOST 28147-CPKW

* * Given a random 64-bit UKM and a GOST 28147 key K, this algorithm * creates a new GOST 28147-89 key K(UKM). * Ref. rfc4357 6.3 CryptoPro KEK Diversification Algorithm * * @memberOf GostCipher * @method diversify * @instance * @private * @param {CryptoOperationData} kek Key encryption key * @param {CryptoOperationData} ukm Random generated value * @returns {CryptoOperationData} Diversified kek */ function diversifyKEK(kek, ukm) // { var n = this.blockSize; // 1) Let K[0] = K; var k = intArray(kek); // 2) UKM is split into components a[i,j]: // UKM = a[0]|..|a[7] (a[i] - byte, a[i,0]..a[i,7] - it’s bits) var a = []; for (var i = 0; i < n; i++) { a[i] = []; for (var j = 0; j < 8; j++) { a[i][j] = (ukm[i] >>> j) & 0x1; } } // 3) Let i be 0. // 4) K[1]..K[8] are calculated by repeating the following algorithm // eight times: for (var i = 0; i < n; i++) { // A) K[i] is split into components k[i,j]: // K[i] = k[i,0]|k[i,1]|..|k[i,7] (k[i,j] - 32-bit integer) // B) Vector S[i] is calculated: // S[i] = ((a[i,0]*k[i,0] + ... + a[i,7]*k[i,7]) mod 2^32) | // (((~a[i,0])*k[i,0] + ... + (~a[i,7])*k[i,7]) mod 2^32); var s = new Int32Array(2); for (var j = 0; j < 8; j++) { if (a[i][j]) s[0] = (s[0] + k[j]) & 0xffffffff; else s[1] = (s[1] + k[j]) & 0xffffffff; } // C) K[i+1] = encryptCFB (S[i], K[i], K[i]) var iv = new Uint8Array(s.buffer); k = new Int32Array(encryptCFB.call(this, k, k, iv)); // D) i = i + 1 } // 5) Let K(UKM) be K[8]. return k; } // /** * Algorithm name GOST 28147-CPKW

* * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK. * It can be used with any KEK (e.g., produced by VKO GOST R 34.10-94 or * VKO GOST R 34.10-2001) because a unique UKM is used to diversify the KEK. * Ref. rfc4357 6.3 CryptoPro Key Wrap * * @memberOf GostCipher * @method wrapKey * @instance * @param {CryptoOperationData} kek Key encryption key * @param {CryptoOperationData} cek Content encryption key * @returns {CryptoOperationData} Encrypted cek */ function wrapKeyCP(kek, cek) // { var n = this.blockSize, k = this.keySize, len = k + (n >> 1); // 1) For a unique symmetric KEK or a KEK produced by VKO GOST R // 34.10-94, generate 8 octets at random. Call the result UKM. For // a KEK, produced by VKO GOST R 34.10-2001, use the UKM that was // used for key derivation. if (!this.ukm) throw new DataError('UKM must be defined'); var ukm = new Uint8Array(this.ukm); // 2) Diversify KEK, using the CryptoPro KEK Diversification Algorithm, // described in Section 6.5. Call the result KEK(UKM). var dek = diversifyKEK.call(this, kek, ukm); // 3) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM), // CEK). Call the result CEK_MAC. var mac = signMAC.call(this, dek, cek, ukm); // 4) Encrypt CEK in ECB mode using KEK(UKM). Call the ciphertext // CEK_ENC. var enc = encryptECB.call(this, dek, cek); // 5) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC). var r = new Uint8Array(len); r.set(new Uint8Array(enc), 0); r.set(new Uint8Array(mac), k); return r.buffer; } // /** * Algorithm name GOST 28147-CPKW

* * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK. * Ref. rfc4357 6.4 CryptoPro Key Unwrap * * @memberOf GostCipher * @method unwrapKey * @instance * @param {CryptoOperationData} kek Key encryption key * @param {CryptoOperationData} data Encrypted content encryption keu * @return {CryptoOperationData} result Decrypted content encryption keu */ function unwrapKeyCP(kek, data) // { var n = this.blockSize, k = this.keySize, len = k + (n >> 1); // 1) If the wrapped content-encryption key is not 44 octets, then error. var d = buffer(data); if (d.byteLength !== len) throw new DataError('Wrapping key size must be ' + len + ' bytes'); // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC, // and CEK_MAC. UKM is the most significant (first) 8 octets. // CEK_ENC is next 32 octets, and CEK_MAC is the least significant // (last) 4 octets. if (!this.ukm) throw new DataError('UKM must be defined'); var ukm = new Uint8Array(this.ukm), enc = new Uint8Array(d, 0, k), mac = new Uint8Array(d, k, n >> 1); // 3) Diversify KEK using the CryptoPro KEK Diversification Algorithm, // described in section 6.5. Call the result KEK(UKM). var dek = diversifyKEK.call(this, kek, ukm); // 4) Decrypt CEK_ENC in ECB mode using KEK(UKM). Call the output CEK. var cek = decryptECB.call(this, dek, enc); // 5) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM), // CEK), compare the result with CEK_MAC. If they are not equal, // then it is an error. var check = verifyMAC.call(this, dek, mac, cek, ukm); if (!check) throw new DataError('Error verify MAC of wrapping key'); return cek; } // /** * SignalCom master key packing algorithm * * kek stored in 3 files - kek.opq, mk.db3, masks.db3 * kek.opq - always 36 bytes length = 32 bytes encrypted kek + 4 bytes mac of decrypted kek * mk.db3 - 6 bytes header (1 byte magic code 0x22 + 1 byte count of masks + 4 bytes mac of * xor summarizing masks value) + attached masks * masks.db3 - detached masks. * Total length of attached + detached masks = 32 bits * count of masks * Default value of count 8 = (7 attached + 1 detached). But really no reason for such * separation - all masks xor summarizing - order is not matter. * Content of file rand.opq can used as ukm. Don't forget change file content after using. * * For usb-token files has names: * a001 - mk.db3, b001 - masks.db3, c001 - kek.opq, d001 - rand.opq * For windows registry * 00000001 - mk.db3, 00000002 - masks.db3, 00000003 - key.opq, 00000004 - rand.opq, * 00000006 - keys\00000001.key, 0000000A - certificate * * @memberOf GostCipher * @method packKey * @instance * @private * @param {CryptoOperationData} unpacked - clear main key 32 bytes * @param {CryptoOperationData} ukm - random vector for packing - 32 bytes * (count of masks - 1) * @returns {CryptoOperationData} packed master key - concatination of mk.db3 + masks.db3 */ function packKeySC(unpacked, ukm) // { var m = this.blockSize >> 1, k = this.keySize; var mcount = 8; var key = new Uint8Array(buffer(unpacked)); if (key.byteLength !== k) throw new DataError('Wrong cleartext size ' + key.byteLength + ' bytes'); // Check or generate UKM ukm = ukm || this.ukm; if (ukm) { ukm = new Uint8Array(buffer(ukm)); if (ukm.byteLength > 0 && ukm.byteLength % k === 0) mcount = ukm.byteLength / k + 1; else throw new DataError('Wrong rand size ' + ukm.byteLength + ' bytes'); } else randomSeed(ukm = new Uint8Array((mcount - 1) * k)); // Output array var d = new Uint8Array(mcount * k + m + 2), b = d.buffer; // Calculate MAC var zero32 = new Uint8Array(k); var mac = signMAC.call(this, key, zero32); d[0] = 0x22; // Magic code d[1] = mcount; // Count of masks d.set(new Uint8Array(mac), 2); d.set(ukm, k + m + 2); for (var i = 1; i < mcount; i++) { var mask = new Uint8Array(b, 2 + m + k * i); for (var j = 0; j < k; j++) key[j] ^= mask[j]; } d.set(key, m + 2); return d.buffer; } // /** * Algorithm name GOST 28147-SCKW

* * SignalCom master key unpacking algorithm * * @memberOf GostCipher * @method unpackKey * @instance * @private * @param {CryptoOperationData} packed - concatination of mk.db3 + masks.db3 * @returns {CryptoOperationData} unpacked master key */ function unpackKeySC(packed) // { var m = this.blockSize >> 1, k = this.keySize; var b = buffer(packed); // Unpack master key var magic = new Uint8Array(b, 0, 1)[0]; if (magic !== 0x22) throw new DataError('Invalid magic number'); var mcount = new Uint8Array(b, 1, 1)[0]; var mac = new Uint8Array(b, 2, m); // MAC for summarized mask // Compute packKey xor summing for all masks var key = new Uint8Array(k); for (var i = 0; i < mcount; i++) { var mask = new Uint8Array(b, 2 + m + k * i, k); for (var j = 0; j < k; j++) key[j] ^= mask[j]; } // Test MAC for packKey with default sBox on zero 32 bytes array var zero32 = new Uint8Array(k); var test = verifyMAC.call(this, key, mac, zero32); if (!test) { // Try to use different sBoxes var names = ['E-A', 'E-B', 'E-C', 'E-D', 'E-SC']; for (var i = 0, n = names.length; i < n; i++) { this.sBox = sBoxes[names[i]]; test = verifyMAC.call(this, key, mac, zero32); if (test) break; } } if (!test) throw new DataError('Invalid main key MAC'); return key.buffer; } // /** * Algorithm name GOST 28147-SCKW

* * SignalCom Key Wrapping algorithm * * @memberOf GostCipher * @method wrapKey * @instance * @param {CryptoOperationData} kek - clear kek or concatination of mk.db3 + masks.db3 * @param {CryptoOperationData} cek - key for wrapping * @returns {CryptoOperationData} wrapped key - file kek.opq */ function wrapKeySC(kek, cek) // { var m = this.blockSize >> 1, n = this.keySize; var k = buffer(kek); var c = buffer(cek); if (k.byteLength !== n) k = unpackKeySC.call(this, k); var enc = encryptECB.call(this, k, c); var mac = signMAC.call(this, k, c); var d = new Uint8Array(m + n); d.set(new Uint8Array(enc), 0); d.set(new Uint8Array(mac), n); return d.buffer; } // /** * Algorithm name GOST 28147-SCKW

* * SignalCom Key UnWrapping algorithm * * @memberOf GostCipher * @method unwrapKey * @instance * @param {CryptoOperationData} kek - concatination of files mk.db3 + masks.db3 or clear kek * @param {CryptoOperationData} cek - wrapping key - file kek.opq * @return {CryptoOperationData} result */ function unwrapKeySC(kek, cek) // { var m = this.blockSize >> 1, n = this.keySize; var k = buffer(kek); var c = buffer(cek); if (k.byteLength !== n) k = unpackKeySC.call(this, k); var enc = new Uint8Array(c, 0, n); // Encrypted kek var mac = new Uint8Array(c, n, m); // MAC for clear kek var d = decryptECB.call(this, k, enc); if (!verifyMAC.call(this, k, mac, d)) throw new DataError('Invalid key MAC'); return d; } // /** * Algorithm name GOST 28147-SCKW

* * SignalCom master key generation for wrapping * * @memberOf GostCipher * @method generateKey * @instance * @return {CryptoOperationData} result */ function generateWrappingKeySC() // { return packKeySC.call(this, generateKey.call(this)); } // function maskKey(mask, key, inverse, keySize) // { var k = keySize / 4, m32 = new Int32Array(buffer(mask)), k32 = new Int32Array(buffer(key)), r32 = new Int32Array(k); if (inverse) for (var i = 0; i < k; i++) r32[i] = (k32[i] + m32[i]) & 0xffffffff; else for (var i = 0; i < k; i++) r32[i] = (k32[i] - m32[i]) & 0xffffffff; return r32.buffer; } // /** * Algorithm name GOST 28147-MASK

* * This algorithm wrap key mask * * @memberOf GostCipher * @method wrapKey * @instance * @param {CryptoOperationData} mask The mask * @param {CryptoOperationData} key The key * @returns {CryptoOperationData} The masked key */ function wrapKeyMask(mask, key) // { return maskKey(mask, key, this.procreator === 'VN', this.keySize); } // /** * Algorithm name GOST 28147-CPKW

* * This algorithm unwrap key mask * * @memberOf GostCipher * @method unwrapKey * @instance * @param {CryptoOperationData} mask The mask * @param {CryptoOperationData} key The masked key * @return {CryptoOperationData} result The key */ function unwrapKeyMask(mask, key) // { return maskKey(mask, key, this.procreator !== 'VN', this.keySize); } // /** * Algorithm name GOST 28147-CPKM

* * Key meshing in according to rfc4357 2.3.2. CryptoPro Key Meshing * * @memberOf GostCipher * @method keyMeshing * @instance * @private * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key * @param {Uint8Array} s 8x8 bit sync (iv) * @param {Integer} i block index * @param {Int32Array} key 8x32 bit key schedule * @param {boolean} e true - decrypt * @returns CryptoOperationData next 8x8 bit key */ function keyMeshingCP(k, s, i, key, e) // { if ((i + 1) * this.blockSize % 1024 === 0) { // every 1024 octets // K[i+1] = decryptECB (K[i], C); k = decryptECB.call(this, k, C); // IV0[i+1] = encryptECB (K[i+1],IVn[i]) s.set(new Uint8Array(encryptECB.call(this, k, s))); // restore key schedule key.set(this.keySchedule(k, e)); } return k; } // /** * Null Key Meshing in according to rfc4357 2.3.1 * * @memberOf GostCipher * @method keyMeshing * @instance * @private * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key */ function noKeyMeshing(k) // { return k; } // /** * Algorithm name GOST 28147-NoPadding

* * No padding. * * @memberOf GostCipher * @method padding * @instance * @private * @param {Uint8Array} d array with source data * @returns {Uint8Array} result */ function noPad(d) // { return new Uint8Array(d); } // /** * Algorithm name GOST 28147-PKCS5Padding

* * PKCS#5 padding: 8-x remaining bytes are filled with the value of * 8-x. If there’s no incomplete block, one extra block filled with * value 8 is added * * @memberOf GostCipher * @method padding * @instance * @private * @param {Uint8Array} d array with source data * @returns {Uint8Array} result */ function pkcs5Pad(d) // { var n = d.byteLength, nb = this.blockSize, q = nb - n % nb, m = Math.ceil((n + 1) / nb) * nb, r = new Uint8Array(m); r.set(d); for (var i = n; i < m; i++) r[i] = q; return r; } // function pkcs5Unpad(d) // { var m = d.byteLength, nb = this.blockSize, q = d[m - 1], n = m - q; if (q > nb) throw DataError('Invalid padding'); var r = new Uint8Array(n); if (n > 0) r.set(new Uint8Array(d.buffer, 0, n)); return r; } // /** * Algorithm name GOST 28147-ZeroPadding

* * Zero padding: 8-x remaining bytes are filled with zero * * @memberOf GostCipher * @method padding * @instance * @private * @param {Uint8Array} d array with source data * @returns {Uint8Array} result */ function zeroPad(d) // { var n = d.byteLength, nb = this.blockSize, m = Math.ceil(n / nb) * nb, r = new Uint8Array(m); r.set(d); for (var i = n; i < m; i++) r[i] = 0; return r; } // /** * Algorithm name GOST 28147-BitPadding

* * Bit padding: P* = P || 1 || 000...0 If there’s no incomplete block, * one extra block filled with 1 || 000...0 * * @memberOf GostCipher * @method padding * @instance * @private * @param {Uint8Array} d array with source data * @returns {Uint8Array} result */ function bitPad(d) // { var n = d.byteLength, nb = this.blockSize, m = Math.ceil((n + 1) / nb) * nb, r = new Uint8Array(m); r.set(d); r[n] = 1; for (var i = n + 1; i < m; i++) r[i] = 0; return r; } // function bitUnpad(d) // { var m = d.byteLength, n = m; while (n > 1 && d[n - 1] === 0) n--; if (d[n - 1] !== 1) throw DataError('Invalid padding'); n--; var r = new Uint8Array(n); if (n > 0) r.set(new Uint8Array(d.buffer, 0, n)); return r; } // /** * Algorithm name GOST 28147-RandomPadding

* * Random padding: 8-x remaining bytes of the last block are set to * random. * * @memberOf GostCipher * @method padding * @instance * @private * @param {Uint8Array} d array with source data * @returns {Uint8Array} result */ function randomPad(d) // { var n = d.byteLength, nb = this.blockSize, q = nb - n % nb, m = Math.ceil(n / nb) * nb, r = new Uint8Array(m), e = new Uint8Array(r.buffer, n, q); r.set(d); randomSeed(e); return r; } // /** * GOST 28147-89 Encryption Algorithm

* * References {@link http://tools.ietf.org/html/rfc5830}

* * When keys and initialization vectors are converted to/from byte arrays, * little-endian byte order is assumed.

* * Normalized algorithm identifier common parameters: * *
    *
  • name Algorithm name 'GOST 28147' or 'GOST R 34.12'
  • *
  • version Algorithm version, number *
      *
    • 1989 Current version of standard
    • *
    • 2015 New draft version of standard
    • *
    *
  • *
  • length Block length *
      *
    • 64 64 bits length (default)
    • *
    • 128 128 bits length (only for version 2015)
    • *
    *
  • *
  • mode Algorithm mode, string *
      *
    • ES Encryption mode (default)
    • *
    • MAC "imitovstavka" (MAC) mode
    • *
    • KW Key wrapping mode
    • *
    *
  • *
  • sBox Paramset sBox for GOST 28147-89, string. Used only if version = 1989
  • *
* * Supported algorithms, modes and parameters: * *
    *
  • Encript/Decrypt mode (ES) *
      *
    • block Block mode, string. Default ECB
    • *
    • keyMeshing Key meshing mode, string. Default NO
    • *
    • padding Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others
    • *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • *
    *
  • *
  • Sign/Verify mode (MAC) *
      *
    • macLength Length of mac in bits (default - 32 bits)
    • *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • *
    *
  • *
  • Wrap/Unwrap key mode (KW) *
      *
    • keyWrapping Mode of keywrapping, string. Default NO - standard GOST key wrapping
    • *
    • ukm {@link CryptoOperationData} User key material. Default - random generated value
    • *
    *
  • *
* * Supported paramters values: * *
    *
  • Block modes (parameter 'block') *
      *
    • ECB "prostaya zamena" (ECB) mode (default)
    • *
    • CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
    • *
    • OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
    • *
    • CTR "gammirovanie" (counter) mode
    • *
    • CBC Cipher-Block-Chaining (CBC) mode
    • *
    *
  • *
  • Key meshing modes (parameter 'keyMeshing') *
      *
    • NO No key wrapping (default)
    • *
    • CP CryptoPor Key key meshing
    • *
    *
  • *
  • Padding modes (parameter 'padding') *
      *
    • NO No padding only for CFB, OFB and CTR modes
    • *
    • PKCS5 PKCS#5 padding mode
    • *
    • ZERO Zero bits padding mode
    • *
    • RANDOM Random bits padding mode
    • *
    • BIT One bit padding mode
    • *
    *
  • *
  • Wrapping key modes (parameter 'keyWrapping') *
      *
    • NO Ref. rfc4357 6.1 GOST 28147-89 Key wrapping
    • *
    • CP CryptoPro Key wrapping mode
    • *
    • SC SignalCom Key wrapping mode
    • *
    *
  • *
* * @class GostCipher * @param {AlgorithmIndentifier} algorithm WebCryptoAPI algorithm identifier */ function GostCipher(algorithm) // { // Check little endian support if (!littleEndian) throw new NotSupportedError('Big endian platform not supported'); algorithm = algorithm || {}; this.keySize = 32; this.blockLength = algorithm.length || 64; this.blockSize = this.blockLength >> 3; this.name = (algorithm.name || (algorithm.version === 1 ? 'RC2' : algorithm.version === 1989 ? 'GOST 28147' : 'GOST R 34.12')) + (algorithm.version > 4 ? '-' + ((algorithm.version || 1989) % 100) : '') + '-' + (this.blockLength === 64 ? '' : this.blockLength + '-') + ((algorithm.mode === 'MAC') ? 'MAC-' + (algorithm.macLength || this.blockLength >> 1) : (algorithm.mode === 'KW' || algorithm.keyWrapping) ? ((algorithm.keyWrapping || 'NO') !== 'NO' ? algorithm.keyWrapping : '') + 'KW' : (algorithm.block || 'ECB') + ((algorithm.block === 'CFB' || algorithm.block === 'OFB' || (algorithm.block === 'CTR' && algorithm.version === 2015)) && algorithm?.shiftBits !== this.blockLength ? '-' + algorithm.shiftBits : '') + (algorithm.padding ? '-' + (algorithm.padding || (algorithm.block === 'CTR' || algorithm.block === 'CFB' || algorithm.block === 'OFB' ? 'NO' : 'ZERO')) + 'PADDING' : '') + ((algorithm.keyMeshing || 'NO') !== 'NO' ? '-CPKEYMESHING' : '')) + (algorithm.procreator ? '/' + algorithm.procreator : '') + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); // Algorithm procreator this.procreator = algorithm.procreator; switch (algorithm.version || 1989) { case 1: this.process = processRC2; this.keySchedule = keyScheduleRC2; this.blockLength = 64; this.effectiveLength = algorithm.length || 32; this.keySize = 8 * Math.ceil(this.effectiveLength / 8); // Max 128 this.blockSize = this.blockLength >> 3; break; case 2015: this.version = 2015; if (this.blockLength === 64) { this.process = process15; this.keySchedule = keySchedule15; } else if (this.blockLength === 128) { this.process = process128; this.keySchedule = keySchedule128; } else throw new DataError('Invalid block length'); this.processMAC = processMAC15; break; case 1989: this.version = 1989; this.process = process89; this.processMAC = processMAC89; this.keySchedule = keySchedule89; if (this.blockLength !== 64) throw new DataError('Invalid block length'); break; default: throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported'); } switch (algorithm.mode || (algorithm.keyWrapping && 'KW') || 'ES') { case 'ES': switch (algorithm.block || 'ECB') { case 'ECB': this.encrypt = encryptECB; this.decrypt = decryptECB; break; case 'CTR': if (this.version === 1989) { this.encrypt = processCTR89; this.decrypt = processCTR89; } else { this.encrypt = processCTR15; this.decrypt = processCTR15; this.shiftBits = algorithm.shiftBits || this.blockLength; } break case 'CBC': this.encrypt = encryptCBC; this.decrypt = decryptCBC; break case 'CFB': this.encrypt = encryptCFB; this.decrypt = decryptCFB; this.shiftBits = algorithm.shiftBits || this.blockLength; break; case 'OFB': this.encrypt = processOFB; this.decrypt = processOFB; this.shiftBits = algorithm.shiftBits || this.blockLength; break; default: throw new NotSupportedError('Block mode ' + algorithm.block + ' not supported'); } switch (algorithm.keyMeshing) { case 'CP': this.keyMeshing = keyMeshingCP; break; default: this.keyMeshing = noKeyMeshing; } if (this.encrypt === encryptECB || this.encrypt === encryptCBC) { switch (algorithm.padding) { case 'PKCS5P': this.pad = pkcs5Pad; this.unpad = pkcs5Unpad; break; case 'RANDOM': this.pad = randomPad; this.unpad = noPad; break; case 'BIT': this.pad = bitPad; this.unpad = bitUnpad; break; default: this.pad = zeroPad; this.unpad = noPad; } } else { this.pad = noPad; this.unpad = noPad; } this.generateKey = generateKey; break; case 'MAC': this.sign = signMAC; this.verify = verifyMAC; this.generateKey = generateKey; this.macLength = algorithm.macLength || (this.blockLength >> 1); this.pad = noPad; this.unpad = noPad; this.keyMeshing = noKeyMeshing; break; case 'KW': this.pad = noPad; this.unpad = noPad; this.keyMeshing = noKeyMeshing; switch (algorithm.keyWrapping) { case 'CP': this.wrapKey = wrapKeyCP; this.unwrapKey = unwrapKeyCP; this.generateKey = generateKey; this.shiftBits = algorithm.shiftBits || this.blockLength; break; case 'SC': this.wrapKey = wrapKeySC; this.unwrapKey = unwrapKeySC; this.generateKey = generateWrappingKeySC; break; default: this.wrapKey = wrapKeyGOST; this.unwrapKey = unwrapKeyGOST; this.generateKey = generateKey; } break; case 'MASK': this.wrapKey = wrapKeyMask; this.unwrapKey = unwrapKeyMask; this.generateKey = generateKey; break; default: throw new NotSupportedError('Mode ' + algorithm.mode + ' not supported'); } // Define sBox parameter var sBox = algorithm.sBox, sBoxName; if (!sBox) sBox = this.version === 2015 ? sBoxes['E-Z'] : this.procreator === 'SC' ? sBoxes['E-SC'] : sBoxes['E-A']; else if (typeof sBox === 'string') { sBoxName = sBox.toUpperCase(); sBox = sBoxes[sBoxName]; if (!sBox) throw new SyntaxError('Unknown sBox name: ' + algorithm.sBox); } else if (!sBox.length || sBox.length !== sBoxes['E-Z'].length) throw new SyntaxError('Length of sBox must be ' + sBoxes['E-Z'].length); this.sBox = sBox; // Initial vector if (algorithm.iv) { this.iv = new Uint8Array(algorithm.iv); if (this.iv.byteLength !== this.blockSize && this.version === 1989) throw new SyntaxError('Length of iv must be ' + this.blockLength + ' bits'); else if (this.iv.byteLength !== this.blockSize >> 1 && this.encrypt === processCTR15) throw new SyntaxError('Length of iv must be ' + this.blockLength >> 1 + ' bits'); else if (this.iv.byteLength % this.blockSize !== 0 && this.encrypt !== processCTR15) throw new SyntaxError('Length of iv must be a multiple of ' + this.blockLength + ' bits'); } else this.iv = this.blockLength === 128 ? defaultIV128 : defaultIV; // User key material if (algorithm.ukm) { this.ukm = new Uint8Array(algorithm.ukm); if (this.ukm.byteLength * 8 !== this.blockLength) throw new SyntaxError('Length of ukm must be ' + this.blockLength + ' bits'); } } // export default GostCipher; ================================================ FILE: src/core/vendor/gost/gostCoding.mjs ================================================ /** * Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM * version 1.76 * 2014-2016, Rudolf Nickolaev. All rights reserved. * * Exported for CyberChef by mshwed [m@ttshwed.com] */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * THIS SOfTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES Of MERCHANTABILITY AND fITNESS fOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * fOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT Of SUBSTITUTE GOODS OR * SERVICES; LOSS Of USE, DATA, OR PROfITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY Of LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT Of THE USE * Of THIS SOfTWARE, EVEN If ADVISED Of THE POSSIBILITY OF SUCH DAMAGE. * */ import gostCrypto from './gostCrypto.mjs'; /** * The Coding interface provides string converting methods: Base64, Hex, * Int16, Chars, BER and PEM * @class GostCoding * */ // var root = {}; var DataError = Error; var CryptoOperationData = ArrayBuffer; var Date = Date; function buffer(d) { if (d instanceof CryptoOperationData) return d; else if (d && d?.buffer instanceof CryptoOperationData) return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; else throw new DataError('CryptoOperationData required'); } // function GostCoding() { } /** * BASE64 conversion * * @class GostCoding.Base64 */ var Base64 = {// /** * Base64.decode convert BASE64 string s to CryptoOperationData * * @memberOf GostCoding.Base64 * @param {String} s BASE64 encoded string value * @returns {CryptoOperationData} Binary decoded data */ decode: function (s) { s = s.replace(/[^A-Za-z0-9\+\/]/g, ''); var n = s.length, k = n * 3 + 1 >> 2, r = new Uint8Array(k); for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) { m4 = i & 3; var c = s.charCodeAt(i); c = c > 64 && c < 91 ? c - 65 : c > 96 && c < 123 ? c - 71 : c > 47 && c < 58 ? c + 4 : c === 43 ? 62 : c === 47 ? 63 : 0; u24 |= c << 18 - 6 * m4; if (m4 === 3 || n - i === 1) { for (m3 = 0; m3 < 3 && j < k; m3++, j++) { r[j] = u24 >>> (16 >>> m3 & 24) & 255; } u24 = 0; } } return r.buffer; }, /** * Base64.encode(data) convert CryptoOperationData data to BASE64 string * * @memberOf GostCoding.Base64 * @param {CryptoOperationData} data Bynary data for encoding * @returns {String} BASE64 encoded data */ encode: function (data) { var slen = 8, d = new Uint8Array(buffer(data)); var m3 = 2, s = ''; for (var n = d.length, u24 = 0, i = 0; i < n; i++) { m3 = i % 3; if (i > 0 && (i * 4 / 3) % (12 * slen) === 0) s += '\r\n'; u24 |= d[i] << (16 >>> m3 & 24); if (m3 === 2 || n - i === 1) { for (var j = 18; j >= 0; j -= 6) { var c = u24 >>> j & 63; c = c < 26 ? c + 65 : c < 52 ? c + 71 : c < 62 ? c - 4 : c === 62 ? 43 : c === 63 ? 47 : 65; s += String.fromCharCode(c); } u24 = 0; } } return s.substr(0, s.length - 2 + m3) + (m3 === 2 ? '' : m3 === 1 ? '=' : '=='); } // }; /** * BASE64 conversion * * @memberOf GostCoding * @insnance * @type GostCoding.Base64 */ GostCoding.prototype.Base64 = Base64; /** * Text string conversion
* Methods support charsets: ascii, win1251, utf8, utf16 (ucs2, unicode), utf32 (ucs4) * * @class GostCoding.Chars */ var Chars = (function () { // var _win1251_ = { 0x402: 0x80, 0x403: 0x81, 0x201A: 0x82, 0x453: 0x83, 0x201E: 0x84, 0x2026: 0x85, 0x2020: 0x86, 0x2021: 0x87, 0x20AC: 0x88, 0x2030: 0x89, 0x409: 0x8A, 0x2039: 0x8B, 0x40A: 0x8C, 0x40C: 0x8D, 0x40B: 0x8E, 0x40f: 0x8f, 0x452: 0x90, 0x2018: 0x91, 0x2019: 0x92, 0x201C: 0x93, 0x201D: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97, 0x2122: 0x99, 0x459: 0x9A, 0x203A: 0x9B, 0x45A: 0x9C, 0x45C: 0x9D, 0x45B: 0x9E, 0x45f: 0x9f, 0xA0: 0xA0, 0x40E: 0xA1, 0x45E: 0xA2, 0x408: 0xA3, 0xA4: 0xA4, 0x490: 0xA5, 0xA6: 0xA6, 0xA7: 0xA7, 0x401: 0xA8, 0xA9: 0xA9, 0x404: 0xAA, 0xAB: 0xAB, 0xAC: 0xAC, 0xAD: 0xAD, 0xAE: 0xAE, 0x407: 0xAf, 0xB0: 0xB0, 0xB1: 0xB1, 0x406: 0xB2, 0x456: 0xB3, 0x491: 0xB4, 0xB5: 0xB5, 0xB6: 0xB6, 0xB7: 0xB7, 0x451: 0xB8, 0x2116: 0xB9, 0x454: 0xBA, 0xBB: 0xBB, 0x458: 0xBC, 0x405: 0xBD, 0x455: 0xBE, 0x457: 0xBf }; var _win1251back_ = {}; for (var from in _win1251_) { var to = _win1251_[from]; _win1251back_[to] = from; } return { /** * Chars.decode(s, charset) convert string s with defined charset to CryptoOperationData * * @memberOf GostCoding.Chars * @param {string} s Javascript string * @param {string} charset Charset, default 'win1251' * @returns {CryptoOperationData} Decoded binary data */ decode: function (s, charset) { charset = (charset || 'win1251').toLowerCase().replace('-', ''); var r = []; for (var i = 0, j = s.length; i < j; i++) { var c = s.charCodeAt(i); if (charset === 'utf8') { if (c < 0x80) { r.push(c); } else if (c < 0x800) { r.push(0xc0 + (c >>> 6)); r.push(0x80 + (c & 63)); } else if (c < 0x10000) { r.push(0xe0 + (c >>> 12)); r.push(0x80 + (c >>> 6 & 63)); r.push(0x80 + (c & 63)); } else if (c < 0x200000) { r.push(0xf0 + (c >>> 18)); r.push(0x80 + (c >>> 12 & 63)); r.push(0x80 + (c >>> 6 & 63)); r.push(0x80 + (c & 63)); } else if (c < 0x4000000) { r.push(0xf8 + (c >>> 24)); r.push(0x80 + (c >>> 18 & 63)); r.push(0x80 + (c >>> 12 & 63)); r.push(0x80 + (c >>> 6 & 63)); r.push(0x80 + (c & 63)); } else { r.push(0xfc + (c >>> 30)); r.push(0x80 + (c >>> 24 & 63)); r.push(0x80 + (c >>> 18 & 63)); r.push(0x80 + (c >>> 12 & 63)); r.push(0x80 + (c >>> 6 & 63)); r.push(0x80 + (c & 63)); } } else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') { if (c < 0xD800 || (c >= 0xE000 && c <= 0x10000)) { r.push(c >>> 8); r.push(c & 0xff); } else if (c >= 0x10000 && c < 0x110000) { c -= 0x10000; var first = ((0xffc00 & c) >> 10) + 0xD800; var second = (0x3ff & c) + 0xDC00; r.push(first >>> 8); r.push(first & 0xff); r.push(second >>> 8); r.push(second & 0xff); } } else if (charset === 'utf32' || charset === 'ucs4') { r.push(c >>> 24 & 0xff); r.push(c >>> 16 & 0xff); r.push(c >>> 8 & 0xff); r.push(c & 0xff); } else if (charset === 'win1251') { if (c >= 0x80) { if (c >= 0x410 && c < 0x450) // А..Яа..я c -= 0x350; else c = _win1251_[c] || 0; } r.push(c); } else r.push(c & 0xff); } return new Uint8Array(r).buffer; }, /** * Chars.encode(data, charset) convert CryptoOperationData data to string with defined charset * * @memberOf GostCoding.Chars * @param {CryptoOperationData} data Binary data * @param {string} charset Charset, default win1251 * @returns {string} Encoded javascript string */ encode: function (data, charset) { charset = (charset || 'win1251').toLowerCase().replace('-', ''); var r = [], d = new Uint8Array(buffer(data)); for (var i = 0, n = d.length; i < n; i++) { var c = d[i]; if (charset === 'utf8') { c = c >= 0xfc && c < 0xfe && i + 5 < n ? // six bytes (c - 0xfc) * 1073741824 + (d[++i] - 0x80 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 : c >> 0xf8 && c < 0xfc && i + 4 < n ? // five bytes (c - 0xf8 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 : c >> 0xf0 && c < 0xf8 && i + 3 < n ? // four bytes (c - 0xf0 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 : c >= 0xe0 && c < 0xf0 && i + 2 < n ? // three bytes (c - 0xe0 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 : c >= 0xc0 && c < 0xe0 && i + 1 < n ? // two bytes (c - 0xc0 << 6) + d[++i] - 0x80 : c; // one byte } else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') { c = (c << 8) + d[++i]; if (c >= 0xD800 && c < 0xE000) { var first = (c - 0xD800) << 10; c = d[++i]; c = (c << 8) + d[++i]; var second = c - 0xDC00; c = first + second + 0x10000; } } else if (charset === 'utf32' || charset === 'ucs4') { c = (c << 8) + d[++i]; c = (c << 8) + d[++i]; c = (c << 8) + d[++i]; } else if (charset === 'win1251') { if (c >= 0x80) { if (c >= 0xC0 && c < 0x100) c += 0x350; // А..Яа..я else c = _win1251back_[c] || 0; } } r.push(String.fromCharCode(c)); } return r.join(''); } }; // })(); /** * Text string conversion * * @memberOf GostCoding * @insnance * @type GostCoding.Chars */ GostCoding.prototype.Chars = Chars; /** * HEX conversion * * @class GostCoding.Hex */ var Hex = {// /** * Hex.decode(s, endean) convert HEX string s to CryptoOperationData in endean mode * * @memberOf GostCoding.Hex * @param {string} s Hex encoded string * @param {boolean} endean Little or Big Endean, default Little * @returns {CryptoOperationData} Decoded binary data */ decode: function (s, endean) { s = s.replace(/[^A-Fa-f0-9]/g, ''); var n = Math.ceil(s.length / 2), r = new Uint8Array(n); s = (s.length % 2 > 0 ? '0' : '') + s; if (endean && ((typeof endean !== 'string') || (endean.toLowerCase().indexOf('little') < 0))) for (var i = 0; i < n; i++) r[i] = parseInt(s.substr((n - i - 1) * 2, 2), 16); else for (var i = 0; i < n; i++) r[i] = parseInt(s.substr(i * 2, 2), 16); return r.buffer; }, /** * Hex.encode(data, endean) convert CryptoOperationData data to HEX string in endean mode * * @memberOf GostCoding.Hex * @param {CryptoOperationData} data Binary data * @param {boolean} endean Little/Big Endean, default Little * @returns {string} Hex decoded string */ encode: function (data, endean) { var s = [], d = new Uint8Array(buffer(data)), n = d.length; if (endean && ((typeof endean !== 'string') || (endean.toLowerCase().indexOf('little') < 0))) for (var i = 0; i < n; i++) { var j = n - i - 1; s[j] = (j > 0 && j % 32 === 0 ? '\r\n' : '') + ('00' + d[i].toString(16)).slice(-2); } else for (var i = 0; i < n; i++) s[i] = (i > 0 && i % 32 === 0 ? '\r\n' : '') + ('00' + d[i].toString(16)).slice(-2); return s.join(''); } // }; /** * HEX conversion * @memberOf GostCoding * @insnance * @type GostCoding.Hex */ GostCoding.prototype.Hex = Hex; /** * String hex-encoded integer conversion * * @class GostCoding.Int16 */ var Int16 = {// /** * Int16.decode(s) convert hex big insteger s to CryptoOperationData * * @memberOf GostCoding.Int16 * @param {string} s Int16 string * @returns {CryptoOperationData} Decoded binary data */ decode: function (s) { s = (s || '').replace(/[^\-A-Fa-f0-9]/g, ''); if (s.length === 0) s = '0'; // Signature var neg = false; if (s.charAt(0) === '-') { neg = true; s = s.substring(1); } // Align 2 chars while (s.charAt(0) === '0' && s.length > 1) s = s.substring(1); s = (s.length % 2 > 0 ? '0' : '') + s; // Padding for singanuture // '800000' - 'ffffff' - for positive // '800001' - 'ffffff' - for negative if ((!neg && !/^[0-7]/.test(s)) || (neg && !/^[0-7]|8[0]+$/.test(s))) s = '00' + s; // Convert hex var n = s.length / 2, r = new Uint8Array(n), t = 0; for (var i = n - 1; i >= 0; --i) { var c = parseInt(s.substr(i * 2, 2), 16); if (neg && (c + t > 0)) { c = 256 - c - t; t = 1; } r[i] = c; } return r.buffer; }, /** * Int16.encode(data) convert CryptoOperationData data to big integer hex string * * @memberOf GostCoding.Int16 * @param {CryptoOperationData} data Binary data * @returns {string} Int16 encoded string */ encode: function (data) { var d = new Uint8Array(buffer(data)), n = d.length; if (d.length === 0) return '0x00'; var s = [], neg = d[0] > 0x7f, t = 0; for (var i = n - 1; i >= 0; --i) { var v = d[i]; if (neg && (v + t > 0)) { v = 256 - v - t; t = 1; } s[i] = ('00' + v.toString(16)).slice(-2); } s = s.join(''); while (s.charAt(0) === '0') s = s.substring(1); return (neg ? '-' : '') + '0x' + s; } // }; /** * String hex-encoded integer conversion * @memberOf GostCoding * @insnance * @type GostCoding.Int16 */ GostCoding.prototype.Int16 = Int16; /** * BER, DER, CER conversion * * @class GostCoding.BER */ var BER = (function () { // // Predefenition block function encodeBER(source, format, onlyContent) { // Correct primitive type var object = source.object; if (object === undefined) object = source; // Determinate tagClass var tagClass = source.tagClass = source.tagClass || 0; // Universial default // Determinate tagNumber. Use only for Universal class if (tagClass === 0) { var tagNumber = source.tagNumber; if (typeof tagNumber === 'undefined') { if (typeof object === 'string') { if (object === '') // NULL tagNumber = 0x05; else if (/^\-?0x[0-9a-fA-F]+$/.test(object)) // INTEGER tagNumber = 0x02; else if (/^(\d+\.)+\d+$/.test(object)) // OID tagNumber = 0x06; else if (/^[01]+$/.test(object)) // BIT STRING tagNumber = 0x03; else if (/^(true|false)$/.test(object)) // BOOLEAN tagNumber = 0x01; else if (/^[0-9a-fA-F]+$/.test(object)) // OCTET STRING tagNumber = 0x04; else tagNumber = 0x13; // Printable string (later can be changed to UTF8String) } else if (typeof object === 'number') { // INTEGER tagNumber = 0x02; } else if (typeof object === 'boolean') { // BOOLEAN tagNumber = 0x01; } else if (object instanceof Array) { // SEQUENCE tagNumber = 0x10; } else if (object instanceof Date) { // GeneralizedTime tagNumber = 0x18; } else if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) { tagNumber = 0x04; } else throw new DataError('Unrecognized type for ' + object); } } // Determinate constructed var tagConstructed = source.tagConstructed; if (typeof tagConstructed === 'undefined') tagConstructed = source.tagConstructed = object instanceof Array; // Create content var content; if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) { // Direct content = new Uint8Array(buffer(object)); if (tagNumber === 0x03) { // BITSTRING // Set unused bits var a = new Uint8Array(buffer(content)); content = new Uint8Array(a.length + 1); content[0] = 0; // No unused bits content.set(a, 1); } } else if (tagConstructed) { // Sub items coding if (object instanceof Array) { var bytelen = 0, ba = [], offset = 0; for (var i = 0, n = object.length; i < n; i++) { ba[i] = encodeBER(object[i], format); bytelen += ba[i].length; } if (tagNumber === 0x11) ba.sort(function (a, b) { // Sort order for SET components for (var i = 0, n = Math.min(a.length, b.length); i < n; i++) { var r = a[i] - b[i]; if (r !== 0) return r; } return a.length - b.length; }); if (format === 'CER') { // final for CER 00 00 ba[n] = new Uint8Array(2); bytelen += 2; } content = new Uint8Array(bytelen); for (var i = 0, n = ba.length; i < n; i++) { content.set(ba[i], offset); offset = offset + ba[i].length; } } else throw new DataError('Constracted block can\'t be primitive'); } else { switch (tagNumber) { // 0x00: // EOC case 0x01: // BOOLEAN content = new Uint8Array(1); content[0] = object ? 0xff : 0; break; case 0x02: // INTEGER case 0x0a: // ENUMIRATED content = Int16.decode( typeof object === 'number' ? object.toString(16) : object); break; case 0x03: // BIT STRING if (typeof object === 'string') { var unusedBits = 7 - (object.length + 7) % 8; var n = Math.ceil(object.length / 8); content = new Uint8Array(n + 1); content[0] = unusedBits; for (var i = 0; i < n; i++) { var c = 0; for (var j = 0; j < 8; j++) { var k = i * 8 + j; c = (c << 1) + (k < object.length ? (object.charAt(k) === '1' ? 1 : 0) : 0); } content[i + 1] = c; } } break; case 0x04: content = Hex.decode( typeof object === 'number' ? object.toString(16) : object); break; // case 0x05: // NULL case 0x06: // OBJECT IDENTIFIER var a = object.match(/\d+/g), r = []; for (var i = 1; i < a.length; i++) { var n = +a[i], r1 = []; if (i === 1) n = n + a[0] * 40; do { r1.push(n & 0x7F); n = n >>> 7; } while (n); // reverse order for (j = r1.length - 1; j >= 0; --j) r.push(r1[j] + (j === 0 ? 0x00 : 0x80)); } content = new Uint8Array(r); break; // case 0x07: // ObjectDescriptor // case 0x08: // EXTERNAL // case 0x09: // REAL // case 0x0A: // ENUMERATED // case 0x0B: // EMBEDDED PDV case 0x0C: // UTF8String content = Chars.decode(object, 'utf8'); break; // case 0x10: // SEQUENCE // case 0x11: // SET case 0x12: // NumericString case 0x16: // IA5String // ASCII case 0x13: // PrintableString // ASCII subset case 0x14: // TeletexString // aka T61String case 0x15: // VideotexString case 0x19: // GraphicString case 0x1A: // VisibleString // ASCII subset case 0x1B: // GeneralString // Reflect on character encoding for (var i = 0, n = object.length; i < n; i++) if (object.charCodeAt(i) > 255) tagNumber = 0x0C; if (tagNumber === 0x0C) content = Chars.decode(object, 'utf8'); else content = Chars.decode(object, 'ascii'); break; case 0x17: // UTCTime case 0x18: // GeneralizedTime var result = object.original; if (!result) { var date = new Date(object); date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); // to UTC var ms = tagNumber === 0x18 ? date.getMilliseconds().toString() : ''; // Milliseconds, remove trailing zeros while (ms.length > 0 && ms.charAt(ms.length - 1) === '0') ms = ms.substring(0, ms.length - 1); if (ms.length > 0) ms = '.' + ms; result = (tagNumber === 0x17 ? date.getYear().toString().slice(-2) : date.getFullYear().toString()) + ('00' + (date.getMonth() + 1)).slice(-2) + ('00' + date.getDate()).slice(-2) + ('00' + date.getHours()).slice(-2) + ('00' + date.getMinutes()).slice(-2) + ('00' + date.getSeconds()).slice(-2) + ms + 'Z'; } content = Chars.decode(result, 'ascii'); break; case 0x1C: // UniversalString content = Chars.decode(object, 'utf32'); break; case 0x1E: // BMPString content = Chars.decode(object, 'utf16'); break; } } if (!content) content = new Uint8Array(0); if (content instanceof CryptoOperationData) content = new Uint8Array(content); if (!tagConstructed && format === 'CER') { // Encoding CER-form for string types var k; switch (tagNumber) { case 0x03: // BIT_STRING k = 1; // ingnore unused bit for bit string case 0x04: // OCTET_STRING case 0x0C: // UTF8String case 0x12: // NumericString case 0x13: // PrintableString case 0x14: // TeletexString case 0x15: // VideotexString case 0x16: // IA5String case 0x19: // GraphicString case 0x1A: // VisibleString case 0x1B: // GeneralString case 0x1C: // UniversalString case 0x1E: // BMPString k = k || 0; // Split content on 1000 octet len parts var size = 1000; var bytelen = 0, ba = [], offset = 0; for (var i = k, n = content.length; i < n; i += size - k) { ba[i] = encodeBER({ object: new Unit8Array(content.buffer, i, Math.min(size - k, n - i)), tagNumber: tagNumber, tagClass: 0, tagConstructed: false }, format); bytelen += ba[i].length; } ba[n] = new Uint8Array(2); // final for CER 00 00 bytelen += 2; content = new Uint8Array(bytelen); for (var i = 0, n = ba.length; i < n; i++) { content.set(ba[i], offset); offset = offset + ba[i].length; } } } // Restore tagNumber for all classes if (tagClass === 0) source.tagNumber = tagNumber; else source.tagNumber = tagNumber = source.tagNumber || 0; source.content = content; if (onlyContent) return content; // Create header // tagNumber var ha = [], first = tagClass === 3 ? 0xC0 : tagClass === 2 ? 0x80 : tagClass === 1 ? 0x40 : 0x00; if (tagConstructed) first |= 0x20; if (tagNumber < 0x1F) { first |= tagNumber & 0x1F; ha.push(first); } else { first |= 0x1F; ha.push(first); var n = tagNumber, ha1 = []; do { ha1.push(n & 0x7F); n = n >>> 7; } while (n) // reverse order for (var j = ha1.length - 1; j >= 0; --j) ha.push(ha1[j] + (j === 0 ? 0x00 : 0x80)); } // Length if (tagConstructed && format === 'CER') { ha.push(0x80); } else { var len = content.length; if (len > 0x7F) { var l2 = len, ha2 = []; do { ha2.push(l2 & 0xff); l2 = l2 >>> 8; } while (l2); ha.push(ha2.length + 0x80); // reverse order for (var j = ha2.length - 1; j >= 0; --j) ha.push(ha2[j]); } else { // simple len ha.push(len); } } var header = source.header = new Uint8Array(ha); // Result - complete buffer var block = new Uint8Array(header.length + content.length); block.set(header, 0); block.set(content, header.length); return block; } function decodeBER(source, offset) { // start pos var pos = offset || 0, start = pos; var tagNumber, tagClass, tagConstructed, content, header, buffer, sub, len; if (source.object) { // Ready from source tagNumber = source.tagNumber; tagClass = source.tagClass; tagConstructed = source.tagConstructed; content = source.content; header = source.header; buffer = source.object instanceof CryptoOperationData ? new Uint8Array(source.object) : null; sub = source.object instanceof Array ? source.object : null; len = buffer && buffer.length || null; } else { // Decode header var d = source; // Read tag var buf = d[pos++]; tagNumber = buf & 0x1f; tagClass = buf >> 6; tagConstructed = (buf & 0x20) !== 0; if (tagNumber === 0x1f) { // long tag tagNumber = 0; do { if (tagNumber > 0x1fffffffffff80) throw new DataError('Convertor not supported tag number more then (2^53 - 1) at position ' + offset); buf = d[pos++]; tagNumber = (tagNumber << 7) + (buf & 0x7f); } while (buf & 0x80); } // Read len buf = d[pos++]; len = buf & 0x7f; if (len !== buf) { if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways throw new DataError('Length over 48 bits not supported at position ' + offset); if (len === 0) len = null; // undefined else { buf = 0; for (var i = 0; i < len; ++i) buf = (buf << 8) + d[pos++]; len = buf; } } start = pos; sub = null; if (tagConstructed) { // must have valid content sub = []; if (len !== null) { // definite length var end = start + len; while (pos < end) { var s = decodeBER(d, pos); sub.push(s); pos += s.header.length + s.content.length; } if (pos !== end) throw new DataError('Content size is not correct for container starting at offset ' + start); } else { // undefined length try { for (; ; ) { var s = decodeBER(d, pos); pos += s.header.length + s.content.length; if (s.tagClass === 0x00 && s.tagNumber === 0x00) break; sub.push(s); } len = pos - start; } catch (e) { throw new DataError('Exception ' + e + ' while decoding undefined length content at offset ' + start); } } } // Header and content header = new Uint8Array(d.buffer, offset, start - offset); content = new Uint8Array(d.buffer, start, len); buffer = content; } // Constructed types - check for string concationation if (sub !== null && tagClass === 0) { var k; switch (tagNumber) { case 0x03: // BIT_STRING k = 1; // ingnore unused bit for bit string case 0x04: // OCTET_STRING case 0x0C: // UTF8String case 0x12: // NumericString case 0x13: // PrintableString case 0x14: // TeletexString case 0x15: // VideotexString case 0x16: // IA5String case 0x19: // GraphicString case 0x1A: // VisibleString case 0x1B: // GeneralString case 0x1C: // UniversalString case 0x1E: // BMPString k = k || 0; // Concatination if (sub.length === 0) throw new DataError('No constructed encoding content of string type at offset ' + start); len = k; for (var i = 0, n = sub.length; i < n; i++) { var s = sub[i]; if (s.tagClass !== tagClass || s.tagNumber !== tagNumber || s.tagConstructed) throw new DataError('Invalid constructed encoding of string type at offset ' + start); len += s.content.length - k; } buffer = new Uint8Array(len); for (var i = 0, n = sub.length, j = k; i < n; i++) { var s = sub[i]; if (k > 0) buffer.set(s.content.subarray(1), j); else buffer.set(s.content, j); j += s.content.length - k; } tagConstructed = false; // follow not required sub = null; break; } } // Primitive types var object = ''; if (sub === null) { if (len === null) throw new DataError('Invalid tag with undefined length at offset ' + start); if (tagClass === 0) { switch (tagNumber) { case 0x01: // BOOLEAN object = buffer[0] !== 0; break; case 0x02: // INTEGER case 0x0a: // ENUMIRATED if (len > 6) { object = Int16.encode(buffer); } else { var v = buffer[0]; if (buffer[0] > 0x7f) v = v - 256; for (var i = 1; i < len; i++) v = v * 256 + buffer[i]; object = v; } break; case 0x03: // BIT_STRING if (len > 5) { // Content buffer object = new Uint8Array(buffer.subarray(1)).buffer; } else { // Max bit mask only for 32 bit var unusedBit = buffer[0], skip = unusedBit, s = []; for (var i = len - 1; i >= 1; --i) { var b = buffer[i]; for (var j = skip; j < 8; ++j) s.push((b >> j) & 1 ? '1' : '0'); skip = 0; } object = s.reverse().join(''); } break; case 0x04: // OCTET_STRING object = new Uint8Array(buffer).buffer; break; // case 0x05: // NULL case 0x06: // OBJECT_IDENTIFIER var s = '', n = 0, bits = 0; for (var i = 0; i < len; ++i) { var v = buffer[i]; n = (n << 7) + (v & 0x7F); bits += 7; if (!(v & 0x80)) { // finished if (s === '') { var m = n < 80 ? n < 40 ? 0 : 1 : 2; s = m + "." + (n - m * 40); } else s += "." + n.toString(); n = 0; bits = 0; } } if (bits > 0) throw new DataError('Incompleted OID at offset ' + start); object = s; break; //case 0x07: // ObjectDescriptor //case 0x08: // EXTERNAL //case 0x09: // REAL //case 0x0A: // ENUMERATED //case 0x0B: // EMBEDDED_PDV case 0x10: // SEQUENCE case 0x11: // SET object = []; break; case 0x0C: // UTF8String object = Chars.encode(buffer, 'utf8'); break; case 0x12: // NumericString case 0x13: // PrintableString case 0x14: // TeletexString case 0x15: // VideotexString case 0x16: // IA5String case 0x19: // GraphicString case 0x1A: // VisibleString case 0x1B: // GeneralString object = Chars.encode(buffer, 'ascii'); break; case 0x1C: // UniversalString object = Chars.encode(buffer, 'utf32'); break; case 0x1E: // BMPString object = Chars.encode(buffer, 'utf16'); break; case 0x17: // UTCTime case 0x18: // GeneralizedTime var shortYear = tagNumber === 0x17; var s = Chars.encode(buffer, 'ascii'), m = (shortYear ? /^(\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)?)?$/ : /^(\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); if (!m) throw new DataError('Unrecognized time format "' + s + '" at offset ' + start); if (shortYear) { // Where YY is greater than or equal to 50, the year SHALL be interpreted as 19YY; and // Where YY is less than 50, the year SHALL be interpreted as 20YY m[1] = +m[1]; m[1] += (m[1] < 50) ? 2000 : 1900; } var dt = new Date(m[1], +m[2] - 1, +m[3], +(m[4] || '0'), +(m[5] || '0'), +(m[6] || '0'), +(m[7] || '0')), tz = dt.getTimezoneOffset(); if (m[8] || tagNumber === 0x17) { if (m[8].toUpperCase() !== 'Z' && m[9]) { tz = tz + parseInt(m[9]); } dt.setMinutes(dt.getMinutes() - tz); } dt.original = s; object = dt; break; } } else // OCTET_STRING object = new Uint8Array(buffer).buffer; } else object = sub; // result return { tagConstructed: tagConstructed, tagClass: tagClass, tagNumber: tagNumber, header: header, content: content, object: object }; } return { /** * BER.decode(object, format) convert javascript object to ASN.1 format CryptoOperationData

* If object has members tagNumber, tagClass and tagConstructed * it is clear define encoding rules. Else method use defaul rules: *
    *
  • Empty string or null - NULL
  • *
  • String starts with '0x' and has 0-9 and a-f characters - INTEGER
  • *
  • String like d.d.d.d (d - set of digits) - OBJECT IDENTIFIER
  • *
  • String with characters 0 and 1 - BIT STRING
  • *
  • Strings 'true' or 'false' - BOOLEAN
  • *
  • String has only 0-9 and a-f characters - OCTET STRING
  • *
  • String has only characters with code 0-255 - PrintableString
  • *
  • Other strings - UTF8String
  • *
  • Number - INTEGER
  • *
  • Date - GeneralizedTime
  • *
  • Boolean - SEQUENCE
  • *
  • CryptoOperationData - OCTET STRING
  • *
* SEQUENCE or SET arrays recursively encoded for each item.
* OCTET STRING and BIT STRING can presents as array with one item. * It means encapsulates encoding for child element.
* * If CONTEXT or APPLICATION classes item presents as array with one * item we use EXPLICIT encoding for element, else IMPLICIT encoding.
* * @memberOf GostCoding.BER * @param {Object} object Object to encoding * @param {string} format Encoding rule: 'DER' or 'CER', default 'DER' * @param {boolean} onlyContent Encode content only, without header * @returns {CryptoOperationData} BER encoded data */ encode: function (object, format, onlyContent) { return encodeBER(object, format, onlyContent).buffer; }, /** * BER.encode(data) convert ASN.1 format CryptoOperationData data to javascript object

* * Conversion rules to javascript object: *
    *
  • BOOLEAN - Boolean object
  • *
  • INTEGER, ENUMIRATED - Integer object if len <= 6 (48 bits) else Int16 encoded string
  • *
  • 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
  • *
  • OCTET STRING - Hex encoded string or Array with one item in case of incapsulates encoding
  • *
  • OBJECT IDENTIFIER - String with object identifier
  • *
  • SEQUENCE, SET - Array of encoded items
  • *
  • UTF8String, NumericString, PrintableString, TeletexString, VideotexString, * IA5String, GraphicString, VisibleString, GeneralString, UniversalString, * BMPString - encoded String
  • *
  • UTCTime, GeneralizedTime - Date
  • *
* @memberOf GostCoding.BER * @param {(CryptoOperationData|GostCoding.BER)} data Binary data to decode * @returns {Object} Javascript object with result of decoding */ decode: function (data) { return decodeBER(data.object ? data : new Uint8Array(buffer(data)), 0); } }; //
})(); /** * BER, DER, CER conversion * @memberOf GostCoding * @insnance * @type GostCoding.BER */ GostCoding.prototype.BER = BER; /** * PEM conversion * @class GostCoding.PEM */ var PEM = {// /** * PEM.encode(data, name) encode CryptoOperationData to PEM format with name label * * @memberOf GostCoding.PEM * @param {(Object|CryptoOperationData)} data Java script object or BER-encoded binary data * @param {string} name Name of PEM object: 'certificate', 'private key' etc. * @returns {string} Encoded object */ encode: function (data, name) { return (name ? '-----BEGIN ' + name.toUpperCase() + '-----\r\n' : '') + Base64.encode(data instanceof CryptoOperationData ? data : BER.encode(data)) + (name ? '\r\n-----END ' + name.toUpperCase() + '-----' : ''); }, /** * PEM.decode(s, name, deep) decode PEM format s labeled name to CryptoOperationData or javascript object in according to deep parameter * * @memberOf GostCoding.PEM * @param {string} s PEM encoded string * @param {string} name Name of PEM object: 'certificate', 'private key' etc. * @param {boolean} deep If true method do BER-decoding, else only BASE64 decoding * @param {integer} index Index of decoded value * @returns {(Object|CryptoOperationData)} Decoded javascript object if deep=true, else CryptoOperationData for father BER decoding */ decode: function (s, name, deep, index) { // Try clear base64 var re1 = /([A-Za-z0-9\+\/\s\=]+)/g, valid = re1.exec(s); if (valid[1].length !== s.length) valid = false; if (!valid && name) { // Try with the name var re2 = new RegExp( '-----\\s?BEGIN ' + name.toUpperCase() + '-----([A-Za-z0-9\\+\\/\\s\\=]+)-----\\s?END ' + name.toUpperCase() + '-----', 'g'); valid = re2.exec(s); } if (!valid) { // Try with some name var re3 = new RegExp( '-----\\s?BEGIN [A-Z0-9\\s]+' + '-----([A-Za-z0-9\\+\\/\\s\\=]+)-----\\s?END ' + '[A-Z0-9\\s]+-----', 'g'); valid = re3.exec(s); } var r = valid && valid[1 + (index || 0)]; if (!r) throw new DataError('Not valid PEM format'); var out = Base64.decode(r); if (deep) out = BER.decode(out); return out; } // }; /** * PEM conversion * @memberOf GostCoding * @insnance * @type GostCoding.PEM */ GostCoding.prototype.PEM = PEM; if (gostCrypto) /** * Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM * * @memberOf gostCrypto * @type GostCoding */ gostCrypto.coding = new GostCoding(); export default GostCoding; ================================================ FILE: src/core/vendor/gost/gostCrypto.mjs ================================================ /** * Implementation Web Crypto interfaces for GOST algorithms * 1.76 * 2014-2016, Rudolf Nickolaev. All rights reserved. * * Exported for CyberChef by mshwed [m@ttshwed.com] */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ import GostRandom from './gostRandom.mjs'; import gostEngine from './gostEngine.mjs'; import crypto from 'crypto' /* * Algorithm normalization * */ // var root = {}; root.gostEngine = gostEngine; var rootCrypto = crypto var SyntaxError = Error, DataError = Error, NotSupportedError = Error, OperationError = Error, InvalidStateError = Error, InvalidAccessError = Error; // Normalize algorithm function normalize(algorithm, method) { if (typeof algorithm === 'string' || algorithm instanceof String) algorithm = {name: algorithm}; var name = algorithm.name; if (!name) throw new SyntaxError('Algorithm name not defined'); // Extract algorithm modes from name var modes = name.split('/'), modes = modes[0].split('-').concat(modes.slice(1)); // Normalize the name with default modes var na = {}; name = modes[0].replace(/[\.\s]/g, ''); modes = modes.slice(1); if (name.indexOf('28147') >= 0) { na = { name: 'GOST 28147', version: 1989, mode: (algorithm.mode || (// ES, MAC, KW (method === 'sign' || method === 'verify') ? 'MAC' : (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), length: algorithm.length || 64 }; } else if (name.indexOf('3412') >= 0) { na = { name: 'GOST R 34.12', version: 2015, mode: (algorithm.mode || (// ES, MAC, KW (method === 'sign' || method === 'verify') ? 'MAC' : (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), length: algorithm.length || 64 // 128 }; } else if (name.indexOf('3411') >= 0) { na = { name: 'GOST R 34.11', version: 2012, // 1994 mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF, CPKDF (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' : (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(), length: algorithm.length || 256 // 512 }; } else if (name.indexOf('3410') >= 0) { na = { name: 'GOST R 34.10', version: 2012, // 1994, 2001 mode: (algorithm.mode || (// SIGN, DH, MASK (method === 'deriveKey' || method === 'deriveBits') ? 'DH' : 'SIGN')).toUpperCase(), length: algorithm.length || 256 // 512 }; } else if (name.indexOf('SHA') >= 0) { na = { name: 'SHA', version: (algorithm.length || 160) === 160 ? 1 : 2, // 1, 2 mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' : (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(), length: algorithm.length || 160 }; } else if (name.indexOf('RC2') >= 0) { na = { name: 'RC2', version: 1, mode: (algorithm.mode || (// ES, MAC, KW (method === 'sign' || method === 'verify') ? 'MAC' : (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), length: algorithm.length || 32 // 1 - 1024 }; } else if (name.indexOf('PBKDF2') >= 0) { na = normalize(algorithm.hash, 'digest'); na.mode = 'PBKDF2'; } else if (name.indexOf('PFXKDF') >= 0) { na = normalize(algorithm.hash, 'digest'); na.mode = 'PFXKDF'; } else if (name.indexOf('CPKDF') >= 0) { na = normalize(algorithm.hash, 'digest'); na.mode = 'CPKDF'; } else if (name.indexOf('HMAC') >= 0) { na = normalize(algorithm.hash, 'digest'); na.mode = 'HMAC'; } else throw new NotSupportedError('Algorithm not supported'); // Compile modes modes.forEach(function (mode) { mode = mode.toUpperCase(); if (/^[0-9]+$/.test(mode)) { if ((['8', '16', '32'].indexOf(mode) >= 0) || (na.length === '128' && mode === '64')) { // Shift bits if (na.mode === 'ES') na.shiftBits = parseInt(mode); else if (na.mode === 'MAC') na.macLength = parseInt(mode); else throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); } else if (['89', '94', '01', '12', '15', '1989', '1994', '2001', '2012', '2015'].indexOf(mode) >= 0) { // GOST Year var version = parseInt(mode); version = version < 1900 ? (version < 80 ? 2000 + version : 1900 + version) : version; na.version = version; } else if (['1'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-1 na.version = 1; na.length = 160; } else if (['256', '384', '512'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-2 na.version = 2; na.length = parseInt(mode); } else if (['40', '128'].indexOf(mode) >= 0 && na.name === 'RC2') { // RC2 na.version = 1; na.length = parseInt(mode); // key size } else if (['64', '128', '256', '512'].indexOf(mode) >= 0) // block size na.length = parseInt(mode); else if (['1000', '2000'].indexOf(mode) >= 0) // Iterations na.iterations = parseInt(mode); // Named Paramsets } 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) { na.sBox = mode; } else if (['S-TEST', 'S-A', 'S-B', 'S-C', 'S-D', 'X-A', 'X-B', 'X-C'].indexOf(mode) >= 0) { na.namedParam = mode; } else if (['S-256-TEST', 'S-256-A', 'S-256-B', 'S-256-C', 'P-256', 'T-512-TEST', 'T-512-A', '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) { na.namedCurve = mode; } else if (['SC', 'CP', 'VN'].indexOf(mode) >= 0) { na.procreator = mode; // Encription GOST 28147 or GOST R 34.12 } else if (na.name === 'GOST 28147' || na.name === 'GOST R 34.12' || na.name === 'RC2') { if (['ES', 'MAC', 'KW', 'MASK'].indexOf(mode) >= 0) { na.mode = mode; } else if (['ECB', 'CFB', 'OFB', 'CTR', 'CBC'].indexOf(mode) >= 0) { na.mode = 'ES'; na.block = mode; } else if (['CPKW', 'NOKW', 'SCKW'].indexOf(mode) >= 0) { na.mode = 'KW'; na.keyWrapping = mode.replace('KW', ''); } else if (['ZEROPADDING', 'PKCS5PADDING', 'NOPADDING', 'RANDOMPADDING', 'BITPADDING'].indexOf(mode) >= 0) { na.padding = mode.replace('PADDING', ''); } else if (['NOKM', 'CPKM'].indexOf(mode) >= 0) { na.keyMeshing = mode.replace('KM', ''); } else throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); // Digesting GOST 34.11 } else if (na.name === 'GOST R 34.11' || na.name === 'SHA') { if (['HASH', 'KDF', 'HMAC', 'PBKDF2', 'PFXKDF', 'CPKDF'].indexOf(mode) >= 0) na.mode = mode; else throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); // Signing GOST 34.10 } else if (na.name === 'GOST R 34.10') { var hash = mode.replace(/[\.\s]/g, ''); if (hash.indexOf('GOST') >= 0 && hash.indexOf('3411') >= 0) na.hash = mode; else if (['SIGN', 'DH', 'MASK'].indexOf(mode)) na.mode = mode; else throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); } }); // Procreator na.procreator = algorithm.procreator || na.procreator || 'CP'; // Key size switch (na.name) { case 'GOST R 34.10': na.keySize = na.length / (na.version === 1994 ? 4 : 8); break; case 'GOST R 34.11': na.keySize = 32; break; case 'GOST 28147': case 'GOST R 34.12': na.keySize = 32; break; case 'RC2': na.keySize = Math.ceil(na.length / 8); break; case 'SHA': na.keySize = na.length / 8; break; } // Encrypt additional modes if (na.mode === 'ES') { if (algorithm.block) na.block = algorithm.block; // ECB, CFB, OFB, CTR, CBC if (na.block) na.block = na.block.toUpperCase(); if (algorithm.padding) na.padding = algorithm.padding; // NO, ZERO, PKCS5, RANDOM, BIT if (na.padding) na.padding = na.padding.toUpperCase(); if (algorithm.shiftBits) na.shiftBits = algorithm.shiftBits; // 8, 16, 32, 64 if (algorithm.keyMeshing) na.keyMeshing = algorithm.keyMeshing; // NO, CP if (na.keyMeshing) na.keyMeshing = na.keyMeshing.toUpperCase(); // Default values if (method !== 'importKey' && method !== 'generateKey') { na.block = na.block || 'ECB'; na.padding = na.padding || (na.block === 'CBC' || na.block === 'ECB' ? 'ZERO' : 'NO'); if (na.block === 'CFB' || na.block === 'OFB') na.shiftBits = na.shiftBits || na.length; na.keyMeshing = na.keyMeshing || 'NO'; } } if (na.mode === 'KW') { if (algorithm.keyWrapping) na.keyWrapping = algorithm.keyWrapping; // NO, CP, SC if (na.keyWrapping) na.keyWrapping = na.keyWrapping.toUpperCase(); if (method !== 'importKey' && method !== 'generateKey') na.keyWrapping = na.keyWrapping || 'NO'; } // Paramsets ['sBox', 'namedParam', 'namedCurve', 'curve', 'param', 'modulusLength'].forEach(function (name) { algorithm[name] && (na[name] = algorithm[name]); }); // Default values if (method !== 'importKey' && method !== 'generateKey') { if (na.name === 'GOST 28147') { na.sBox = na.sBox || (na.procreator === 'SC' ? 'E-SC' : 'E-A'); // 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC' } else if (na.name === 'GOST R 34.12' && na.length === 64) { na.sBox = 'E-Z'; } else if (na.name === 'GOST R 34.11' && na.version === 1994) { na.sBox = na.sBox || (na.procreator === 'SC' ? 'D-SC' : 'D-A'); // 'D-SC' } else if (na.name === 'GOST R 34.10' && na.version === 1994) { na.namedParam = na.namedParam || (na.mode === 'DH' ? 'X-A' : 'S-A'); // 'S-B', 'S-C', 'S-D', 'X-B', 'X-C' } else if (na.name === 'GOST R 34.10' && na.version === 2001) { na.namedCurve = na.namedCurve || (na.length === 256 ? 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' na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C' } else if (na.name === 'GOST R 34.10' && na.version === 2012) { na.namedCurve = na.namedCurve || (na.length === 256 ? 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' na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C' } } // Vectors switch (na.mode) { case 'DH': algorithm.ukm && (na.ukm = algorithm.ukm); algorithm['public'] && (na['public'] = algorithm['public']); break; case 'SIGN': case 'KW': algorithm.ukm && (na.ukm = algorithm.ukm); break; case 'ES': case 'MAC': algorithm.iv && (na.iv = algorithm.iv); break; case 'KDF': algorithm.label && (na.label = algorithm.label); algorithm.contex && (na.context = algorithm.contex); break; case 'PBKDF2': algorithm.salt && (na.salt = algorithm.salt); algorithm.iterations && (na.iterations = algorithm.iterations); algorithm.diversifier && (na.diversifier = algorithm.diversifier); break; case 'PFXKDF': algorithm.salt && (na.salt = algorithm.salt); algorithm.iterations && (na.iterations = algorithm.iterations); algorithm.diversifier && (na.diversifier = algorithm.diversifier); break; case 'CPKDF': algorithm.salt && (na.salt = algorithm.salt); algorithm.iterations && (na.iterations = algorithm.iterations); break; } // Verification method and modes if (method && ( ((na.mode !== 'ES' && na.mode !== 'SIGN' && na.mode !== 'MAC' && na.mode !== 'HMAC' && na.mode !== 'KW' && na.mode !== 'DH' && na.mode !== 'MASK') && (method === 'generateKey')) || ((na.mode !== 'ES') && (method === 'encrypt' || method === 'decrypt')) || ((na.mode !== 'SIGN' && na.mode !== 'MAC' && na.mode !== 'HMAC') && (method === 'sign' || method === 'verify')) || ((na.mode !== 'HASH') && (method === 'digest')) || ((na.mode !== 'KW' && na.mode !== 'MASK') && (method === 'wrapKey' || method === 'unwrapKey')) || ((na.mode !== 'DH' && na.mode !== 'PBKDF2' && na.mode !== 'PFXKDF' && na.mode !== 'CPKDF' && na.mode !== 'KDF') && (method === 'deriveKey' || method === 'deriveBits')))) throw new NotSupportedError('Algorithm mode ' + na.mode + ' not valid for method ' + method); // Normalize hash algorithm algorithm.hash && (na.hash = algorithm.hash); if (na.hash) { if ((typeof na.hash === 'string' || na.hash instanceof String) && na.procreator) na.hash = na.hash + '/' + na.procreator; na.hash = normalize(na.hash, 'digest'); } // Algorithm object identirifer algorithm.id && (na.id = algorithm.id); return na; } // Check for possibility use native crypto.subtle function checkNative(algorithm) { if (!rootCrypto || !rootCrypto.subtle || !algorithm) return false; // Prepare name var name = (typeof algorithm === 'string' || algorithm instanceof String) ? name = algorithm : algorithm.name; if (!name) return false; name = name.toUpperCase(); // Digest algorithm for key derivation if ((name.indexOf('KDF') >= 0 || name.indexOf('HMAC') >= 0) && algorithm.hash) return checkNative(algorithm.hash); // True if no supported names return name.indexOf('GOST') === -1 && name.indexOf('SHA-1') === -1 && name.indexOf('RC2') === -1 && name.indexOf('?DES') === -1; } // /* * Key conversion methods * */ // // Check key parameter function checkKey(key, method) { if (!key.algorithm) throw new SyntaxError('Key algorithm not defined'); if (!key.algorithm.name) throw new SyntaxError('Key algorithm name not defined'); var name = key.algorithm.name, gostCipher = name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2', gostDigest = name === 'GOST R 34.11' || name === 'SHA', gostSign = name === 'GOST R 34.10'; if (!gostCipher && !gostSign && !gostDigest) throw new NotSupportedError('Key algorithm ' + name + ' is unsupproted'); if (!key.type) throw new SyntaxError('Key type not defined'); if (((gostCipher || gostDigest) && key.type !== 'secret') || (gostSign && !(key.type === 'public' || key.type === 'private'))) throw new DataError('Key type ' + key.type + ' is not valid for algorithm ' + name); if (!key.usages || !key.usages.indexOf) throw new SyntaxError('Key usages not defined'); for (var i = 0, n = key.usages.length; i < n; i++) { var md = key.usages[i]; if (((md === 'encrypt' || md === 'decrypt') && key.type !== 'secret') || (md === 'sign' && key.type === 'public') || (md === 'verify' && key.type === 'private')) throw new InvalidStateError('Key type ' + key.type + ' is not valid for ' + md); } if (method) if (key.usages.indexOf(method) === -1) throw new InvalidAccessError('Key usages is not contain method ' + method); if (!key.buffer) throw new SyntaxError('Key buffer is not defined'); var size = key.buffer.byteLength * 8, keySize = 8 * key.algorithm.keySize; if ((key.type === 'secret' && size !== (keySize || 256) && (key.usages.indexOf('encrypt') >= 0 || key.usages.indexOf('decrypt') >= 0)) || (key.type === 'private' && !(size === 256 || size === 512)) || (key.type === 'public' && !(size === 512 || size === 1024))) throw new SyntaxError('Key buffer has wrong size ' + size + ' bit'); } // Extract key and enrich cipher algorithm function extractKey(method, algorithm, key) { checkKey(key, method); if (algorithm) { var params; switch (algorithm.mode) { case 'ES': params = ['sBox', 'keyMeshing', 'padding', 'block']; break; case 'SIGN': params = ['namedCurve', 'namedParam', 'sBox', 'curve', 'param', 'modulusLength']; break; case 'MAC': params = ['sBox']; break; case 'KW': params = ['keyWrapping', 'ukm']; break; case 'DH': params = ['namedCurve', 'namedParam', 'sBox', 'ukm', 'curve', 'param', 'modulusLength']; break; case 'KDF': params = ['context', 'label']; break; case 'PBKDF2': params = ['sBox', 'iterations', 'salt']; break; case 'PFXKDF': params = ['sBox', 'iterations', 'salt', 'diversifier']; break; case 'CPKDF': params = ['sBox', 'salt']; break; } if (params) params.forEach(function (name) { key.algorithm[name] && (algorithm[name] = key.algorithm[name]); }); } return key.buffer; } // Make key definition function convertKey(algorithm, extractable, keyUsages, keyData, keyType) { var key = { type: keyType || (algorithm.name === 'GOST R 34.10' ? 'private' : 'secret'), extractable: extractable || 'false', algorithm: algorithm, usages: keyUsages || [], buffer: keyData }; checkKey(key); return key; } function convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, publicBuffer, privateBuffer) { if (!keyUsages || !keyUsages.indexOf) throw new SyntaxError('Key usages not defined'); var publicUsages = keyUsages.filter(function (value) { return value !== 'sign'; }); var privateUsages = keyUsages.filter(function (value) { return value !== 'verify'; }); return { publicKey: convertKey(publicAlgorithm, extractable, publicUsages, publicBuffer, 'public'), privateKey: convertKey(privateAlgorithm, extractable, privateUsages, privateBuffer, 'private') }; } // Swap bytes in buffer function swapBytes(src) { if (src instanceof CryptoOperationData) src = new Uint8Array(src); var dst = new Uint8Array(src.length); for (var i = 0, n = src.length; i < n; i++) dst[n - i - 1] = src[i]; return dst.buffer; } // /** * Promise stub object (not fulfill specification, only for internal use) * Class not defined if Promise class already defined in root context

* * The Promise object is used for deferred and asynchronous computations. A Promise is in one of the three states: *
    *
  • pending: initial state, not fulfilled or rejected.
  • *
  • fulfilled: successful operation
  • *
  • rejected: failed operation.
  • *
* Another term describing the state is settled: the Promise is either fulfilled or rejected, but not pending.

* @class Promise * @global * @param {function} executor Function object with two arguments resolve and reject. * The first argument fulfills the promise, the second argument rejects it. * We can call these functions, once our operation is completed. */ // if (!Promise) { root.Promise = (function () { function mswrap(value) { if (value && value.oncomplete === null && value.onerror === null) { return new Promise(function (resolve, reject) { value.oncomplete = function () { resolve(value.result); }; value.onerror = function () { reject(new OperationError(value.toString())); }; }); } else return value; } function Promise(executor) { var state = 'pending', result, resolveQueue = [], rejectQueue = []; function call(callback) { try { callback(); } catch (e) { } } try { executor(function (value) { if (state === 'pending') { state = 'fulfilled'; result = value; resolveQueue.forEach(call); } }, function (reason) { if (state === 'pending') { state = 'rejected'; result = reason; rejectQueue.forEach(call); } }); } catch (error) { if (state === 'pending') { state = 'rejected'; result = error; rejectQueue.forEach(call); } } /** * The then() method returns a Promise. It takes two arguments, both are * callback functions for the success and failure cases of the Promise. * * @method then * @memberOf Promise * @instance * @param {function} onFulfilled A Function called when the Promise is fulfilled. This function has one argument, the fulfillment value. * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason. * @returns {Promise} */ this.then = function (onFulfilled, onRejected) { return new Promise(function (resolve, reject) { function asyncOnFulfilled() { var value; try { value = onFulfilled ? onFulfilled(result) : result; } catch (error) { reject(error); return; } value = mswrap(value); if (value && value?.then?.call) { value.then(resolve, reject); } else { resolve(value); } } function asyncOnRejected() { var reason; try { reason = onRejected ? onRejected(result) : result; } catch (error) { reject(error); return; } reason = mswrap(reason); if (reason && reason?.then?.call) { reason.then(resolve, reject); } else { reject(reason); } } if (state === 'fulfilled') { asyncOnFulfilled(); } else if (state === 'rejected') { asyncOnRejected(); } else { resolveQueue.push(asyncOnFulfilled); rejectQueue.push(asyncOnRejected); } }); }; /** * The catch() method returns a Promise and deals with rejected cases only. * It behaves the same as calling Promise.prototype.then(undefined, onRejected). * * @method catch * @memberOf Promise * @instance * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason. * @returns {Promise} */ this['catch'] = function (onRejected) { return this.then(undefined, onRejected); }; } /** * The Promise.all(iterable) method returns a promise that resolves when all * of the promises in the iterable argument have resolved.

* * The result is passed as an array of values from all the promises. * If something passed in the iterable array is not a promise, it's converted to * one by Promise.resolve. If any of the passed in promises rejects, the * all Promise immediately rejects with the value of the promise that rejected, * discarding all the other promises whether or not they have resolved. * * @method all * @memberOf Promise * @static * @param {KeyUsages} promises Array with promises. * @returns {Promise} */ Promise.all = function (promises) { return new Promise(function (resolve, reject) { var result = [], count = 0; function asyncResolve(k) { count++; return function (data) { result[k] = data; count--; if (count === 0) resolve(result); }; } function asyncReject(reason) { if (count > 0) reject(reason); count = 0; } for (var i = 0, n = promises.length; i < n; i++) { var data = promises[i]; if (data?.then?.call) data.then(asyncResolve(i), asyncReject); else result[i] = data; } if (count === 0) resolve(result); }); }; return Promise; })(); } //
/* * Worker executor * */ // var baseUrl = '', nameSuffix = ''; // Try to define from DOM model if (typeof document !== 'undefined') { (function () { var regs = /^(.*)gostCrypto(.*)\.js$/i; var list = document.querySelectorAll('script'); for (var i = 0, n = list.length; i < n; i++) { var value = list[i].getAttribute('src'); var test = regs.exec(value); if (test) { baseUrl = test[1]; nameSuffix = test[2]; } } })(); } // Local importScripts procedure for include dependens function importScripts() { for (var i = 0, n = arguments.length; i < n; i++) { var name = arguments[i].split('.'), src = baseUrl + name[0] + nameSuffix + '.' + name[1]; var el = document.querySelector('script[src="' + src + '"]'); if (!el) { el = document.createElement('script'); el.setAttribute('src', src); document.head.appendChild(el); } } } // Create Worker var worker = false, tasks = [], sequence = 0; // Worker will create only for first child process and // Gost implementation libraries not yet loaded if (!root.importScripts && !root.gostEngine) { try { worker = new Worker(baseUrl + 'gostEngine' + nameSuffix + '.js'); // Result of opertion worker.onmessage = function (event) { // Find task var id = event.data.id; for (var i = 0, n = tasks.length; i < n; i++) if (tasks[i].id === id) break; if (i < n) { var task = tasks[i]; tasks.splice(i, 1); // Reject if error or resolve with result if (event.data.error) task.reject(new OperationError(event.data.error)); else task.resolve(event.data.result); } }; // Worker error - reject all waiting tasks worker.onerror = function (event) { for (var i = 0, n = tasks.length; i < n; i++) tasks[i].reject(event.error); tasks = []; }; } catch (e) { // Worker is't supported worker = false; } } if (!root.importScripts) { // This procedure emulate load dependents as in Worker root.importScripts = importScripts; } if (!worker) { // Import main module // Reason: we are already in worker process or Worker interface is not // yet supported root.gostEngine || require('./gostEngine'); } // Executor for any method function execute(algorithm, method, args) { return new Promise(function (resolve, reject) { try { if (worker) { var id = ++sequence; tasks.push({ id: id, resolve: resolve, reject: reject }); worker.postMessage({ id: id, algorithm: algorithm, method: method, args: args }); } else { if (root.gostEngine) resolve(root.gostEngine.execute(algorithm, method, args)); else reject(new OperationError('Module gostEngine not found')); } } catch (error) { reject(error); } }); } // Self resolver function call(callback) { try { callback(); } catch (e) { } } // /* * WebCrypto common class references * */ // /** * The Algorithm object is a dictionary object [WebIDL] which is used to * specify an algorithm and any additional parameters required to fully * specify the desired operation.
*
 *  dictionary Algorithm {
 *      DOMString name;
 *  };
 * 
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary} * @class Algorithm * @param {DOMString} name The name of the registered algorithm to use. */ /** * AlgorithmIdentifier - Algorithm or DOMString name of algorithm
*
 *  typedef (Algorithm or DOMString) AlgorithmIdentifier;
 * 
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary} * @class AlgorithmIdentifier */ /** * The KeyAlgorithm interface represents information about the contents of a * given Key object. *
 *  interface KeyAlgorithm {
 *      readonly attribute DOMString name
 *  };
 * 
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-algorithm-interface} * @class KeyAlgorithm * @param {DOMString} name The name of the algorithm used to generate the Key */ /** * The type of a key. The recognized key type values are "public", "private" * and "secret". Opaque keying material, including that used for symmetric * algorithms, is represented by "secret", while keys used as part of asymmetric * algorithms composed of public/private keypairs will be either "public" or "private". *
 *  typedef DOMString KeyType;
 * 
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} * @class KeyType */ /** * Sequence of operation type that may be performed using a key. The recognized * key usage values are "encrypt", "decrypt", "sign", "verify", "deriveKey", * "deriveBits", "wrapKey" and "unwrapKey". *
 *  typedef DOMString[] KeyUsages;
 * 
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} * @class KeyUsages */ /** * The Key object represents an opaque reference to keying material that is * managed by the user agent.
* This specification provides a uniform interface for many different kinds of * keying material managed by the user agent. This may include keys that have * been generated by the user agent, derived from other keys by the user agent, * imported to the user agent through user actions or using this API, * pre-provisioned within software or hardware to which the user agent has * access or made available to the user agent in other ways. The term key refers * broadly to any keying material including actual keys for cryptographic * operations and secret values obtained within key derivation or exchange operations.
* The Key object is not required to directly interface with the underlying key * storage mechanism, and may instead simply be a reference for the user agent * to understand how to obtain the keying material when needed, eg. when performing * a cryptographic operation. *
 *  interface Key {
 *      readonly attribute KeyType type;
 *      readonly attribute boolean extractable;
 *      readonly attribute KeyAlgorithm algorithm;
 *      readonly attribute KeyUsages usages;
 *  };
 * 
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} * @class Key * @param {KeyType} type The type of a key. The recognized key type values are "public", "private" and "secret". * @param {boolean} extractable Whether or not the raw keying material may be exported by the application. * @param {KeyAlgorithm} algorithm The Algorithm used to generate the key. * @param {KeyUsages} usages Key usage array: type of operation that may be performed using a key. */ /** * The KeyPair interface represents an asymmetric key pair that is comprised of both public and private keys. *
 *  interface KeyPair {
 *      readonly attribute Key publicKey;
 *      readonly attribute Key privateKey;
 *  };
 * 
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#keypair} * @class KeyPair * @param {Key} privateKey Private key * @param {Key} publicKey Public key */ /** * Specifies a serialization format for a key. The recognized key format values are: *
    *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • *
  • 'jwk' - The key is represented as JSON according to the JSON Web Key format.
  • *
*
 *  typedef DOMString KeyFormat;
 *  
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} * @class KeyFormat */ /** * Binary data *
 *  typedef (ArrayBuffer or ArrayBufferView) CryptoOperationData;
 *  
* @class CryptoOperationData */ var CryptoOperationData = ArrayBuffer; /** * DER-encoded ArrayBuffer or PEM-encoded DOMString constains ASN.1 object
*
 *  typedef (ArrayBuffer or DOMString) FormatedData;
 * 
* @class FormatedData */ //
/** * The gostCrypto provide general purpose cryptographic functionality for * GOST standards including a cryptographically strong pseudo-random number * generator seeded with truly random values. * * @namespace gostCrypto */ var gostCrypto = {}; /** * The SubtleCrypto class provides low-level cryptographic primitives and algorithms. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#subtlecrypto-interface} * * @class SubtleCrypto */ // function SubtleCrypto() { } /** * The encrypt method returns a new Promise object that will encrypt data * using the specified algorithm identifier with the supplied Key. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-encrypt}

* * Supported algorithm names: *
    *
  • GOST 28147-ECB "prostaya zamena" (ECB) mode (default)
  • *
  • GOST 28147-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • *
  • GOST 28147-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • *
  • GOST 28147-CTR "gammirovanie" (counter) mode
  • *
  • GOST 28147-CBC Cipher-Block-Chaining (CBC) mode
  • *
  • GOST R 34.12-ECB "prostaya zamena" (ECB) mode (default)
  • *
  • GOST R 34.12-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • *
  • GOST R 34.12-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • *
  • GOST R 34.12-CTR "gammirovanie" (counter) mode
  • *
  • GOST R 34.12-CBC Cipher-Block-Chaining (CBC) mode
  • *
* For more information see {@link GostCipher} * * @memberOf SubtleCrypto * @method encrypt * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} key Key object * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.encrypt = function (algorithm, key, data) // { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.encrypt(algorithm, key, data); algorithm = normalize(algorithm, 'encrypt'); return execute(algorithm, 'encrypt', [extractKey('encrypt', algorithm, key), data]); }); }; // /** * The decrypt method returns a new Promise object that will decrypt data * using the specified algorithm identifier with the supplied Key. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-decrypt}

* * Supported algorithm names: *
    *
  • GOST 28147-ECB "prostaya zamena" (ECB) mode (default)
  • *
  • GOST 28147-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • *
  • GOST 28147-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • *
  • GOST 28147-CTR "gammirovanie" (counter) mode
  • *
  • GOST 28147-CBC Cipher-Block-Chaining (CBC) mode
  • *
  • GOST R 34.12-ECB "prostaya zamena" (ECB) mode (default)
  • *
  • GOST R 34.12-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • *
  • GOST R 34.12-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • *
  • GOST R 34.12-CTR "gammirovanie" (counter) mode
  • *
  • GOST R 34.12-CBC Cipher-Block-Chaining (CBC) mode
  • *
* For additional modes see {@link GostCipher} * * @memberOf SubtleCrypto * @method decrypt * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} key Key object * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.decrypt = function (algorithm, key, data) // { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.decrypt(algorithm, key, data); algorithm = normalize(algorithm, 'decrypt'); return execute(algorithm, 'decrypt', [extractKey('decrypt', algorithm, key), data]); }); }; // /** * The sign method returns a new Promise object that will sign data using * the specified algorithm identifier with the supplied Key. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-sign}

* * Supported algorithm names: *
    *
  • GOST R 34.10-94 GOST Signature
  • *
  • GOST R 34.10-94/GOST R 34.11-94 GOST Signature with Hash
  • *
  • GOST R 34.10 ECGOST Signature
  • *
  • GOST R 34.10/GOST R 34.11-94 ECGOST Signature with Old-Style Hash
  • *
  • GOST R 34.10/GOST R 34.11 ECGOST Signature with Streebog Hash
  • *
  • GOST 28147-MAC MAC base on GOST 28147
  • *
  • GOST R 34.12-MAC MAC base on GOST R 43.12
  • *
  • GOST R 34.11-HMAC HMAC base on GOST 34.11
  • *
  • SHA-HMAC HMAC base on SHA
  • *
* For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher} * * @memberOf SubtleCrypto * @method sign * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} key Key object * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.sign = function (algorithm, key, data) // { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.sign(algorithm, key, data); algorithm = normalize(algorithm, 'sign'); var value = execute(algorithm, 'sign', [extractKey('sign', algorithm, key), data]).then(function (data) { if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') { data = gostCrypto.asn1.GostSignature.encode(data); } return data; }); return value; }); }; // /** * The verify method returns a new Promise object that will verify data * using the specified algorithm identifier with the supplied Key. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-verify}

* * Supported algorithm names: *
    *
  • GOST R 34.10-94 GOST Signature
  • *
  • GOST R 34.10-94/GOST R 34.11-94 GOST Signature with Hash
  • *
  • GOST R 34.10 ECGOST Signature
  • *
  • GOST R 34.10/GOST R 34.11-94 ECGOST Signature with Old-Style Hash
  • *
  • GOST R 34.10/GOST R 34.11 ECGOST Signature with Streebog Hash
  • *
  • GOST 28147-MAC MAC base on GOST 28147
  • *
  • GOST R 34.12-MAC MAC base on GOST R 34.12
  • *
  • GOST R 34.11-HMAC HMAC base on GOST 34.11
  • *
  • SHA-HMAC HMAC base on SHA
  • *
* For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher} * * @memberOf SubtleCrypto * @method verify * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} key Key object * @param {CryptoOperationData} signature Signature data * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with boolean value of verification result */ SubtleCrypto.prototype.verify = function (algorithm, key, signature, data) // { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.verify(algorithm, key, signature, data); algorithm = normalize(algorithm, 'verify'); if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') { var obj = gostCrypto.asn1.GostSignature.decode(signature); signature = {r: obj.r, s: obj.s}; } return execute(algorithm, 'verify', [extractKey('verify', algorithm, key), signature, data]); }); }; // /** * The digest method returns a new Promise object that will digest data * using the specified algorithm identifier. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-digest}

* * Supported algorithm names: *
    *
  • GOST R 34.11-94 Old-Style GOST Hash
  • *
  • GOST R 34.11 GOST Streebog Hash
  • *
  • SHA SHA Hash
  • *
* For additional modes see {@link GostDigest} * * @memberOf SubtleCrypto * @method digest * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.digest = function (algorithm, data) // { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.digest(algorithm, data); algorithm = normalize(algorithm, 'digest'); return execute(algorithm, 'digest', [data]); }); }; // /** * The generateKey method returns a new Promise object that will key(s) using * the specified algorithm identifier. Key can be used in according with * KeyUsages sequence. The recognized key usage values are "encrypt", "decrypt", * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey". * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey}

* * Supported algorithm names: *
    *
  • GOST R 34.10 ECGOST Key Pairs
  • *
  • GOST 28147 Key for encryption GOST 28147 modes
  • *
  • GOST 28147-KW Key for wrapping GOST 28147 modes
  • *
  • GOST R 34.12 Key for encryption GOST R 34.12 modes
  • *
  • GOST R 34.12-KW Key for wrapping GOST R 34.12 modes
  • *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • *
* For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
* Note: Generation key for GOST R 34.10-94 not supported. * * @memberOf SubtleCrypto * @method generateKey * @instance * @param {AlgorithmIdentifier} algorithm Key algorithm identifier * @param {boolean} extractable Whether or not the raw keying material may be exported by the application * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key * @returns {Promise} Promise that resolves with {@link Key} or {@link KeyPair} in according to key algorithm */ SubtleCrypto.prototype.generateKey = function (algorithm, extractable, keyUsages) // { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.generateKey(algorithm, extractable, keyUsages); var privateAlgorithm = algorithm.privateKey, publicAlgorithm = algorithm.publicKey; algorithm = normalize(algorithm, 'generateKey'); if (privateAlgorithm) privateAlgorithm = normalize(privateAlgorithm, 'generateKey'); else privateAlgorithm = algorithm; if (publicAlgorithm) publicAlgorithm = normalize(publicAlgorithm, 'generateKey'); else publicAlgorithm = algorithm; return execute(algorithm, 'generateKey', []).then(function (data) { if (data.publicKey && data.privateKey) return convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, data.publicKey, data.privateKey); else return convertKey(algorithm, extractable, keyUsages, data); }); }); }; // /** * The deriveKey method returns a new Promise object that will key(s) using * the specified algorithm identifier. Key can be used in according with * KeyUsage sequence. The recognized key usage values are "encrypt", "decrypt", * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey". * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveKey}

* * Supported algorithm names: *
    *
  • GOST R 34.10-DH ECDH Key Agreement mode
  • *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • *
  • GOST R 34.11-PBKDF2 Password Based Key for Derivation Algorithm
  • *
  • GOST R 34.11-PFXKDF PFX Key for Derivation Algorithm
  • *
  • GOST R 34.11-CPKDF Password Based Key for CryptoPro Derivation Algorithm
  • *
  • SHA-PBKDF2 Password Based Key for Derivation Algorithm
  • *
  • SHA-PFXKDF PFX Key for Derivation Algorithm
  • *
* For additional modes see {@link GostSign} and {@link GostDigest} * * @memberOf SubtleCrypto * @method deriveKey * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} baseKey Derivation key object * @param {AlgorithmIdentifier} derivedKeyType Derived key algorithm identifier * @param {boolean} extractable Whether or not the raw keying material may be exported by the application * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key * @returns {Promise} Promise that resolves with {@link Key} */ SubtleCrypto.prototype.deriveKey = function (algorithm, baseKey, derivedKeyType, extractable, keyUsages) // { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.deriveKey(algorithm, baseKey, derivedKeyType, extractable, keyUsages); algorithm = normalize(algorithm, 'deriveKey'); derivedKeyType = normalize(derivedKeyType, 'generateKey'); algorithm.keySize = derivedKeyType.keySize; if (algorithm['public']) { algorithm['public'].algorithm = normalize(algorithm['public'].algorithm); algorithm['public'] = extractKey('deriveKey', algorithm, algorithm['public']); } return execute(algorithm, 'deriveKey', [extractKey('deriveKey', algorithm, baseKey)]).then(function (data) { return convertKey(derivedKeyType, extractable, keyUsages, data); }); }); }; // /** * The deriveBits method returns length bits on baseKey using the * specified algorithm identifier. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveBits}

* * Supported algorithm names: *
    *
  • GOST R 34.10-DH ECDH Key Agreement mode
  • *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • *
  • GOST R 34.11-PBKDF2 Password Based Key for Derivation Algorithm
  • *
  • GOST R 34.11-PFXKDF PFX Key for Derivation Algorithm
  • *
  • GOST R 34.11-CPKDF Password Based Key for CryptoPro Derivation Algorithm
  • *
  • SHA-PBKDF2 Password Based Key for Derivation Algorithm
  • *
  • SHA-PFXKDF PFX Key for Derivation Algorithm
  • *
* For additional modes see {@link GostSign} and {@link GostDigest} * * @memberOf SubtleCrypto * @method deriveBits * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} baseKey Derivation key object * @param {number} length Length bits * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.deriveBits = function (algorithm, baseKey, length) // { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.deriveBits(algorithm, baseKey, length); algorithm = normalize(algorithm, 'deriveBits'); if (algorithm['public']) algorithm['public'] = extractKey('deriveBits', algorithm, algorithm['public']); return execute(algorithm, 'deriveBits', [extractKey('deriveBits', algorithm, baseKey), length]); }); }; // /** * The importKey method returns a new Promise object that will key(s) using * the specified algorithm identifier. Key can be used in according with * KeyUsage sequence. The recognized key usage values are "encrypt", "decrypt", * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey".

* Parameter keyData contains data in defined format. * The suppored key format values are: *
    *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • *
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-importKey}

* * Supported algorithm names: *
    *
  • GOST R 34.10-94 GOST Private and Public keys
  • *
  • GOST R 34.10 ECGOST Private and Public keys
  • *
  • GOST 28147 Key for encryption GOST 28147 modes
  • *
  • GOST 28147-KW Key for key wrapping GOST 28147 modes
  • *
  • GOST R 34.12 Key for encryption GOST 34.12 modes
  • *
  • GOST R 34.12-KW Key for key wrapping GOST 34.12 modes
  • *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • *
* For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
* * @memberOf SubtleCrypto * @method importKey * @instance * @param {KeyFormat} format Key format Format specifies a serialization format for a key * @param {CryptoOperationData} keyData * @param {AlgorithmIdentifier} algorithm Key algorithm identifier * @param {boolean} extractable Whether or not the raw keying material may be exported by the application * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key * @returns {Promise} Promise that resolves with {@link Key} */ SubtleCrypto.prototype.importKey = function (format, keyData, algorithm, extractable, keyUsages) // { var type; return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.importKey(format, keyData, algorithm, extractable, keyUsages); if (format === 'raw') { algorithm = normalize(algorithm, 'importKey'); if (keyUsages && keyUsages.indexOf) { var name = algorithm.name.toUpperCase().replace(/[\.\s]/g, ''); if (name.indexOf('3410') >= 0 && keyUsages.indexOf('sign') >= 0) type = 'private'; else if (name.indexOf('3410') >= 0 && keyUsages.indexOf('verify') >= 0) type = 'public'; } return keyData; } else { var key; if (format === 'pkcs8') key = gostCrypto.asn1.GostPrivateKeyInfo.decode(keyData).object; else if (format === 'spki') key = gostCrypto.asn1.GostSubjectPublicKeyInfo.decode(keyData).object; else throw new NotSupportedError('Key format not supported'); algorithm = normalize(key.algorithm, 'importKey'); type = key.type; if (extractable !== false) extractable = extractable || key.extractable; if (keyUsages) { for (var i = 0; i < keyUsages.length; i++) { if (key.usages.indexOf(keyUsages[i]) < 0) throw DataError('Key usage not valid for this key'); } } else keyUsages = key.usages; var data = key.buffer, keySize = algorithm.keySize, dataLen = data.byteLength; if (type === 'public' || keySize === dataLen) return data; else { // Remove private key masks if (dataLen % keySize > 0) throw new DataError('Invalid key size'); algorithm.mode = 'MASK'; algorithm.procreator = 'VN'; var chain = []; for (var i = keySize; i < dataLen; i += keySize) { chain.push((function (mask) { return function (data) { return execute(algorithm, 'unwrapKey', [mask, data]).then(function (data) { var next = chain.pop(); if (next) return next(data); else { delete algorithm.mode; return data; } }); }; })(new Uint8Array(data, i, keySize))); } return chain.pop()(new Uint8Array(data, 0, keySize)); } } }).then(function (data) { return convertKey(algorithm, extractable, keyUsages, data, type); }); }; // /** * The exportKey method returns a new Promise object that will key data in * defined format.

* The suppored key format values are: *
    *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • *
* WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-exportKey}

* * Supported algorithm names: *
    *
  • GOST R 34.10-94 GOST Private and Public keys
  • *
  • GOST R 34.10 ECGOST Private and Public keys
  • *
  • GOST 28147 Key for encryption GOST 28147 modes
  • *
  • GOST 28147-KW Key for key wrapping GOST 28147 modes
  • *
  • GOST R 34.12 Key for encryption GOST R 34.12 modes
  • *
  • GOST R 34.12-KW Key for key wrapping GOST R 34.12 modes
  • *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • *
  • GOST R 34.11-PBKDF2 Import Password for Key for Derivation Algorithm
  • *
  • GOST R 34.11-PFXKDF Import PFX Key for Derivation Algorithm
  • *
  • GOST R 34.11-CPKDF Import Password Key for CryptoPro Derivation Algorithm
  • *
  • SHA-PBKDF2 Import Password for Key for Derivation Algorithm
  • *
  • SHA-PFXKDF Import PFX Key for Derivation Algorithm
  • *
* For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
* * @memberOf SubtleCrypto * @method exportKey * @instance * @param {KeyFormat} format Format specifies a serialization format for a key * @param {Key} key Key object * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.exportKey = function (format, key) // { return new Promise(call).then(function () { if (key && checkNative(key.algorithm)) return rootCrypto.subtle.exportKey(format, key); if (!key.extractable) throw new InvalidAccessError('Key not extractable'); var raw = extractKey(null, null, key); if (format === 'raw') return raw; else if (format === 'pkcs8' && key?.algorithm?.id) { if (key.algorithm.procreator === 'VN') { // Add masks for ViPNet var algorithm = key.algorithm, mask; algorithm.mode = 'MASK'; return execute(algorithm, 'generateKey').then(function (data) { mask = data; return execute(algorithm, 'wrapKey', [mask, key.buffer]); }).then(function (data) { delete algorithm.mode; var d = new Uint8Array(data.byteLength + mask.byteLength); d.set(new Uint8Array(data, 0, data.byteLength)); d.set(new Uint8Array(mask, 0, mask.byteLength), data.byteLength); var buffer = d.buffer; buffer.enclosed = true; return gostCrypto.asn1.GostPrivateKeyInfo.encode({ algorithm: algorithm, buffer: buffer }); }); } else return gostCrypto.asn1.GostPrivateKeyInfo.encode(key); } else if (format === 'spki' && key?.algorithm?.id) return gostCrypto.asn1.GostSubjectPublicKeyInfo.encode(key); else throw new NotSupportedError('Key format not supported'); }); }; // /** * The wrapKey method returns a new Promise object that will wrapped key(s). * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-wrapKey}

* * Supported algorithm names: *
    *
  • GOST 28147-KW Key Wrapping GOST 28147 modes
  • *
  • GOST R 34.12-KW Key Wrapping GOST R 34.12 modes
  • *
  • GOST 28147-MASK Key Mask GOST 28147 modes
  • *
  • GOST R 34.12-MASK Key Mask GOST R 34.12 modes
  • *
  • GOST R 34.10-MASK Key Mask GOST R 34.10 modes
  • *
* For additional modes see {@link GostCipher}
* * @memberOf SubtleCrypto * @method wrapKey * @instance * @param {KeyFormat} format Format specifies a serialization format for a key. Now suppored only 'raw' key format. * @param {Key} key Key object * @param {Key} wrappingKey Wrapping key object * @param {AlgorithmIdentifier} wrapAlgorithm Algorithm identifier * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.wrapKey = function (format, key, wrappingKey, wrapAlgorithm) // { return new Promise(call).then(function () { if (checkNative(wrapAlgorithm)) return rootCrypto.subtle.wrapKey(format, key, wrappingKey, wrapAlgorithm); wrapAlgorithm = normalize(wrapAlgorithm, 'wrapKey'); var keyData = extractKey(null, null, key); if (wrapAlgorithm.procreator === 'SC' && key.type === 'private') keyData = swapBytes(keyData); return execute(wrapAlgorithm, 'wrapKey', [extractKey('wrapKey', wrapAlgorithm, wrappingKey), keyData]).then(function (data) { if (format === 'raw') return data; else throw new NotSupportedError('Key format not supported'); }); }); }; // /** * The unwrapKey method returns a new Promise object that will unwrapped key(s). * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-unwrapKey}

* * Supported algorithm names: *
    *
  • GOST 28147-KW Key Wrapping GOST 28147 modes
  • *
  • GOST R 34.12-KW Key Wrapping GOST R 34.12 modes
  • *
  • GOST 28147-MASK Key Mask GOST 28147 modes
  • *
  • GOST R 34.12-MASK Key Mask GOST R 34.12 modes
  • *
  • GOST R 34.10-MASK Key Mask GOST R 34.10 modes
  • *
* For additional modes see {@link GostCipher}
* * @memberOf SubtleCrypto * @method unwrapKey * @instance * @param {KeyFormat} format Format specifies a serialization format for a key. Now suppored only 'raw' key format. * @param {CryptoOperationData} wrappedKey Wrapped key data * @param {Key} unwrappingKey Unwrapping key object * @param {AlgorithmIdentifier} unwrapAlgorithm Algorithm identifier * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm Key algorithm identifier * @param {boolean} extractable Whether or not the raw keying material may be exported by the application * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key * @returns {Promise} Promise that resolves with {@link Key} */ SubtleCrypto.prototype.unwrapKey = function (format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) // { return new Promise(call).then(function () { if (checkNative(unwrapAlgorithm)) return rootCrypto.subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages); unwrapAlgorithm = normalize(unwrapAlgorithm, 'unwrapKey'); unwrappedKeyAlgorithm = normalize(unwrappedKeyAlgorithm, 'importKey'); if (format !== 'raw') throw new NotSupportedError('Key format not supported'); return execute(unwrapAlgorithm, 'unwrapKey', [extractKey('unwrapKey', unwrapAlgorithm, unwrappingKey), wrappedKey]).then(function (data) { var type; if (unwrappedKeyAlgorithm && unwrappedKeyAlgorithm.name) { var name = unwrappedKeyAlgorithm.name.toUpperCase().replace(/[\.\s]/g, ''); if (name.indexOf('3410') >= 0 && keyUsages.indexOf('sign') >= 0) type = 'private'; else if (name.indexOf('3410') >= 0 && keyUsages.indexOf('verify') >= 0) type = 'public'; } if (unwrapAlgorithm.procreator === 'SC' && type === 'private') data = swapBytes(data); return convertKey(unwrappedKeyAlgorithm, extractable, keyUsages, data, type); }); }); }; // /** * The subtle attribute provides an instance of the SubtleCrypto * interface which provides low-level cryptographic primitives and * algorithms. * * @memberOf gostCrypto * @type SubtleCrypto */ gostCrypto.subtle = new SubtleCrypto(); /** * The getRandomValues method generates cryptographically random values. * * First try to use Web Crypto random genereator. Next make random * bytes based on standart Math.random mixed with time and mouse pointer * * @memberOf gostCrypto * @param {(CryptoOperationData)} array Destination buffer for random data */ gostCrypto.getRandomValues = function (array) // { // Execute randomizer GostRandom = GostRandom || root.GostRandom; var randomSource = GostRandom ? new GostRandom() : rootCrypto; if (randomSource.getRandomValues) randomSource.getRandomValues(array); else throw new NotSupportedError('Random generator not found'); }; // //
export default gostCrypto; ================================================ FILE: src/core/vendor/gost/gostDigest.mjs ================================================ /** * GOST R 34.11-94 / GOST R 34.11-12 implementation * 1.76 * 2014-2016, Rudolf Nickolaev. All rights reserved. * * Exported for CyberChef by mshwed [m@ttshwed.com] */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Converted to JavaScript from source https://www.streebog.net/ * Copyright (c) 2013, Alexey Degtyarev. * All rights reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ import GostRandom from './gostRandom.mjs'; import GostCipher from './gostCipher.mjs'; import crypto from 'crypto'; /* * GOST R 34.11 * Common methods * */ // var root = {}; var rootCrypto = crypto var DataError = Error, NotSupportedError = Error; // Copy len values from s[sOfs] to d[dOfs] function arraycopy(s, sOfs, d, dOfs, len) { for (var i = 0; i < len; i++) d[dOfs + i] = s[sOfs + i]; } // Swap bytes in buffer function swap(s) { var src = new Uint8Array(s), dst = new Uint8Array(src.length); for (var i = 0, n = src.length; i < n; i++) dst[n - i - 1] = src[i]; return dst.buffer; } // Convert BASE64 string to Uint8Array // for decompression of constants and precalc values function b64decode(s) { // s = s.replace(/[^A-Za-z0-9\+\/]/g, ''); var n = s.length, k = n * 3 + 1 >> 2, r = new Uint8Array(k); for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) { m4 = i & 3; var c = s.charCodeAt(i); c = c > 64 && c < 91 ? c - 65 : c > 96 && c < 123 ? c - 71 : c > 47 && c < 58 ? c + 4 : c === 43 ? 62 : c === 47 ? 63 : 0; u24 |= c << 18 - 6 * m4; if (m4 === 3 || n - i === 1) { for (m3 = 0; m3 < 3 && j < k; m3++, j++) { r[j] = u24 >>> (16 >>> m3 & 24) & 255; } u24 = 0; } } return r.buffer; } // Random seed function getSeed(length) { GostRandom = GostRandom || root.GostRandom; var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; if (randomSource.getRandomValues) { var d = new Uint8Array(Math.ceil(length / 8)); randomSource.getRandomValues(d); return d; } else throw new NotSupportedError('Random generator not found'); } // Check buffer function buffer(d) { if (d instanceof ArrayBuffer) return d; else if (d && d?.buffer instanceof ArrayBuffer) return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; else throw new DataError('ArrayBuffer or ArrayBufferView required'); } // /** * Algorithm name GOST R 34.11 or GOST R 34.11-12

* * http://tools.ietf.org/html/rfc6986 * * The digest method returns digest data in according to GOST R 4311-2012.
* Size of digest also defines in algorithm name. *
    *
  • GOST R 34.11-256-12 - 256 bits digest
  • *
  • GOST R 34.11-512-12 - 512 bits digest
  • *
* * @memberOf GostDigest * @method digest * @instance * @param {(ArrayBuffer|TypedArray)} data Data * @returns {ArrayBuffer} Digest of data */ var digest2012 = (function () // { // Constants var buffer0 = new Int32Array(16); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var buffer512 = new Int32Array(16); // [512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; buffer512[0] = 512; // Constant C var C = (function (s) { var h = new Int32Array(b64decode(s)), r = new Array(12); for (var i = 0; i < 12; i++) r[i] = new Int32Array(h.buffer, i * 64, 16); return r; })( 'B0Wm8lllgN0jTXTMNnR2BRXTYKQIKkKiAWlnkpHgfEv8xIV1jbhOcRbQRS5DdmovH3xlwIEvy+vp2soe2lsIsbebsSFwBHnmVs3L1xui3VXKpwrbwmG1XFiZ1hJrF7WaMQG1Fg9e1WGYKyMKcur+89e1cA9GneNPGi+dqYq1o2+yCroK9ZYemTHbeoZD9LbCCdtiYDc6ycGxnjWQ5A/i03t7KbEUderyix+cUl9e8QY1hD1qKPw5Cscvzius3HT1LtHjhLy+DCLxN+iToepTNL4DUpMzE7fYddYD7YIs16k/NV5orRxynX08XDN+hY5I3eRxXaDhSPnSZhXos98f71f+bHz9WBdg9WPqqX6iVnoWGicjtwD/36P1OiVHF82/vf8PgNc1njVKEIYWHxwVf2MjqWwMQT+amUdHraxr6ktufWRGekBo+jVPkDZyxXG/tsa+wmYf8gq0t5oct6b6z8aO8Jq0mn8YbKRCUfnEZi3AOTB6O8Okb9nTOh2urk+uk9QUOk1WhojzSjyiTEUXNQQFSiiDaUcGNyyCLcWrkgnJk3oZMz5H08mHv+bHxp45VAkkv/6GrFHsxaruFg7H9B7nAr/UDX+k' + '2ahRWTXCrDYvxKXRK43RaZAGm5LLK4n0msTbTTtEtIke3jaccfi3TkFBbgwCqucDp8mTTUJbH5vbWiODUURhcmAqH8uS3DgOVJwHppqKK3uxzrLbC0QKgIQJDeC3Vdk8JEKJJRs6fTreXxbs2JpMlJsiMRZUWo837ZxFmPvHtHTDtjsV0fqYNvRSdjswbB56SzNprwJn558DYTMbiuH/H9t4iv8c50GJ8/PkskjlKjhSbwWApt6+qxst84HNpMprXdhvwEpZot6Ybkd9Hc2678q5SOrvcR2KeWaEFCGAASBhB6vru2v62JT+WmPNxgIw+4nI79CezXsg1xvxSpK8SJkbstnVF/T6UijhiKqkHeeGzJEYne+AXZufITDUEiD4dx3fvDI8pM16sUkEsIAT0roxFvFn5443'); // Precalc Ax var Ax = (function (s) { return new Int32Array(b64decode(s)); })( '5vh+XFtxH9Alg3eACST6FshJ4H6FLqSoW0aGoY8GwWoLMumi13tBbqvaN6RngVxm9heWqBpoZnb13AtwY5GVS0hi84235kvx/1ximmi9hcXLgn2m/NdXlWbTba9pufCJNWyfdEg9g7B8vOyxI4yZoTanAqwxxHCNnrao0C+839aLGfpR5bOuN5zPtUCKEn0LvAx4tQggj1rlM+OEIojs7c7Cx9N3wV/S7HgXtlBdD165TMLAgzaHHYwgXbTLCwStdjyFWyigiS9YjRt59v8yVz/s9p5DEZM+D8DTn4A6GMnuAQom9fOtgxDv6PRBGXmmXc2hDH3pOhBKG+4dEkjpLFO/8tshhHM5tPUMz6aiPQlftLyc2EeYzeiKLYsHHFb5f3dxaVp1apzF8C5xoLoevKZj+atCFeZyLrGeIt5fu3gNuc4PJZS6FIJSDmOXZk2ELwMeagII6phcfyFEob5r8Ho3yxzRY2Lbg+COK0sxHGTPcEebq5YOMoVrqYa53ucetUeMh3r1bOm4/kKIX2HW/RvdAVaWYjjIYiFXkj74qS78l/9CEUR2+J19NQhWRSzrTJDJsOCnElYjCFAt+8sBbC16A/qnpkhF' + '9G6LOL/GxKu9vvj91HfeujqsTOvIB5t58JyxBeiHnQwn+moQrIpYy4lg58FAHQzqGm+BHko1aSiQxPsHc9GW/0NQGi9gnQqf96UW4MY/N5Yc5KazuNqSUhMkdSw44IqbpahkczvsFU8r8SRXVUmzP9dm2xVEDcXHp9F5455Ct5La3xUaYZl/04agNF7AJxQjONVRe22pOaRlGPB3EEADtAJ5HZClrqLdiNJniZxKXQqTD2bfCihlwk7p1CBFCbCLMlU4kWaFKSpBKQe/xTOoQrJ+K2JUTcZzbFMERWKV4Ada9AbpU1GQih8vO2vBI2Fvw3sJ3FJV5cY5Z9Ezsf5oRCmIOcfw5xHiQJuH9xlk+aLpOK3D20sHGQwLTkf5w+v0VTTVdtNriENGEKBa64sC2CDDzfWCMvJRbeGEDb7Cseeg6N4GsPodCHuFS1QNNDM7QuKaZ7zKW3/YpgiKxDfdDsY7s6nZQ+2BIXFNvV5lo7FnYe3nte6haSQx98jVc6v21R/GheGjZxpeBjzUBBDJLSg6uY8ssEACj+vAbLLy95AX1k8Rb6HTPOBzWfGpnuSqeE7WjHTNwAZuKhnVxztC2ocStBYccEXD' + 'NxWC5O2TIW2s45BBSTn2/H7F8SGGIjt8wLCUBCusFvv510U3mlJ+v3N8Py6jtoFoM+e42brSeMqpoyo0wi/+u+SBY8z+370NjllAJG6lpnBRxu9LhCrR5CK60GUnnFCM2RSIwhhgjO4xnqVJH3zaF9OU4SgTTJxgCUv0MnLV47Ob9hKlpKrXkcy72kPSb/0PNN4fPJRq0lBPW1RomV7ha9+fr2/qj3eUJkjqWHDdCSu/x+Vtcdl8Z93msv9PIdVJPCdrRjroYAORdntPr4bHH2ihPng11LmgtowRXwMMn9QUHdLJFlggAZg9j33dUySsZKpwP8wXUlTCyYmUjgK0Jj5edtafRsLeUHRvA1h9gARF2z2CknLx5WBYSgKbVgvz+65Ypz/83GKhWl5ObK1M6EupblXOH7jMCPl0eq6CslPBAhRM9/tHG58EKJjz6442BosnrfLv+3rtypf+jApevneOBRP099jPMCwlAcMri/eNkt38F1xVTfhlxX9GBS9f6vMwG6Ky9CSqaLfsu9YNhpmPDzUBBHVMAAAAAAAAAADxLjFNNNDM7HEFIr4GGCO1rygNmTDABcGX/VziXWk8ZRmkHMYzzJoV' + 'lYRBcvjHnrjcVDK3k3aEqZQ2wTokkM9YgCsT8zLI71nEQq45fO1PXPoc2O/jq42C8uWslU0pP9Fq2CPokHobfU0iSfg88EO2A8ud2Hn58z3eLS8nNtgmdCpDpB+JHuLfb5iZnRtsEzrUrUbNPfQ2+rs131AmmCXAlk/cqoE+bYXrQbBTfuWlxAVAunWLFghHpBrkO+e7RK/juMQp0GcXl4GZk7vun765rpqN0eyXVCHzVyzdkX5uMWOT19rir/jOR6IgEjfcUzijI0PeyQPuNXn8VsSompHmAbKASNxXUeASlvVk5Lfbe3X3GINRWXoS222VUr3OLjMenbsjHXQwj1INcpP90yLZ4gpEYQwwRnf+7uLStOrUJcow/e4ggAZ1YerKSkcBWhPnSv4UhyZOMCzIg7J78RmlFmTPWbP2gtyoEap8HnivWx1WJvtkjcOytz6RF99bzjTQX3zwarVvXf0lfwrNEycYV03I5nbFKp4HOaflLriqmlSGVT4PPNmjVv9IrqqSe36+dWUlrY4th30ObPn/28hBOx7MoxRQyplpE74w6YPoQK1REAmVbqccsbW2ui20NU5Eab3KTiWgBRWvUoHKD3Hh' + 'dEWYy40OK/JZP5sxKqhjt++zim4ppPxja2qjoEwtSp09lesO5r8x46KRw5YVVL/VGBacju+by/URXWi8nU4oRrqHXxj6z3Qg0e38uLbiPr2wBzby8eNkroTZKc5libb+cLei9tpPclUOclPXXG1JKQTyOj1XQVmnCoBp6gssEI5J0HPFa7EaEYqrehk55P/XzQlaCw44rO/J+2A2WXn1SJK95pfWfzQix4kz4QUUvGHhwdm5dcm1StImYWDPG82AmkSS7Xj9hnGzzKsqiBqXk3LOv2Z/4dCI1tRbXZhalCfIEagFjD9V3mX1tDGWtQYZ90+WsdZwbkOFnR6Ly0PTNlqrioXM+j2E+ce/mcKV/P2iH9Wh3ktjD82z73Y7i0VtgD9Z+Hz3w4WyfHO+XzGRPJjjrGYzsEghv2FnTCa4+BgP+8mVxMEwyKqghiAQdhqYYFfzQiEBFqr2PHYMBlTMNS3bRcxmfZBCvPRalkvUA4Jo6KDD7zxvPae9ktJp/3O8KQriAgHtIoe33jTN6IWBj9kB7qfdYQWb1vonMhmgNVPVbxrodMzOyeoxJFwug/VUcDRVXaB75JnOJtKsVue+9/0WGFelBU44' + 'ag59pFJ0NtFb2Go4HN6f8sr3dWIxdwwysJqu2eJ5yNBd7xCRxgZ02xEQRqJRXlBFI1Ns5HKYAvzFDLz39bY8+nOhaIfNFx8DfSlBr9nyjb0/Xj60Wk87nYTu/jYbZ3FAPbjj0+cHYnEaOij58g/SSH68fHW0nnYndOXyk8frVlwY3PWeT0eLpAxu9E+prctSxpmBLZjax2B4iwbcbkadDvxl+Op1IexOMKX3IZ6OC1Ur7D9lvKV7a93QSWm68bdemZBM2+OU6lcUsgHR5upA9ruwwIJBKErdUPIEY7+PHf/o1/k7k8usuE2Mto5HfIbowd0bOZImjj98WqESCdYvyy89mKvbNcmuZxNpViv9X/UVweFsNs7igB1+su3485sX2pTTfbAN/gGHe8PsdguK2suEld/hU65EBaJHc7e0ELMShXt4PDKr3463cNBoElE7U2c5udLj5mVYTVficbJkaNeJx4/JhJclqTW7+n0a4QKLFTej36ZBiNDNXZvDeN56Ssgsmk2Az7dCd38bg722IHLSiDodM711XnotS6tqj0H02qtruxyV2ZBc/+f9jTG2g6pkIhGbOB/ArvuEQgIsSaD5CMZjAzrj' + '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' + 'KHkCv9HJgPH1qUvWazg3r4iACnmyyroSVVBDEAg7DUzfNpQOB7nusgTRp85nkLLFYSQT//EltNwm8SuXxSwST4YII1GmLyis75NjL5k35ec1B7BSKTob5ucsMK5XCpxw01hgQa4UJeDeRXSz151MxJK6IoBAxWha8AsMpdyMJxy+Eofx9pxabvOeMX+x4NyGSV0RQCDsNC1pm0B+PxjNS9yjqdRq1RUoDR0U8nmJaSQAAAAAAAAAAFk+t1+hlsYeLk54FgsRa9htSuewWIh/juZf0BOHLj4Gem3bu9MOxOKsl/yJyq7xsQnMszweGdvhifPqxGLuGGR3cM9JqoetxlbFfsplV/bWA5U92m1s+5o2ko2IRFbgfB7rjzeVn2CNMdYXnE6qqSNvrDrX5cAmYkMEn6ZTmRRWq9NmncBSuO6vAsFTp8IKKzzLA243I8AHk8nCPZDhyizDO8ZeL27X00z/VjOXWCSeselOZDJdaqY34W01lHJCCnn45mG+Yj94UhTZBALHRBNILvH98MiWWxP2m8XsFgmpDogpKBTlkr5OGYtUKhB9cszAD8vrr+cbG0nIRCIrcD4lZBZNqEDp1SDGUT4f9Plm' + 'usMgP5EM6Kvy7dHCYcR+8IFMuUWs02Hzlf64lEo5IQVcnPAsFiLWrZcYZfP3cXjpvYe6K5vwofREQAWyWWVdCe11vkgkf7wLdZYSLhfP9Cq0SwkXhel6FZZrhU4nVdqf7uCDkkkTR5EyQypGI8ZSuahGW0etPkN0+LRfJBKxXoskF/bweGRLo/shYv5/3aURS7vMJ52kbcEBc+C90CSidiIgjFmivKCKj8SQbbg2803kuQ10OmZn6nFHteBwX0bvJ4LLKhUIsDnsBl719FsefSG1sYPP0FsQ2+czwGApXHefpzZyOUwBfs9VMhGGwxyB2HIOGg1Fp+07j5l6Pd+JWDr8ecft+ysu6aQZhkPvDs5fCc32e04tN09qa+n6NN8Etq3UcDihI/mNIk0KBX6qocliSLhcG/eo4/2XYDCaLrULKm5bo1GCDetCxOH+p1cilI1YKZodg3N/z5zIZLrUUaVbT7XUtypQCL9Tgc49eZdGptjV5C0E5dIrgPx+MIeWV7aed7VzVKA5aUQdgJfQtDMwyvvz4vDP4o533eC+jMNisS4lnElPRqbOcm+529HKQeJCwe7RTbp2Ay/0eqMPsEWyaKk6zeTM' + 'r38L6IRUnQgEg1SzwUaCY5JUNcLIDv7S7k438n/f+6cWejOSDGDxTfsSO1LqA+WESgyrU/27kAed6vY4D3iKGctI7FWPDLMqtZ3Estb+9+Dc28oi9PPsthHfWBNUmpxA4z/e31aKztOgwcgSQyLpwwela4FY+m0NdyeVebHh893ZsYt0QirABLjsLZ//q8KU9Kz4qC11kU97v2mx7ytoeMT2L69Iesfhds6AnMZ+XQxnEdiPkuTBTGJ7mdkkPe3+I0qlw9+2i1GQmx8VJi2/bU9m6gVLYry1GuLPWlKqaui+oFP70M4BSO1oCMDmYxTJQ/4WzRWoJxDNBJIxoGlw9ue8imyXzEywM3zoNfyzucBl3vJYfMeA81IhTt5BMrtQlfFeQ5D0k9+HCDliXdLg8UExPBr7i2avkXIK8FGyEbxHfUJ+1O6lcy47TO72474lgmJ4NOsLzEOcA+PdeOckyCh3MorZhn35FLUZReJDsPJXSw+I9+uX4oi2+piapJQ6GcTwaMsWhYZQ7mQJrxH6733zF9XATqukelZ8VJi0xqm2u/uAT0IYjjzCK887xc0L0EM26qo5dxPwL6wb7DMTLCUG26fw00iN' + '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' + 'Bkx+ZC9lw9w9Kz4qTFpj2lvT80CpIQxHtHTRV6FhWTGsWTTaHehyZm7jZRF693ZbyG7TZxawXESbpohcIB1JxbkFOHqINGxFExByxLq53f+/SUYep1GvmdUpd7wc4FuhsPeF5GAn21JUbTC6bld4jDBa1wdlD1auyYfGgmEv8pWlq4lE9fvFcX7VKOdZ8kTKjdy7zix9uIiqFUq+Mo2xuh5hm+mT7OiLCfK9nugTtxd0AapLKF0csyGFjxQxlcruSMOBhBOY0bj8t1DTsvmIiTmoapmNHOG5H4iODORzRlp4mVaDdpeHFgLPKtfuI0G/hccTtbPxoU7/kW/hK0Vn53waAjC30QV1DJj8yF7Km6Wj5/cg2p4GrWpgMaK7sfQ4lz50lH7X0mAs9GY5GMD/ml9Qp/NoZ44kNNmDtKRJ1M1orxt1VZK1h388PQIubeobq/xfW0USH2sNcektKVU1dN/99RBtTwPYCBuoe5+MGcbbfqGjrAmBu7vKEq1mFy36eXBDZgEIKccXkyZ3e/9fnAAAAAAAAAAA6yR2pMkG1xVyTdQvBzjfb7dS7mU43bZfN/+8hj31O6OO+oT8tcFX5unrXHMnJZaq' + 'GwvavyU1xDmG4SyHKk1OIJlpoovOPgh6+vsut52cS1UFakFWttksslo65qXevqKWIqOwJqgpJYBTyFs7Nq0VgbEekAEXuHWDxR86Sj/laTDgGeHtzzYhveyBHSWR/LoYRFt9TE1SSh2o2mBp3K7wBVj1zHIwneMp1MBiWWt/9XDOIq0DOdWfmFkc2ZdHAk34i5DFqgMYe1T2Y9J/w1bQ8NhYnpE1tW7VNTCWUdPWehwS+WchzSZzLtKMHD1EGjasSSqUYWQHf2ktHXPcb19RS28KcPQNaNiKYLSzDsoerEHTZQnYM4WYfQs9l0kGMPaonszJCpbEZXeiDuLFrQGofOSatV4OcKPepEKcoYJka6Dal7RG25Yvaszth9TX9t4nKrgYXTelPEafJdzv4VvLpsGcbvn+o+tTp2SjkxvYhM4v0lkLgXwQ9FaiGm2AdDkz5XOgu3nvDQ8VXAygldweI2wsT8aU1DfkEDZN9iMFMpHdMt/Hg2xCZwMmPzKZvO9uZvjNauV7b52MNa4rW+IWWTGzwuISkPh/k70gJ7+RUANpRg6QIg0bVimeJ2+uGdMoY5KMPFOiQy9wgv746Rue0LxveSw+7UD3' + 'TEDVN9LeU9t16L+uX8KyYk2pwNKlQf0KTo//4Dz9EmQmIOSVaW+n4+Hw9Ai4qY9s0aojD92m2cLH0BCd0cYoj4p50E90h9WFRpRXm6NxC6I4QX98+oNPaB1HpNsKUAflIGya8UYKZD+hKN33NL1HEoFERwZytyMt8uCGzAIQUpMYLeWNvIkrV8qh+bD4kx37a4kkR8wuWun53RGFBCCkO0vlvraKJD7WVYQlXxnI1l07Z0BOYz+gBqaNtnZsRyof94rHmrTJfiHDU0QuEICq7JpPnblXgucUBbp7yCybMiAxpUZl+LZeT7G2Ufd1R/TUi/oNhXukZoKFqWxaoWqYu5kPrvkI63nJoV43okf0pi12hX3NXSd0HvjFC4AKGCC8vmXcsgH3orRmbRuYb5Qm50zJIb9TxOZIlUEKD5PZykIgzcyqZHuk70KaQGCJChhxDE6k9psys4vM2jYt3jVM05bcI7x8Wy+pwwm7aKqFGrPSYTGnNkjgEwIdxSlB/E2yzVrat3BL5IqneWXZhO1x5jI4b9YXNLuk6C1t1TirckVcIUfqYXe0sV2hq3DPCRzorJB/znK4vf9XyF39lyJ4qKTkTGprb5QN' + 'OFGZW08f3+RiV4zK7XG8ntmIK7DAHSwKkXudXRE8UDuiwx4RqHZDxuRjySOjmcHO9xaGxX6odtyHtKlz4JbVCa8NVn2dOlgUtAwqP1ncxvQ2AviEldEh3dPh3T2YNkhK+UXnGqRmiOV1GFR+sqWR9ZNmWHRQwB2JnqgQGGWMBltPVAgMvEYDoy0DhMZRN7893DJQeOyGHirqMKj8eVc/9yFNIDDKBQy2ZfAyK4AWwwxpvpbdGyRwh9uV7pmB4WG40fwYFNnKBfiCDtK7zA3nKWPXYFBDDxTHO8yw6KCdOg+OQHZNVz9UojnRdcHhYXe9EvWjfHNPH0urN8EvH9/CbVZIsWc5XNDxbATtFTe/QqftlxYdFDBAZX1sZ9qrcrgH7Bf6h7pO6Dzfr3nLAwT7wXM/BgVxvEY+eNYcEofpiifQfPSOd7StobnCYlNskN0m4kSbWGCAFgWPwJrX+UH8+/rYzqlL5G0Oo0PyiwYI65+bEmvQSRc0e5qSh0rnaZwiGwF8QsTmnuA6TFxyDuOSVktun14+o5naa6NT9FrYPTXn/uCQTBskJSLQCYMlh+ldhCmAwA8UMOLGs8Cghh4okwh0M6QZ1yny' + 'NB89rdQtbG/uCj+u+7Kljkruc8SQ3TGDqrcttbGhajSpKgQGXiOP33tLNaFoa2/MaiO/bvSmlWwZHLlrhRrTUlXVmNTW3jUayWBN5fKufvMcpsKjqYHhct4vlVGtelOYMCWq/1bI9hYVUh2dHihg2VBv4xz6RQc6GJxV8StkewsBgOyarn6oWXzsi0AFDBBeI1DlGYv5QQTvitM0VcwN1wenvuFtZ3+S5eMluQ3naZdaBhWRom5jerYR7xYYIItGCfTfPrepgaseuweK6H2swLeRA4y2XiMfD9ONRXSwVmBn7fcCweqOvrpfS+CDEjjN48R3ws7+vlwNzkhsNUwb0oxds2QWwxkQJuqe0adicyQDnSmz74Ll658o/ILL8q4CqKronPBdJ4ZDGqz6J3SwKM9HH54xt6k4WBvQuOOSLsi8eBmbQAvvBpD7cce/QvhiHzvrEEYDBJloPnpHtVrY3piPQmOmldGQ2AjHKm5jhFMGJ1J7wxnXy+uwRGbXKZeu5n4MCuJljHwU0vEHsFbIgHEiwywwQAuMinrhH9Xaztug3ts46YoOdK0Qk1TcxhWmC+kaF/ZVzBmN3V/+uL2xSb/lMCiviQrt' + '1lum9bStemp5VvCIKZcifhDoZlUys1L5DlNh39rO/jnOx/MEn8kBYf9itWFnf18ul1zPJtIlh/BR7w+GVDuvYy8eQe8Qy/KPUnImNbu5SoiujbrnM0TwTUEHadNmiP2as6uU3jS7uWaAExeSjfGqm6VkoPDFETxU8THUvr2xoRd/caLz6o71tUCHhUnI9lXDfvFOaUTwXezURmPc9VE32PKs/Q1SM0T8AAAAAAAAAABfvG5ZjvVRWhbPNC7xqoUysDa9bds5XI0TdU/m3TG3Ervfp3otbJCUiefIrDpYKzA8aw4JzfpFncSuBYnH4mUhSXNad39f1GjK/WRWHSybGNoVAgMvn8nhiGckNpQmg2k3ghQeO6+JhJy11TEkcEvp19tKbxrT0jOm+YlDKpPZv501OauKDuOwU/LKrxXH4tFuGSg8dkMPFT3r4pNjhO3EXjyCwyCL+QMzuINMuUoT/WRw3rEuaGtVNZ/RN3pTxDZhyqV5AvNZdQQ6l1KC5Zp5/X9wSCaDEpzFLukTaZzNeCi5/w59rI0dVFV0TnignUPLfYjMs1IzQUS9EhtKE8+6TUnNJf26ThE+dssgjAYILz/2J7oieKB2' + 'wolX8gT7supFPf6B5G1n45TB5pU9p2IbLINoXP9JF2TzLBGX/E3spSsk1r2SLmj2sit4RJrFET9I87bt0SF8MS6erXW+tVrWF0/YtF/ULWtO1OSWEjir+pLmtO7+vrXQRqDXMgvvgghHIDuopZEqUST3W/jmnj6W8LE4JBPPCU7+4ln7yQH3dydqcksJHNt9vfj1Ae51R19ZmzwiTeyGkW2EAY+Zwer+dJi45BzbOazgWV5xIXxbtyqkOic8UMCv9QtD7D9UO26Djj4hYnNPcMCUkttFB/9Ycr/qn9/C7mcRaIrPnM36oBqBkNhqmDa5esvZO8YVx5XHMyw6KGCAyoY0RelO6H1Q9pZqX9DW3oXprYFPltXaHHCiL7aePqPVCmn2jVgrZEC4Qo7Jwu51f2BKSeOsjfEsW4b5CwwQyyPh2bLrjwLz7ik5E5TT0iVEyOChf1zQ1qq1jMal96JurYGT+wgjjwLC1caPRlsvn4H8/5zSiP26xXcFkVfzWdxHHSYuOQf/SSv7WCIz5ZrFV92yvOJC+LZzJXe3Ykjgls9vmcSm2D2nTMEUfkHreVcB9IuvdpEqkzc+8p0kmywKGenhYyK2+GIv' + 'VTaZQEd1f3qfTVbVpHsLM4IlZ0ZqoRdMuPUFfesIL7LMSMEL9EdfUzcwiNQnXew6lo9DJRgK7RAXPSMs9wFhUa5O0J+Ub8wT/UtHQcRTmHMbWz8N2ZM3ZS/8sJZ7ZEBS4CN20gqJhAyjrjpwMpsY10GcvSM13oUm+v6/EVt8MZkDlwdPhaqbDcWK1PtINrlwvsYL4/xBBKge/zbcS3CHchMf3DPthFO2CETjPjQXZNMP8RtuqzjNOWQ1Hwp3YbhaO1aU9QnPug4whXCEuHJF0Eevs70il6488rpcL29rVUp0vcR2H09w4c/fxkRx7cRe5hB4TB3ArxZ6yinWPBE/KC3tQRd2qFmvrF8hHpmj1e7UhPlJqH7zOzzjbKWW4BPk0SDwmDqdQyxrxARk3Fl1Y2nV9eXRlWyemulfBDaYuyTJ7MjaZqTvRNaVCMilsurGxAwiNcBQO4A4wZO6jGUhAxzux11GvJ6P0zEBGTdRWtHY4uVohuylD7E3EI1XecmRcJ87aQXKQgZP61CDFoDK7+xFavMkG9I4WNZzr+GBq74kL1Tnytm/jAIR8YENzBn9kLxNuw9DxgqVGERqnaB2HaG/y/E/VwEq' + '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' + 'dqw19xKDaRwsa7fZ/s7bX/zNsY9MNRqDH3nAEsMWBYLwq62uYqdMt+GlgByC7wb8Z6IYRfLLI1dRFGZfXfBNnb9A/S10J4ZYoDk9P7cxg9oFpAnRkuOwF6n7KM8LQGX5JamiKUK/PXzbdeInA0Y+ArMm4QxatdBs55aOgpWmLea5c/OzY26tQt9XHTgZwwzl7lSbcinXy8USmSr9ZeLRRvjvTpBWsChktwQeE0Aw4ovALt0q2tUJZ5MrSvSK6V0Hb+b7e8bcR4Qjmqy3VfYWZkAaS+29uAfWSF6o04mvYwWkG8IgrbSxPXU7MriXKfIRmX5YS7MyICkdaDGTztocf/9atsDJn4GOFrvV4n9n46GlnTTuJdIzzZj4roU7VKLZbfcK+ssQXnl5XS6ZubukJY5De2dEM0F4AYb2zohmgvDr8JKjuzR70rzX+mLxjR1VrdnX0BHFVx4L0+Rxsb3/3qpsL4CO6v70XuV9MfbIgKT1D6R/8ET8oBrdycNR9bWV6nZkbTNS+SIAAAAAAAAAAIWQnxb1jr6mRilFc6rxLMwKVRK/Odt9Lnjb2Fcx3SbVKc++CGwta0ghi102WDoPmxUs0q36zXis' + 'g6ORiOLHlbzDudplX3+Sap7LoBssHYnDB7X4UJ8vqep+6NbJJpQNzza2fhqvO27KhgeYWXAkJav7eEnf0xqzaUx8V8yTKlHi2WQTpg6KJ/8mPqVmxxWmcWxx/DRDdtyJSk9ZUoRjevja8xTpiyC88lcnaMFKuWaHEIjbfGguyLuIcHX5U3pqYi56RljzAsKiYZEW2+WCCE2ofd4BgybnCdzAGnecaZfo7cOcPax9UMimCjOhoHiowMGoK+RSs4uXP3Rr6hNKiOmiKMy+uv2aJ6vq2U4GjHwE9IlSsXgiflBc9Iyw+wSZWWAX4BVt5Iq9RDi08qc9NTGMUormSf9YhbUV75JN/Pt2DGYcIS6SVjS0kxlcxZp5hpzaUZoh0ZA+MpSBBbW+XC0ZSs6M1F8umEONTKI4Epzbm2+pyr7+OdSBsmAJ7wuMQd7R6/aRpY4VTm2mTZ7mSB9UsG+OzxP9iknYXh0ByeH1r8gmURwJTuP2mKMwde5nrVrHgi7sTbJDjdR8KMGZ2nWJ9oM32xzoks3ON8V8Id2jUwWX3lA8VGBqQvKqVD/3k11yen5zYhup4jKHUwdFnfFWoZ4Pwt/kd8Yd07TNnCJ9' + '5Yd/A5hqNBuUnrKkFcb07WIGEZRgKJNAY4DnWuhOEbCL53K21tDxb1CSkJHVls9t6GeV7D6e4N98+SdIK1gUMshqPhTuwm20cRnNp42swPbkAYnNEAy265KtvDoCj9/3sqAXwtLTUpwgDav40FyNazSnj5ui93c347RxnY8jHwFFvkI8L1u3wfceVf79iOVdaFMDK1nz7m5ls+nE/wc6qncqwzma5evsh4Ful/hCp1sRDi2y4EhKSzMSd8s92N7dvVEMrHnrn6U1IXlVKpH1x4qwqWhG4GptQ8foC0vwszoIybNUaxYe5TnxwjXrqZC+wb7yN2YGx7IsIJIzYUVpqusBUjtvwyialGlTq5Nazt0nKDj2PhM0DosEVeyhK6BSd6GyxJeP+KKlUSLKE+VAhiJ2E1hi0/HN243f3gi3bP5dHhLInkoXig5WgWsDlphn7l95lTMD7Vmv7XSLq3jXHW2Sny35PlPu9dio+Lp5jCr2GbFpjjnPa5Xdry90kQTi7CqcgOCIZCfOXI/YgluV6sTg2Zk6xgJxRpnDpRcwdvk9GxUfUKKfQp7VBeorx1lGNGZaz9x/S5hhsftTKSNC98chwAgOhkEw' + 'hpPNFpb9e3SHJzGScTaxS9NEbIpjoXIbZpo16KZoDkrKtljyOVCaFqTl3k70Loq5N6dDXug/CNkTTmI54mx/loJ5Gjwt9nSIP27wCoMpFjyOWn5C/etlkVyq7kx5gd21GfI0eFrx6A0lXd3j7Zi9cFCJijKpnMysKMpFGdpOZlauWYgPTLMdIg2XmPo31tsmMvlo8LT/zRqgDwlkTyWFRfo61RdeJN5y9GxUfF2yRhVxPoD7/w9+IHhDzytz0qr6vRfqNq7fYrT9ERus0W+Sz0q6p9vHLWfgs0FrXa1J+tO8oxaySRSoixXRUAaK7PkU4nwd6+Me/EBP5Ix1m+2iI37c/RQbUix4TlBw8XwmaBzmlsrBWBXzvDXSpks7tIGngAz/Kf59/fYe2frD1bqksGwmY6ke9ZnRA8EZkTRAQ0H3rU3tafIFVM2dlkm2G9aryMO95+rbE2jRMYmfsCr7ZR0Y41Lh+ufx2jkjWu98psGhu/XgqO5PepE3eAXPmgseMThxYYC/jlvZ+DrL2zzlgAJ15RXTi4l+Ry0/IfD7vMYtlG63ho6jlbo8JI0hlC4J5yI2Rb/eOYP/ZP65AuQbscl3QWMNENlX' + 'w8sXIrWNTsyieuxxnK4MO5n+y1GkjBX7FGWsgm0nMyvhvQR6116/AXn3M6+UGWDFZy7JbEGjxHXCf+umUkaE82Tv0P1144c07Z5gBAdDrhj7jimTue8UTThFPrEMYlqBaXhIB0I1XBJIz0LOFKbunhysH9YGMS3Oe4LWukeS6budFBx7H4caB1YWuA3BHEouuEnBmPIfp3d8qRgByNmlBrE0jkh+wnOtQbINHph7OkR0YKtVo8+744TmKANFdvIKG4fRbYl6YXMP4n3v5F1SWIPN5rjKPb63DCNkftAdERl6Nio+oFkjhLYfQPPxiT8QddRX0UQEcdxFWNo0I3A1uNymEWWH/CBDjZtn08mrJtArC1yI7g4lF2/nejgqtdqQJpzEctnY/jFjxB5G+qjLibervHcWQvUvfR3khS8SbzmoxrowJDOboGAFB9fO6IjIj+6Cxhogr65XokSJJteAEfyl5yg2pFjwByvOu49LTL1Je75K820koTyv6Zu3aVV9EvqevQWntanowEuqW4Nr20JzFI+sO3kFkIOEgShRwSHlV9NQbFWw/XL/mWrLTz1hPtoMjmTi3APwhoNW5rlJ6QTq1yq7Cw/8' + 'F6S1E1lncGrjyOFvBNU2f/hPMAKNr1cMGEbI/L06IjJbgSD39sqRCNRvojHs6j6mM02UdFM0ByVYQDlmworSSb7W86eanyH1aMy0g6X+li3QhXUbV+ExWv7QAj3lL9GOSw5bXyDmrd8aMy3pbrGrTKPOEPV7ZcYEEI97qNYsPNerB6OhEHPY4WsNrRKRvtVs8vNmQzUywJcuVXcmss7g1AAAAAAAAAAAywKkdt6bUCnk4y/Ui556wnNLZe4shPdeblOGvM1+EK8BtPyE58vKP8/oc1xlkF/VNhO/2g/0wuYRO4csMef26C/hi6JVBSrr6XS3LrxIoeQKvFZBuJ2Xm7RqpeYiArZuROwmsMS7/4emkDtbJ6UDx39oAZD8meZHl6hKOqcajZzdEu3hYDfqfMVUJR3dDchOiMVMfZVr4xNNkWlgSGYrXbCAcsyZCbmStd5ZYsXJfFGBuAOtGbY3ybL1l9lKgjDsCwiqxV9WXaTxMn/SAXKD1q2YkZ54815jarlRlnZ1H1Mk6SFnClN3T7n9PRwV1G1IkvZhlPvaSF9aNdxzEQFbN97T9HBUd6k9wAoOs4HNDY27iNgJxl/kNhYQSZe+rLpV' + 'IbcKyVaTsoxZ9MXiJUEYdtXbXrULIfSZVdehnPVcCW+pcka0w/hRn4VS1IeivTg1VGNdGBKXw1Ajwu/chRg78p9h+W7MDJN5U0iTo53cj+1e3wtZqgpUy6wsbRqfOJRc1667oNiqfecqv6AMCcXvKNhMxk889y+/IAP2TbFYeLOnJMffwG7J+AafMj9ogIaCzClqzVHQHJQFXiuuXMDFw2Jw4sIdYwG2O4QnIDgiGcDS8JAOhGq4JFL8byd6F0XSxpU8jOlNiw/gCfj+MJV1PmVbLHmSKE0LmEo31UNH38Tqta6/iAjipZo/0sCQzFa6nKDg//hM0DhMJZXkr63hYt9nCPSzvGMCv2IPI31U68qTQp0QHBGCYAl9T9CM3dTajC+bVy5g7O9winx/GMS0Hzow26Tf6dP/QAbxmn+w8Htfa/fdTcGe9B9tBkcycW6P+fvMhmpknTMwjI3lZ3REZIlxsPlyoCks1hpHJD9ht9jv64UR1MgnZpYctr5A0UejqrNfJfe4Et52FU5AcEQynVE9drZOVwaT80eax9L5Cqibiy5EdwechSl+uZ09haxpfjfmLfx9QMN3byWk7pOeW+BFyFDdj7Wt' + '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+' + 'sQ3wgn+/lqlqaSea1Pja4eeGidzT1f8ugS4aKx+lU9H7rZDW66DKGBrFQ7I0MQ45FgT33yy5eCemJBxpURifAnU1E8zqr3xeZPKln8hMTvokfSseSJ9fWttk1xirR0xIefSnofInCkAVc9qDKpvrrjSXhnloYhxyUUg40qIwIwTwr2U3/XL2hR0GAj46a0S6Z4WIw85u3XNmqJP3zHCs/9TSTim17anfOFYyFHDqamwHw0GMDlpKgyvLsi9WNbrNBLRs0Ah42QoG7lq4DEQ7DzshH0h2yPnlCVjDiRLu3pjRSznNv4sBWTl7KSBy9Bvgh8BAkxPhaN6tJumIR8qjn04UDIScZ4W71f9VHbfz2FOgykbRXVykDc1gIMeH/jRvhLdtzxXD+1fe/aD8oSHkzkuNe2CWAS09msZCrSmKLGQIddi9EPCvFLNXxup7g3SsTWMh2JpFFjLtqWcJxxmyP/dsJLvzKLwGxmLVJpEsCPI84l7EeJKzZrl4KD9vTzm9wIyPnp1oM/1PORewnnn0N1k94G+ywIwQ1oh4QbHRS9oZsm7uMhOdsLSUh2Z12T4vglk3dxmHwFiQ6ax4PUZhdfGCfgP/bIcJ' + 'lF3AqDU+uH9FFvllirW5Jj+Vc5h+sCDvuFUzC21RSDEq5qkbVCvLQWMx5BPGFgR5QI+OgYDTEaDv81FhwyVQOtBmIvm9lXDViHbZog1LjUmlUzE1VzoMi+Fo02TfkcQh9BsJ5/UKL48SsJsPJMGhLdpJzCypWT3EH1w0Vj5Xpr9U0U82qFaLgq983+BD9kGa6momhclD+Lzl3L+01+kdK7J63d55nQUga0Q8rtbmq217rpHJ9hvoRT64aKx8rlFjEce2UyLjMqTSPBSRuamS0I+1mC4DEcfKcKxkKODJ1NiJW8KWD1X8xXZCPpDsje/Xb/BQft6ecmc9z0XweozC6kqgYFSUH1yxWBD7W7De/Zxe/qHjvJrGk27dS0rcgAPrdBgI+OixDdIUXsG3KIWaIii8n3NQFylEJwoGQk69zNOXKu30Mxwr9gWZd+QKZqiGJVAwKkqBLtbdio2gpwN3R8UV+HqXDpt7MCPqqWAaxXi346o6c/utpg+2mTEequWXAAAAAAAAAAAxDvGdYgS09CKTcaZE22RVDeyvWRqWB5JcpJeLuKYklhwrGQo4dTU2QaKVtYLNYCwyedzBZCYnfcGhlKqfdkJx' + 'E52AOybf0KGuUcTUQegwFtgT+kStZd/BrAvyvEXU0hMjvmqSRsUV2UnXTQiSPc84nQUDISfQZucvf97/Xk1jx6R+KgFVJH0HmbFv8S+ov+1GYdQ5jJcqr9/Qu8ijP5VC3KeWlKUdBsuwIOu2faHnJboPBWNpbao05PGkgNX3bKfEOONOlRDq95OegSQ7ZPL8je+uRgctJc8sCPOjWG/wTtelY3WzzzpWIMlHzkDnhlBD+KPdhvGCKVaLeV6sammHgAMBHx27Il31NhLT9xReAxifddowDew8lXDbnDcgyfO7Ih5Xa3PbuHL2UkDk9TbdRDviUYiryKriH/442bNXqP1Dym7n5PEXyqNhS4mkfuz+NOcy4cZinoN0LEMbmbHUzzoWr4PC1mqq5agESZDpHCYnHXZMo71fkcS3TD9YEPl8bdBF+EGixn8a/Rn+YzFPyPlXI42YnOmnCQddUwbujlX8VAKqSPoOSPpWPJAjvrRl376rylI/dmyHfSLYvOHuzE0784XgReO+u2mzYRVzPhDqrWcg/UMots6xDnHl3Cq9zETvZzfgt1I/FY6kErCNmJx0xS22zmGb61mZK5Rd6Ios78oJd29M' + 'o71rjVt+N4TrRz2xy12JMMP7osKbSqB0nCgYFSXOF2toMxHy0MQ45F/Tute+hLcf/G7RWuX6gJs2zbARbF7+dymRhEdSCVjIopBwuVlgRghTEg66pgzBAToMBHx01ohpaR4KxtLaSWhz20l05utHUXqDiv30BZnJWkrNM7TiH5lgRslPwDSX8OarkujRy46iM1TH9WY4VvHZPuFwr3uuTWFr0nvCKuZ8krOaEDl6g3CryLMwS46YkL+WcodjCwKyW2fWB7b8bhXQMcOXzlU/5ha6WwGwBrUlqJut5ilucMhqH1Jdd9NDW24QNXBXPfoLZg77Khf8lat2Mnqel2NL9kutnWRiRYv18YMMrtvD90jFyPVCZpEx/5UEShzcSLDLiSli3zz4uGawueII6TDBNaFPs/BhGnZ8jSYF8hwWATbWtxki/sxUnjcIlDilkH2LC12jjlgD1JxaW8yc6m88vO2uJG07c//l0rh+D94i7c5eVKuxyoGF7B3n+I/oBWG5rV4ahwE1oIwvKtvWZc7MdleAtaeC9YNYPtyKLu3kez/J2Vw1Br7nD4O+ER1sTgXupgO5CVk2dBAQPIG0gJ/eXSxptgJ9DHdK' + 'OZCA19XIeVMJ1B4WSHQGtM3WOxgmUF5f+Z3C9JsCmOic0FQKlDy2f7yoS3+JHxfFcj0ds7eN8qZ4qm5x5ztPLhQz5pmgcWcNhPIb5FRiB4KY3zMntNIPL/BJ3OLTdp5c22xgGZZW63pkh0ayB4tHgzLNI1mNy63PHqSVW/DH2oXpoUNAG51Gtf2Spdm77CG4yBOMeQ4Ljhsu4AuabXulYvhXEriTt/H86yj+2AvqlJ1WSmXrikDqTGyZiOhHSigjRTWJixIdjy2r2MAyMazL9Loukcq5hny9eWC+Pe+OJjoMEal3YC/W8MtQ4a0WyTUn6uIulANf/YkoZtEvXeLOGv8bGEGrm/OQn5M53oz+DUOWRyfIxIoL91JFAsaqrlMcm5xe86wQtBNPovpJQqsypT8WWmLlURIrx0FI2nbm49eSSEDl5GSyp9NyrkPWl4TaIztyoQXhGoakigSRSUGmOLS2hSXJ3nhl3eq6rKbPgAIKl3PCULa9iMKE/7tevTOTi6DfRyyPak4q72y3TZUcMkJ5g3IqMY1Bc/fN/784m7IHTAr5OCwCbIpqDwskOgNab9rlPF+Ikx/Gi5iWflOKw0T/WccaqOY5' + '4vzgzkOekimiDN4kedjNQBnon6LI69jp9Ea7z/OYJwxDs1M+IoTkVdgvDc2OlFBGUQZvErJs6CDnOVeva8VCbQgezlpAwW+gOxk9T8W/q3t/5mSI3xdNQg6YFO9wWATYgTeshXw518axczJE4YWoIWlcP4lvEfhn9s8GV+Pv9SQaq/J20Clj1S2jZk51uR5eAom9mBB30iiQwf199BNgjzxVN7b9k6kXqhIQfjkZouAGhtq1MJlreNqmsFWe44Juw04v91YIWodtU1ikT/9BN/xYdZWzWUisfKUJXMfV9n77FH9si3VKwL/rJquR3az5aJbvxWekkXPKmjHhHnxcM7vkQYaxMxWpDdt5O2iav+RwtKArp/ogjuR6OntzB/lRjOzVvhSjaCLu7Um5I7FE2Rdwi024s9wxYIghnydl/tOz+o/c8fJ6CZELLTH8pgmbD1LEo3jtbcxQzL9eutmBNGvVghF/ZipPlM6aUNT92d8rJbz7RSB1JmfEK2YfSfy/SSQg/HIyWd0DQ23UGMK7PB9uRRf4crORoIVjvGmvH2jUPqS67ruGtgHK0EwItWkUrJTKywmAyZhUw9hzmjc4ZCb+xcAtusrC' + '3qnXeL4NOz4ED2ctIO65UOWw6jd7spBF8wqxNsu0JWBiAZwHNxIs++hrkwwTKC+hzBzrVC7lN0tTj9KKohs6CBthIjrYnArBNsJEdK0lFJ96I9Pp90ydBr4h9ueZaMXtz1+GgDYnjHf3BdYb61qcME0rR9FS3OCNX557/cI07Pgkd3hYPc0Y6oZ7pnxEFdWqTOGXnVppiZkAAAAAAAAAAOxk9CEzxpbxtXxVacFrEXHBx5JvRn+Ir2VNlv4PPi6XFfk21ajEDhm4pyxSqfGulalRfaoh2xncWNJxBPoY7pRZGKFI8q2HgFzdFina9lfEgnTBUWT7bPrR+xPbxuBW8n1v2RDPYJ9qtj84vdmpqk09n+f69SbAA3S7xwaHFJne32MHNLa4Uio60+0DzQrCb/reryCDwCPUwA1CI07K4buFOMuoXNdulsQCJQ5uJFjrR7w0EwJqXQWv16cfEUJypJeN94TMP2LjuW38HqFEx4Ehss85FZbIrjGOTo2VCRbzzpVWzD6S5WM4WlCb3X0QRzWBKaC156+j5vOH42NwK3ngdV1WU+lAAXvpA6X/+fQSErU8LJDoDHUzB/MVhX7E24+vuGoMYdMe' + '2eXdgYYhOVJ3+KrSn9Yi4iW9qBQ1eHH+dXEXSo+h8MoTf+xgmF1lYTBEnsGdvH/npUDU3UH0zyzcIGrgrnrpFluRHNDi2lWosjBfkPlHEx00S/nsvVLGt10XxmXSQz7QGCJP7sBesf2eWemShEtkV5pWjr+kpd0Ho8YOaHFtpFR+LLTE16IkVoexdjBMoLy+QTrupjLzNn2ZFeNrvGdmO0DwPuo6Rl9pHC0ow+CwCK1OaCoFSh5bsQXFt2EoW9BE4b+NGltcKRXywGF6wwFMdLf16PHRHMNZY8tMSz+nRe+dGoRGnInfa+M2MIJLK/s91fR09uYO76L1jGuD+y1OGEZ25F8K3zQRIHgfdR0jobq9Ypszgap+0a4dd1MZ9xuw/tHIDaMumoRVCQg/koJRcCmsAWNVV6cOp8lpRVGDHQSOZWgmBNS6ChH2UfiIKrdJ133JbvZ5PYrvJ5n1KwQtzUju8LB6hzDJIvGi7Q1Uc5JhQvHTL9CXx0pnTShq8OLhgP18yXSMvtJxfnBnr09JmpOCkKns0duziOOykzRN0XInNBWMJQ+j1g'); //== // Variables var sigma, N, h; // 64bit tools function get8(x, i) { return (x[i >> 2] >> ((i & 3) << 3)) & 0xff; } // 512bit tools function add512(x, y) { var CF = 0, w0, w1; for (var i = 0; i < 16; i++) { w0 = (x[i] & 0xffff) + (y[i] & 0xffff) + (CF || 0); w1 = (x[i] >>> 16) + (y[i] >>> 16) + (w0 >>> 16); x[i] = (w0 & 0xffff) | (w1 << 16); CF = (w1 >>> 16); } } function get512(d) { return new Int32Array(d.buffer, d.byteOffset, 16); } function copy512(r, d) { for (var i = 0; i < 16; i++) r[i] = d[i]; } function new512() { return new Int32Array(16); } // Core private algorithms function xor512(x, y) { for (var i = 0; i < 16; i++) x[i] = x[i] ^ y[i]; } var r = new512(); function XLPS(x, y) { copy512(r, x); xor512(r, y); for (var i = 0; i < 8; i++) { var z0, z1, k = get8(r, i) << 1; z0 = Ax[k]; z1 = Ax[k + 1]; for (var j = 1; j < 8; j++) { k = (j << 9) + (get8(r, (j << 3) + i) << 1); z0 = z0 ^ Ax[k]; z1 = z1 ^ Ax[k + 1]; } x[i << 1] = z0; x[(i << 1) + 1] = z1; } } var data = new512(), Ki = new512(); function g(h, N, m) { var i; copy512(data, h); XLPS(data, N); /* Starting E() */ copy512(Ki, data); XLPS(data, m); for (i = 0; i < 11; i++) { XLPS(Ki, C[i]); XLPS(data, Ki); } XLPS(Ki, C[11]); xor512(data, Ki); /* E() done */ xor512(h, data); xor512(h, m); } // Stages function stage2(d) { var m = get512(d); g(h, N, m); add512(N, buffer512); add512(sigma, m); } function stage3(d) { var n = d.length; if (n > 63) return; var b0 = new Int32Array(16); b0[0] = n << 3; var b = new Uint8Array(64); for (var i = 0; i < n; i++) b[i] = d[i]; b[n] = 0x01; var m = get512(b), m0 = get512(b0); g(h, N, m); add512(N, m0); add512(sigma, m); g(h, buffer0, N); g(h, buffer0, sigma); } return function (data) { // Cleanup sigma = new512(); N = new512(); // Initial vector h = new512(); for (var i = 0; i < 16; i++) if (this.bitLength === 256) h[i] = 0x01010101; // Make data var d = new Uint8Array(buffer(data)); var n = d.length; var r = n % 64, q = (n - r) / 64; for (var i = 0; i < q; i++) stage2.call(this, new Uint8Array(d.buffer, i * 64, 64)); stage3.call(this, new Uint8Array(d.buffer, q * 64, r)); var digest; if (this.bitLength === 256) { digest = new Int32Array(8); for (var i = 0; i < 8; i++) digest[i] = h[8 + i]; } else { digest = new Int32Array(16); for (var i = 0; i < 16; i++) digest[i] = h[i]; } // Swap hash for SignalCom if (this.procreator === 'SC' || this.procreator === 'VN') return swap(digest.buffer); else return digest.buffer; }; } // )(); /** * Algorithm name GOST R 34.11-94

* * http://tools.ietf.org/html/rfc5831 * * The digest method returns digest data in according to GOST R 34.11-94. * @memberOf GostDigest * @method digest * @instance * @param {(ArrayBuffer|TypedArray)} data Data * @returns {ArrayBuffer} Digest of data */ var digest94 = (function () // { var C, H, M, Sum; // (i + 1 + 4(k - 1)) = 8i + k i = 0-3, k = 1-8 function P(d) { var K = new Uint8Array(32); for (var k = 0; k < 8; k++) { K[4 * k] = d[k]; K[1 + 4 * k] = d[ 8 + k]; K[2 + 4 * k] = d[16 + k]; K[3 + 4 * k] = d[24 + k]; } return K; } //A (x) = (x0 ^ x1) || x3 || x2 || x1 function A(d) { var a = new Uint8Array(8); for (var j = 0; j < 8; j++) { a[j] = (d[j] ^ d[j + 8]); } arraycopy(d, 8, d, 0, 24); arraycopy(a, 0, d, 24, 8); return d; } // (in:) n16||..||n1 ==> (out:) n1^n2^n3^n4^n13^n16||n16||..||n2 function fw(d) { var wS = new Uint16Array(d.buffer, 0, 16); var wS15 = wS[0] ^ wS[1] ^ wS[2] ^ wS[3] ^ wS[12] ^ wS[15]; arraycopy(wS, 1, wS, 0, 15); wS[15] = wS15; } //Encrypt function, ECB mode function encrypt(key, s, sOff, d, dOff) { var t = new Uint8Array(8); arraycopy(d, dOff, t, 0, 8); var r = new Uint8Array(this.cipher.encrypt(key, t)); arraycopy(r, 0, s, sOff, 8); } // block processing function process(d, dOff) { var S = new Uint8Array(32), U = new Uint8Array(32), V = new Uint8Array(32), W = new Uint8Array(32); arraycopy(d, dOff, M, 0, 32); //key step 1 // H = h3 || h2 || h1 || h0 // S = s3 || s2 || s1 || s0 arraycopy(H, 0, U, 0, 32); arraycopy(M, 0, V, 0, 32); for (var j = 0; j < 32; j++) { W[j] = (U[j] ^ V[j]); } // Encrypt GOST 28147-ECB encrypt.call(this, P(W), S, 0, H, 0); // s0 = EK0 [h0] //keys step 2,3,4 for (var i = 1; i < 4; i++) { var tmpA = A(U); for (var j = 0; j < 32; j++) { U[j] = (tmpA[j] ^ C[i][j]); } V = A(A(V)); for (var j = 0; j < 32; j++) { W[j] = (U[j] ^ V[j]); } // Encrypt GOST 28147-ECB encrypt.call(this, P(W), S, i * 8, H, i * 8); // si = EKi [hi] } // x(M, H) = y61(H^y(M^y12(S))) for (var n = 0; n < 12; n++) { fw(S); } for (var n = 0; n < 32; n++) { S[n] = (S[n] ^ M[n]); } fw(S); for (var n = 0; n < 32; n++) { S[n] = (H[n] ^ S[n]); } for (var n = 0; n < 61; n++) { fw(S); } arraycopy(S, 0, H, 0, H.length); } // 256 bitsblock modul -> (Sum + a mod (2^256)) function summing(d) { var carry = 0; for (var i = 0; i < Sum.length; i++) { var sum = (Sum[i] & 0xff) + (d[i] & 0xff) + carry; Sum[i] = sum; carry = sum >>> 8; } } // reset the chaining variables to the IV values. var C2 = new Uint8Array([ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF ]); return function (data) { // Reset buffers H = new Uint8Array(32); M = new Uint8Array(32); Sum = new Uint8Array(32); // Reset IV value C = new Array(4); for (var i = 0; i < 4; i++) C[i] = new Uint8Array(32); arraycopy(C2, 0, C[2], 0, C2.length); // Make data var d = new Uint8Array(buffer(data)); var n = d.length; var r = n % 32, q = (n - r) / 32; // Proccess full blocks for (var i = 0; i < q; i++) { var b = new Uint8Array(d.buffer, i * 32, 32); summing.call(this, b); // calc sum M process.call(this, b, 0); } // load d the remadder with padding zero; if (r > 0) { var b = new Uint8Array(d.buffer, q * 32), c = new Uint8Array(32); arraycopy(b, 0, c, 0, r); summing.call(this, c); // calc sum M process.call(this, c, 0); } // get length into L (byteCount * 8 = bitCount) in little endian. var L = new Uint8Array(32), n8 = n * 8, k = 0; while (n8 > 0) { L[k++] = n8 & 0xff; n8 = Math.floor(n8 / 256); } process.call(this, L, 0); process.call(this, Sum, 0); var h = H.buffer; // Swap hash for SignalCom if (this.procreator === 'SC') h = swap(h); return h; }; } // )(); /** * Algorithm name SHA-1

* * https://tools.ietf.org/html/rfc3174 * * The digest method returns digest data in according to SHA-1.
* * @memberOf GostDigest * @method digest * @instance * @param {(ArrayBuffer|TypedArray)} data Data * @returns {ArrayBuffer} Digest of data */ var digestSHA1 = (function () // { // Create a buffer for each 80 word block. var state, block = new Uint32Array(80); function common(a, e, w, k, f) { return (f + e + w + k + ((a << 5) | (a >>> 27))) >>> 0; } function f1(a, b, c, d, e, w) { return common(a, e, w, 0x5A827999, d ^ (b & (c ^ d))); } function f2(a, b, c, d, e, w) { return common(a, e, w, 0x6ED9EBA1, b ^ c ^ d); } function f3(a, b, c, d, e, w) { return common(a, e, w, 0x8F1BBCDC, (b & c) | (d & (b | c))); } function f4(a, b, c, d, e, w) { return common(a, e, w, 0xCA62C1D6, b ^ c ^ d); } function cycle(state, block) { var a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; // Partially unroll loops so we don't have to shift variables. var fn = f1; for (var i = 0; i < 80; i += 5) { if (i === 20) { fn = f2; } else if (i === 40) { fn = f3; } else if (i === 60) { fn = f4; } e = fn(a, b, c, d, e, block[i]); b = ((b << 30) | (b >>> 2)) >>> 0; d = fn(e, a, b, c, d, block[i + 1]); a = ((a << 30) | (a >>> 2)) >>> 0; c = fn(d, e, a, b, c, block[i + 2]); e = ((e << 30) | (e >>> 2)) >>> 0; b = fn(c, d, e, a, b, block[i + 3]); d = ((d << 30) | (d >>> 2)) >>> 0; a = fn(b, c, d, e, a, block[i + 4]); c = ((c << 30) | (c >>> 2)) >>> 0; } state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; } // Swap bytes for 32bits word function swap32(b) { return ((b & 0xff) << 24) | ((b & 0xff00) << 8) | ((b >> 8) & 0xff00) | ((b >> 24) & 0xff); } // input is a Uint8Array bitstream of the data return function (data) { var d = new Uint8Array(buffer(data)), dlen = d.length; // Pad the input string length. var len = dlen + 9; if (len % 64) { len += 64 - (len % 64); } state = new Uint32Array(5); state[0] = 0x67452301; state[1] = 0xefcdab89; state[2] = 0x98badcfe; state[3] = 0x10325476; state[4] = 0xc3d2e1f0; for (var ofs = 0; ofs < len; ofs += 64) { // Copy input to block and write padding as needed for (var i = 0; i < 64; i++) { var b = 0, o = ofs + i; if (o < dlen) { b = d[o]; } else if (o === dlen) { b = 0x80; } else { // Write original bit length as a 64bit big-endian integer to the end. var x = len - o - 1; if (x >= 0 && x < 4) { b = (dlen << 3 >>> (x * 8)) & 0xff; } } // Interpret the input bytes as big-endian per the spec if (i % 4 === 0) { block[i >> 2] = b << 24; } else { block[i >> 2] |= b << ((3 - (i % 4)) * 8); } } // Extend the block for (var i = 16; i < 80; i++) { var w = block[i - 3] ^ block[i - 8] ^ block[i - 14] ^ block[i - 16]; block[i] = (w << 1) | (w >>> 31); } cycle(state, block); } // Swap the bytes around since they are big endian internally for (var i = 0; i < 5; i++) state[i] = swap32(state[i]); return state.buffer; }; } // )(); /** * Algorithm name GOST R 34.11-HMAC

* * HMAC with the specified hash function. * @memberOf GostDigest * @method sign * @instance * @param {ArrayBuffer} key The key for HMAC. * @param {Hash} data Data */ function signHMAC(key, data) // { // GOST R 34.11-94 - B=32b, L=32b // GOST R 34.11-256 - B=64b, L=32b // GOST R 34.11-512 - B=64b, L=64b var b = (this.digest === digest94) ? 32 : 64, l = this.bitLength / 8, k = buffer(key), d = buffer(data), k0; if (k.byteLength === b) k0 = new Uint8Array(k); else { var k0 = new Uint8Array(b); if (k.byteLength > b) { k0.set(new Uint8Array(this.digest(k))); } else { k0.set(new Uint8Array(k)); } } var s0 = new Uint8Array(b + d.byteLength), s1 = new Uint8Array(b + l); for (var i = 0; i < b; i++) { s0[i] = k0[i] ^ 0x36; s1[i] = k0[i] ^ 0x5C; } s0.set(new Uint8Array(d), b); s1.set(new Uint8Array(this.digest(s0)), b); return this.digest(s1); } // /** * Algorithm name GOST R 34.11-HMAC

* * Verify HMAC based on GOST R 34.11 hash * * @memberOf GostDigest * @method verify * @instance * @param {(ArrayBuffer|TypedArray)} key Key which used for HMAC generation * @param {(ArrayBuffer|TypedArray)} signature generated HMAC * @param {(ArrayBuffer|TypedArray)} data Data * @returns {boolean} HMAC verified = true */ function verifyHMAC(key, signature, data) // { var hmac = new Uint8Array(this.sign(key, data)), test = new Uint8Array(signature); if (hmac.length !== test.length) return false; for (var i = 0, n = hmac.length; i < n; i++) if (hmac[i] !== test[i]) return false; return true; } // /** * Algorithm name GOST R 34.11-KDF

* * Simple generate key 256/512 bit random seed for derivation algorithms * * @memberOf GostDigest * @method generateKey * @instance * @returns {ArrayBuffer} Generated key */ function generateKey() // { return getSeed(this.bitLength).buffer; } // /** * Algorithm name GOST R 34.11-PFXKDF

* * Derive bits from password (PKCS12 mode) *
    *
  • algorithm.salt - random value, salt
  • *
  • algorithm.iterations - number of iterations
  • *
* @memberOf GostDigest * @method deriveBits * @instance * @param {ArrayBuffer} baseKey - password after UTF-8 decoding * @param {number} length output bit-length * @returns {ArrayBuffer} result */ function deriveBitsPFXKDF(baseKey, length) // { if (length % 8 > 0) throw new DataError('Length must multiple of 8'); var u = this.bitLength / 8, v = (this.digest === digest94) ? 32 : 64, n = length / 8, r = this.iterations; // 1. Construct a string, D (the "diversifier"), by concatenating v/8 // copies of ID. var ID = this.diversifier, D = new Uint8Array(v); for (var i = 0; i < v; i++) D[i] = ID; // 2. Concatenate copies of the salt together to create a string S of // length v(ceiling(s/v)) bits (the final copy of the salt may be // truncated to create S). Note that if the salt is the empty // string, then so is S. var S0 = new Uint8Array(buffer(this.salt)), s = S0.length, slen = v * Math.ceil(s / v), S = new Uint8Array(slen); for (var i = 0; i < slen; i++) S[i] = S0[i % s]; // 3. Concatenate copies of the password together to create a string P // of length v(ceiling(p/v)) bits (the final copy of the password // may be truncated to create P). Note that if the password is the // empty string, then so is P. var P0 = new Uint8Array(buffer(baseKey)), p = P0.length, plen = v * Math.ceil(p / v), P = new Uint8Array(plen); for (var i = 0; i < plen; i++) P[i] = P0[i % p]; // 4. Set I=S||P to be the concatenation of S and P. var I = new Uint8Array(slen + plen); arraycopy(S, 0, I, 0, slen); arraycopy(P, 0, I, slen, plen); // 5. Set c=ceiling(n/u). var c = Math.ceil(n / u); // 6. For i=1, 2, ..., c, do the following: var A = new Uint8Array(c * u); for (var i = 0; i < c; i++) { // A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1, // H(H(H(... H(D||I)))) var H = new Uint8Array(v + slen + plen); arraycopy(D, 0, H, 0, v); arraycopy(I, 0, H, v, slen + plen); for (var j = 0; j < r; j++) H = new Uint8Array(this.digest(H)); arraycopy(H, 0, A, i * u, u); // B. Concatenate copies of Ai to create a string B of length v // bits (the final copy of Ai may be truncated to create B). var B = new Uint8Array(v); for (var j = 0; j < v; j++) B[j] = H[j % u]; // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit // blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by // setting I_j=(I_j+B+1) mod 2^v for each j. var k = (slen + plen) / v; for (j = 0; j < k; j++) { var cf = 1, w; for (var l = v - 1; l >= 0; --l) { w = I[v * j + l] + B[l] + cf; cf = w >>> 8; I[v * j + l] = w & 0xff; } } } // 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom // bit string, A. // 8. Use the first n bits of A as the output of this entire process. var R = new Uint8Array(n); arraycopy(A, 0, R, 0, n); return R.buffer; } // /** * Algorithm name GOST R 34.11-KDF

* * Derive bits for KEK deversification in 34.10-2012 algorithm * KDF(KEK, UKM, label) = HMAC256 (KEK, 0x01|label|0x00|UKM|0x01|0x00) * Default label = 0x26|0xBD|0xB8|0x78 * * @memberOf GostDigest * @method deriveBits * @instance * @param {(ArrayBuffer|TypedArray)} baseKey base key for deriviation * @param {number} length output bit-length * @returns {ArrayBuffer} result */ function deriveBitsKDF(baseKey, length) // { if (length % 8 > 0) throw new DataError('Length must be multiple of 8'); var rlen = length / 8, label, context = new Uint8Array(buffer(this.context)), blen = this.bitLength / 8, n = Math.ceil(rlen / blen); if (this.label) label = new Uint8Array(buffer(this.label)); else label = new Uint8Array([0x26, 0xBD, 0xB8, 0x78]); var result = new Uint8Array(rlen); for (var i = 0; i < n; i++) { var data = new Uint8Array(label.length + context.length + 4); data[0] = i + 1; data.set(label, 1); data[label.length + 1] = 0x00; data.set(context, label.length + 2); data[data.length - 2] = length >>> 8; data[data.length - 1] = length & 0xff; result.set(new Uint8Array(signHMAC.call(this, baseKey, data), 0, i < n - 1 ? blen : rlen - i * blen), i * blen); } return result.buffer; } // /** * Algorithm name GOST R 34.11-PBKDF1

* * Derive bits from password *
    *
  • algorithm.salt - random value, salt
  • *
  • algorithm.iterations - number of iterations
  • *
* @memberOf GostDigest * @method deriveBits * @instance * @param {ArrayBuffer} baseKey - password after UTF-8 decoding * @param {number} length output bit-length * @returns {ArrayBuffer} result */ function deriveBitsPBKDF1(baseKey, length) // { if (length < this.bitLength / 2 || length % 8 > 0) throw new DataError('Length must be more than ' + this.bitLength / 2 + ' bits and multiple of 8'); var hLen = this.bitLength / 8, dkLen = length / 8, c = this.iterations, P = new Uint8Array(buffer(baseKey)), S = new Uint8Array(buffer(this.salt)), slen = S.length, plen = P.length, T = new Uint8Array(plen + slen), DK = new Uint8Array(dkLen); if (dkLen > hLen) throw new DataError('Invalid parameters: Length value'); arraycopy(P, 0, T, 0, plen); arraycopy(S, 0, T, plen, slen); for (var i = 0; i < c; i++) T = new Uint8Array(this.digest(T)); arraycopy(T, 0, DK, 0, dkLen); return DK.buffer; } // /** * Algorithm name GOST R 34.11-PBKDF2

* * Derive bits from password *
    *
  • algorithm.salt - random value, salt
  • *
  • algorithm.iterations - number of iterations
  • *
* @memberOf GostDigest * @method deriveBits * @instance * @param {ArrayBuffer} baseKey - password after UTF-8 decoding * @param {number} length output bit-length * @returns {ArrayBuffer} result */ function deriveBitsPBKDF2(baseKey, length) // { var diversifier = this.diversifier || 1; // For PKCS12 MAC required 3*length length = length * diversifier; if (length < this.bitLength / 2 || length % 8 > 0) throw new DataError('Length must be more than ' + this.bitLength / 2 + ' bits and multiple of 8'); var hLen = this.bitLength / 8, dkLen = length / 8, c = this.iterations, P = new Uint8Array(buffer(baseKey)), S = new Uint8Array(buffer(this.salt)); var slen = S.byteLength, data = new Uint8Array(slen + 4); arraycopy(S, 0, data, 0, slen); if (dkLen > (0xffffffff - 1) * 32) throw new DataError('Invalid parameters: Length value'); var n = Math.ceil(dkLen / hLen), DK = new Uint8Array(dkLen); for (var i = 1; i <= n; i++) { data[slen] = i >>> 24 & 0xff; data[slen + 1] = i >>> 16 & 0xff; data[slen + 2] = i >>> 8 & 0xff; data[slen + 3] = i & 0xff; var U = new Uint8Array(signHMAC.call(this, P, data)), Z = U; for (var j = 1; j < c; j++) { U = new Uint8Array(signHMAC.call(this, P, U)); for (var k = 0; k < hLen; k++) Z[k] = U[k] ^ Z[k]; } var ofs = (i - 1) * hLen; arraycopy(Z, 0, DK, ofs, Math.min(hLen, dkLen - ofs)); } if (diversifier > 1) { var rLen = dkLen / diversifier, R = new Uint8Array(rLen); arraycopy(DK, dkLen - rLen, R, 0, rLen); return R.buffer; } else return DK.buffer; } // /** * Algorithm name GOST R 34.11-CPKDF

* * Derive bits from password. CryptoPro algorithm *
    *
  • algorithm.salt - random value, salt
  • *
  • algorithm.iterations - number of iterations
  • *
* @memberOf GostDigest * @method deriveBits * @instance * @param {ArrayBuffer} baseKey - password after UTF-8 decoding * @param {number} length output bit-length * @returns {ArrayBuffer} result */ function deriveBitsCP(baseKey, length) { if (length > this.bitLength || length % 8 > 0) throw new DataError('Length can\'t be more than ' + this.bitLength + ' bits and multiple of 8'); // GOST R 34.11-94 - B=32b, L=32b // GOST R 34.11-256 - B=64b, L=32b // GOST R 34.11-512 - B=64b, L=64b var b = (this.digest === digest94) ? 32 : 64, l = this.bitLength / 8, p = baseKey && baseKey.byteLength > 0 ? new Uint8Array(buffer(baseKey)) : false, plen = p ? p.length : 0, iterations = this.iterations, salt = new Uint8Array(buffer(this.salt)), slen = salt.length, d = new Uint8Array(slen + plen); arraycopy(salt, 0, d, 0, slen); if (p) arraycopy(p, 0, d, slen, plen); var h = new Uint8Array(this.digest(d)), k = new Uint8Array(b), s0 = new Uint8Array(b), s1 = new Uint8Array(b); var c = 'DENEFH028.760246785.IUEFHWUIO.EF'; for (var i = 0; i < c.length; i++) k[i] = c.charCodeAt(i); d = new Uint8Array(2 * (b + l)); for (var j = 0; j < iterations; j++) { for (var i = 0; i < b; i++) { s0[i] = k[i] ^ 0x36; s1[i] = k[i] ^ 0x5C; k[i] = 0; } arraycopy(s0, 0, d, 0, b); arraycopy(h, 0, d, b, l); arraycopy(s1, 0, d, b + l, b); arraycopy(h, 0, d, b + l + b, l); arraycopy(new Uint8Array(this.digest(d)), 0, k, 0, l); } for (var i = 0; i < l; i++) { s0[i] = k[i] ^ 0x36; s1[i] = k[i] ^ 0x5C; k[i] = 0; } d = new Uint8Array(2 * l + slen + plen); arraycopy(s0, 0, d, 0, l); arraycopy(salt, 0, d, l, slen); arraycopy(s1, 0, d, l + slen, l); if (p) arraycopy(p, 0, d, l + slen + l, plen); h = this.digest(this.digest(d)); if (length === this.bitLength) return h; else { var rlen = length / 8, r = new Uint8Array(rlen); arraycopy(h, 0, r, 0, rlen); return r.buffer; } } /** * Algorithm name GOST R 34.11-KDF or GOST R 34.11-PBKDF2 or other

* * Derive key from derive bits subset * * @memberOf GostDigest * @method deriveKey * @instance * @param {ArrayBuffer} baseKey * @returns {ArrayBuffer} */ function deriveKey(baseKey) // { return this.deriveBits(baseKey, this.keySize * 8); } // /** * GOST R 34.11 Algorithm

* * References: {@link http://tools.ietf.org/html/rfc6986} and {@link http://tools.ietf.org/html/rfc5831}

* * Normalized algorithm identifier common parameters: * *
    *
  • name Algorithm name 'GOST R 34.11'
  • *
  • version Algorithm version *
      *
    • 1994 old-style 256 bits digest based on GOST 28147-89
    • *
    • 2012 256 ro 512 bits digest algorithm "Streebog" GOST R 34.11-2012 (default)
    • *
    *
  • *
  • length Digest length *
      *
    • 256 256 bits digest
    • *
    • 512 512 bits digest, valid only for algorithm "Streebog"
    • *
    *
  • *
  • mode Algorithm mode *
      *
    • HASH simple digest mode (default)
    • *
    • HMAC HMAC algorithm based on GOST R 34.11
    • *
    • KDF Derive bits for KEK deversification
    • *
    • PBKDF2 Password based key dirivation algorithms PBKDF2 (based on HMAC)
    • *
    • PFXKDF Password based PFX key dirivation algorithms
    • *
    • CPKDF CpyptoPro Password based key dirivation algorithms
    • *
    *
  • *
  • sBox Paramset sBox for GOST 28147-89. Used only if version = 1994
  • *
* * Supported algorithms, modes and parameters: * *
    *
  • Digest HASH mode (default)
  • *
  • Sign/Verify HMAC modes parameters depends on version and length *
      *
    • version: 1994 HMAC parameters (B = 32, L = 32)
    • *
    • version: 2012, length: 256 HMAC parameters (B = 64, L = 32)
    • *
    • version: 2012, length: 512 HMAC parameters (B = 64, L = 64)
    • *
    *
  • *
  • DeriveBits/DeriveKey KDF mode *
      *
    • context {@link CryptoOperationData} Context of the key derivation
    • *
    • label {@link CryptoOperationData} Label that identifies the purpose for the derived keying material
    • *
    *
  • *
  • DeriveBits/DeriveKey PBKDF2 mode *
      *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, ID=3 - integrity key for MACing
    • *
    *
  • *
  • DeriveBits/DeriveKey PFXKDF mode *
      *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, * ID=2 - IV (Initial Value) for encryption or decryption, ID=3 - integrity key for MACing
    • *
    *
  • *
  • DeriveBits/DeriveKey CPKDF mode *
      *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • *
    *
  • *
* * @class GostDigest * @param {AlgorithmIdentifier} algorithm WebCryptoAPI algorithm identifier */ function GostDigest(algorithm) // { algorithm = algorithm || {}; this.name = (algorithm.name || 'GOST R 34.10') + '-' + ((algorithm.version || 2012) % 100) + ((algorithm.version || 2012) > 1 ? '-' + (algorithm.length || 256) : '') + (((algorithm.mode || 'HASH') !== 'HASH') ? '-' + algorithm.mode : '') + (algorithm.procreator ? '/' + algorithm.procreator : '') + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); // Algorithm procreator this.procreator = algorithm.procreator; // Bit length this.bitLength = algorithm.length || 256; switch (algorithm.version || 2012) { case 1: // SHA-1 this.digest = digestSHA1; this.bitLength = 160; break; case 1994: this.digest = digest94; // Define chiper algorithm this.sBox = (algorithm.sBox || (algorithm.procreator === 'SC' ? 'D-SC' : 'D-A')).toUpperCase(); //if (!GostCipher) // GostCipher = root.GostCipher; if (!GostCipher) throw new NotSupportedError('Object GostCipher not found'); this.cipher = new GostCipher({ name: 'GOST 28147', block: 'ECB', sBox: this.sBox, procreator: this.procreator }); break; case 2012: this.digest = digest2012; break; default: throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported'); } // Key size this.keySize = algorithm.keySize || (algorithm.version <= 2 ? this.bitLength / 8 : 32); switch (algorithm.mode || 'HASH') { case 'HASH': break; case 'HMAC': this.sign = signHMAC; this.verify = verifyHMAC; this.generateKey = generateKey; break; case 'KDF': this.deriveKey = deriveKey; this.deriveBits = deriveBitsKDF; this.label = algorithm.label; this.context = algorithm.context; break; case 'PBKDF2': this.deriveKey = deriveKey; this.deriveBits = deriveBitsPBKDF2; this.generateKey = generateKey; this.salt = algorithm.salt; this.iterations = algorithm.iterations || 2000; this.diversifier = algorithm.diversifier || 1; break; case 'PFXKDF': this.deriveKey = deriveKey; this.deriveBits = deriveBitsPFXKDF; this.generateKey = generateKey; this.salt = algorithm.salt; this.iterations = algorithm.iterations || 2000; this.diversifier = algorithm.diversifier || 1; break; case 'CPKDF': this.deriveKey = deriveKey; this.deriveBits = deriveBitsCP; this.generateKey = generateKey; this.salt = algorithm.salt; this.iterations = algorithm.iterations || 2000; break; default: throw new NotSupportedError('Algorithm mode ' + algorithm.mode + ' not supported'); } } // export default GostDigest; ================================================ FILE: src/core/vendor/gost/gostEngine.mjs ================================================ /** * @file GOST 34.10-2012 signature function with 1024/512 bits digest * @version 1.76 * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved. */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ import GostRandom from './gostRandom.mjs'; import GostCipher from './gostCipher.mjs'; import GostDigest from './gostDigest.mjs'; import GostSign from './gostSign.mjs'; /* * Engine definition base on normalized algorithm identifier * */ // var root = {}; // Define engine function defineEngine(method, algorithm) { if (!algorithm) throw new (root.SyntaxError || Error)('Algorithm not defined'); if (!algorithm.name) throw new (root.SyntaxError || Error)('Algorithm name not defined'); var name = algorithm.name, mode = algorithm.mode; if ((name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2') && (method === 'generateKey' || (mode === 'MAC' && (method === 'sign' || method === 'verify')) || ((mode === 'KW' || mode === 'MASK') && (method === 'wrapKey' || method === 'unwrapKey')) || ((!mode || mode === 'ES') && (method === 'encrypt' || method === 'decrypt')))) { return 'GostCipher'; } else if ((name === 'GOST R 34.11' || name === 'SHA') && (method === 'digest' || (mode === 'HMAC' && (method === 'sign' || method === 'verify' || method === 'generateKey')) || ((mode === 'KDF' || mode === 'PBKDF2' || mode === 'PFXKDF' || mode === 'CPKDF') && (method === 'deriveKey' || method === 'deriveBits' || method === 'generateKey')))) { return 'GostDigest'; } else if (name === 'GOST R 34.10' && (method === 'generateKey' || ((!mode || mode === 'SIGN') && (method === 'sign' || method === 'verify')) || (mode === 'MASK' && (method === 'wrapKey' || method === 'unwrapKey')) || (mode === 'DH' && (method === 'deriveKey' || method === 'deriveBits')))) { return 'GostSign'; } else throw new (root.NotSupportedError || Error)('Algorithm ' + name + '-' + mode + ' is not valid for ' + method); } // /** * Object implements dedicated Web Workers and provide a simple way to create * and run GOST cryptographic algorithms in background thread. * * Object provide interface to GOST low-level cryptogric classes: *
    *
  • GostCipher - implementation of GOST 28147, GOST R 34.12, GOST R 34.13 Encryption algorithms. Reference {@link http://tools.ietf.org/html/rfc5830}
  • *
  • 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}
  • *
  • 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}
  • *
* @namespace gostEngine */ var gostEngine = { /** * gostEngine.execute(algorithm, method, args) Entry point to execution * all low-level GOST cryptographic methods * *
    *
  • Determine the appropriate engine for a given execution method
  • *
  • Create cipher object for determineted engine
  • *
  • Execute method of cipher with given args
  • *
* * @memberOf gostEngine * @param {AlgorithmIndentifier} algorithm Algorithm identifier * @param {string} method Crypto method for execution * @param {Array} args Method arguments (keys, data, additional parameters) * @returns {(CryptoOperationData|Key|KeyPair|boolean)} Result of method execution */ execute: function (algorithm, method, args) // { // Define engine for GOST algorithms var engine = defineEngine(method, algorithm); // Create cipher var cipher = this['get' + engine](algorithm); // Execute method return cipher[method].apply(cipher, args); }, // /** * gostEngine.getGostCipher(algorithm) returns GOST 28147 / GOST R 34.12 cipher instance

* * GOST 28147-89 / GOST R 34.12-15 Encryption Algorithm

* When keys and initialization vectors are converted to/from byte arrays, * little-endian byte order is assumed.

* * Normalized algorithm identifier common parameters: * *
    *
  • name Algorithm name 'GOST 28147' or 'GOST R 34.12'
  • *
  • version Algorithm version, number *
      *
    • 1989 Current version of standard
    • *
    • 2015 New draft version of standard
    • *
    *
  • *
  • length Block length *
      *
    • 64 64 bits length (default)
    • *
    • 128 128 bits length (only for version 2015)
    • *
    *
  • *
  • mode Algorithm mode, string *
      *
    • ES Encryption mode (default)
    • *
    • MAC "imitovstavka" (MAC) mode
    • *
    • KW Key wrapping mode
    • *
    • MASK Key mask mode
    • *
    *
  • *
  • sBox Paramset sBox for GOST 28147-89, string. Used only if version = 1989
  • *
* * Supported algorithms, modes and parameters: * *
    *
  • Encript/Decrypt mode (ES) *
      *
    • block Block mode, string. Default ECB
    • *
    • keyMeshing Key meshing mode, string. Default NO
    • *
    • padding Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others
    • *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • *
    *
  • *
  • Sign/Verify mode (MAC) *
      *
    • macLength Length of mac in bits (default - 32 bits)
    • *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • *
    *
  • *
  • Wrap/Unwrap key mode (KW) *
      *
    • keyWrapping Mode of keywrapping, string. Default NO - standard GOST key wrapping
    • *
    • ukm {@link CryptoOperationData} User key material. Default - random generated value
    • *
    *
  • *
  • Wrap/Unwrap key mode (MASK)
  • *
* * Supported paramters values: * *
    *
  • Block modes (parameter 'block') *
      *
    • ECB "prostaya zamena" (ECB) mode (default)
    • *
    • CFB "gammirovanie s obratnoj svyaziyu" (64-bit CFB) mode
    • *
    • CTR "gammirovanie" (counter) mode
    • *
    • CBC Cipher-Block-Chaining (CBC) mode
    • *
    *
  • *
  • Key meshing modes (parameter 'keyMeshing') *
      *
    • NO No key wrapping (default)
    • *
    • CP CryptoPor Key key meshing
    • *
    *
  • *
  • Padding modes (parameter 'padding') *
      *
    • NO No padding only for CFB and CTR modes
    • *
    • PKCS5 PKCS#5 padding mode
    • *
    • ZERO Zero bits padding mode
    • *
    • RANDOM Random bits padding mode
    • *
    *
  • *
  • Wrapping key modes (parameter 'keyWrapping') *
      *
    • NO Ref. rfc4357 6.1 GOST 28147-89 Key wrapping
    • *
    • CP CryptoPro Key wrapping mode
    • *
    • SC SignalCom Key wrapping mode
    • *
    *
  • *
* * @memberOf gostEngine * @param {AlgorithmIndentifier} algorithm Algorithm identifier * @returns {GostCipher} Instance of GostCipher */ getGostCipher: function (algorithm) // { return new (GostCipher || (GostCipher = root.GostCipher))(algorithm); }, // /** * gostEngine.getGostDigest(algorithm) returns GOST R 34.11 cipher instance

* * Normalized algorithm identifier common parameters: * *
    *
  • name Algorithm name 'GOST R 34.11'
  • *
  • version Algorithm version *
      *
    • 1994 old-style 256 bits digest based on GOST 28147-89
    • *
    • 2012 256 ro 512 bits digest algorithm "Streebog" GOST R 34.11-2012 (default)
    • *
    *
  • *
  • length Digest length *
      *
    • 256 256 bits digest
    • *
    • 512 512 bits digest, valid only for algorithm "Streebog"
    • *
    *
  • *
  • mode Algorithm mode *
      *
    • HASH simple digest mode (default)
    • *
    • HMAC HMAC algorithm based on GOST R 34.11
    • *
    • KDF Derive bits for KEK deversification
    • *
    • PBKDF2 Password based key dirivation algorithms PBKDF2 (based on HMAC)
    • *
    • PFXKDF PFX key dirivation algorithms PFXKDF
    • *
    • CPKDF CryptoPro Password based key dirivation algorithms
    • *
    *
  • *
  • sBox Paramset sBox for GOST 28147-89. Used only if version = 1994
  • *
* * Supported algorithms, modes and parameters: * *
    *
  • Digest HASH mode (default)
  • *
  • Sign/Verify HMAC modes parameters depends on version and length *
      *
    • version: 1994 HMAC parameters (B = 32, L = 32)
    • *
    • version: 2012, length: 256 HMAC parameters (B = 64, L = 32)
    • *
    • version: 2012, length: 512 HMAC parameters (B = 64, L = 64)
    • *
    *
  • *
  • DeriveBits/DeriveKey KDF mode *
      *
    • context {@link CryptoOperationData} Context of the key derivation
    • *
    • label {@link CryptoOperationData} Label that identifies the purpose for the derived keying material
    • *
    *
  • *
  • DeriveBits/DeriveKey PBKDF2 mode *
      *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • *
    *
  • *
  • DeriveBits/DeriveKey PFXKDF mode *
      *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, * ID=2 - IV (Initial Value) for encryption or decryption, ID=3 - integrity key for MACing
    • *
    *
  • *
  • DeriveBits/DeriveKey CPKDF mode *
      *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • *
    *
  • *
* * @memberOf gostEngine * @param {AlgorithmIndentifier} algorithm Algorithm identifier * @returns {GostDigest} Instance of GostDigest */ getGostDigest: function (algorithm) // { return new (GostDigest || (GostDigest = root.GostDigest))(algorithm); }, // /** * gostEngine.getGostSign(algorithm) returns GOST R 34.10 cipher instance

* * Normalized algorithm identifier common parameters: * *
    *
  • name Algorithm name 'GOST R 34.10'
  • *
  • version Algorithm version *
      *
    • 1994 - Old-style GOST R 34.10-94 ExpMod algorithm with GOST R 34.11-94 hash
    • *
    • 2001 - GOST R 34.10-2001 Eliptic curve algorithm with old GOST R 34.11-94 hash
    • *
    • 2012 - GOST R 34.10-2012 Eliptic curve algorithm with GOST R 34.11-12 hash, default mode
    • *
    *
  • *
  • length Length of hash and signature. Key length == hash length for EC algorithms and 2 * hash length for ExpMod algorithm *
      *
    • GOST R 34.10-256 - 256 bits digest, default mode
    • *
    • GOST R 34.10-512 - 512 bits digest only for GOST R 34.11-2012 hash
    • *
    *
  • *
  • mode Algorithm mode *
      *
    • SIGN Digital signature mode (default)
    • *
    • DH Diffie-Hellman key generation and key agreement mode
    • *
    • MASK Key mask mode
    • *
    *
  • *
  • sBox Paramset sBox for GOST 34.11-94. Used only if version = 1994 or 2001
  • *
* * Supported algorithms, modes and parameters: * *
    *
  • Sign/Verify mode (SIGN)
  • *
  • Wrap/Unwrap mode (MASK)
  • *
  • DeriveKey/DeriveBits mode (DH) *
      *
    • {@link CryptoOperationData} ukm User key material. Default - random generated value
    • *
    • {@link CryptoOperationData} public The peer's EC public key data
    • *
    *
  • *
  • GenerateKey mode (SIGN and DH and MASK) version = 1994 *
      *
    • namedParam Paramset for key generation algorithm. If specified no additianal parameters required
    • *
    * Additional parameters, if namedParam not specified *
      *
    • modulusLength Bit length of p (512 or 1024 bits). Default = 1024
    • *
    • p {@link CryptoOperationData} Modulus, prime number, 2^(t-1) *
    • q {@link CryptoOperationData} Order of cyclic group, prime number, 2^254 *
    • a {@link CryptoOperationData} Generator, integer, 1 *
    *
  • *
  • GenerateKey mode (SIGN and DH and MASK) version = 2001 or 2012 *
      *
    • namedCurve Paramset for key generation algorithm. If specified no additianal parameters required
    • *
    * Additional EC parameters, if namedCurve not specified *
      *
    • p {@link CryptoOperationData} Prime number - elliptic curve modulus
    • *
    • a {@link CryptoOperationData} Coefficients a of the elliptic curve E
    • *
    • b {@link CryptoOperationData} Coefficients b of the elliptic curve E
    • *
    • q {@link CryptoOperationData} Prime number - order of cyclic group
    • *
    • x {@link CryptoOperationData} Base point p x-coordinate
    • *
    • y {@link CryptoOperationData} Base point p y-coordinate
    • *
    *
  • *
* * @memberOf gostEngine * @param {AlgorithmIndentifier} algorithm Algorithm identifier * @returns {GostSign} Instance of GostSign */ getGostSign: function (algorithm) // { return new (GostSign || (GostSign = root.GostSign))(algorithm); } // }; /* * Worker method execution * */ // // Worker for gostCripto method execution if (root.importScripts) { /** * Method called when {@link SubtleCrypto} calls its own postMessage() * method with data parameter: algorithm, method and arg.
* Call method execute and postMessage() results to onmessage event handler * in the main process.
* If error occurred onerror event handler executed in main process. * * @memberOf gostEngine * @name onmessage * @param {MessageEvent} event Message event with data {algorithm, method, args} */ root.onmessage = function (event) { try { postMessage({ id: event.data.id, result: gostEngine.execute(event.data.algorithm, event.data.method, event.data.args)}); } catch (e) { postMessage({ id: event.data.id, error: e.message }); } }; } else { // Load dependens var baseUrl = '', nameSuffix = ''; // Try to define from DOM model if (typeof document !== 'undefined') { (function () { var regs = /^(.*)gostCrypto(.*)\.js$/i; var list = document.querySelectorAll('script'); for (var i = 0, n = list.length; i < n; i++) { var value = list[i].getAttribute('src'); var test = regs.exec(value); if (test) { baseUrl = test[1]; nameSuffix = test[2]; } } })(); } // Local importScripts procedure for include dependens var importScripts = function () { for (var i = 0, n = arguments.length; i < n; i++) { var name = arguments[i].split('.'), src = baseUrl + name[0] + nameSuffix + '.' + name[1]; var el = document.querySelector('script[src="' + src + '"]'); if (!el) { el = document.createElement('script'); el.setAttribute('src', src); document.head.appendChild(el); } } }; // Import engines if (!GostRandom) importScripts('gostRandom.js'); if (!GostCipher) importScripts('gostCipher.js'); if (!GostDigest) importScripts('gostDigest.js'); if (!GostSign) importScripts('gostSign.js'); } //
export default gostEngine; ================================================ FILE: src/core/vendor/gost/gostRandom.mjs ================================================ /** * Implementation Web Crypto random generatore for GOST algorithms * 1.76 * 2014-2016, Rudolf Nickolaev. All rights reserved. * * Exported for CyberChef by mshwed [m@ttshwed.com] */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ import crypto from 'crypto'; /** * The gostCrypto provide general purpose cryptographic functionality for * GOST standards including a cryptographically strong pseudo-random number * generator seeded with truly random values. * * @Class GostRandom * */ // var root = {}; var rootCrypto = crypto; var TypeMismatchError = Error; var QuotaExceededError = Error; // Initialize mouse and time counters for random generator var randomRing = { seed: new Uint8Array(1024), getIndex: 0, setIndex: 0, set: function (x) { if (this.setIndex >= 1024) this.setIndex = 0; this.seed[this.setIndex++] = x; }, get: function () { if (this.getIndex >= 1024) this.getIndex = 0; return this.seed[this.getIndex++]; } }; if (typeof document !== 'undefined') { try { // Mouse move event to fill random array document.addEventListener('mousemove', function (e) { randomRing.set((Date.now() & 255) ^ ((e.clientX || e.pageX) & 255) ^ ((e.clientY || e.pageY) & 255)); }, false); } catch (e) { } try { // Keypress event to fill random array document.addEventListener('keydown', function (e) { randomRing.set((Date.now() & 255) ^ (e.keyCode & 255)); }, false); } catch (e) { } } // function GostRandom() { } /** * The getRandomValues method generates cryptographically random values.

* * Random generator based on JavaScript Web Crypto random genereator * (if it is possible) or Math.random mixed with time and parameters of * mouse and keyboard events * * @memberOf GostRandom * @param {(ArrayBuffer|ArrayBufferView)} array Destination buffer for random data */ GostRandom.prototype.getRandomValues = function (array) // { if (!array.byteLength) throw new TypeMismatchError('Array is not of an integer type (Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, or Uint32Array)'); if (array.byteLength > 65536) throw new QuotaExceededError('Byte length of array can\'t be greate then 65536'); var u8 = new Uint8Array(array.buffer, array.byteOffset, array.byteLength); if (rootCrypto && rootCrypto.getRandomValues) { // Native window cryptographic interface rootCrypto.getRandomValues(u8); } else { // Standard Javascript method for (var i = 0, n = u8.length; i < n; i++) u8[i] = Math.floor(256 * Math.random()) & 255; } // Mix bio randomizator for (var i = 0, n = u8.length; i < n; i++) u8[i] = u8[i] ^ randomRing.get(); return array; }; // export default GostRandom; ================================================ FILE: src/core/vendor/gost/gostSign.mjs ================================================ /** * @file GOST 34.10-2012 signature function with 1024/512 bits digest * @version 1.76 * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved. */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Used library JSBN http://www-cs-students.stanford.edu/~tjw/jsbn/ * Copyright (c) 2003-2005 Tom Wu (tjw@cs.Stanford.EDU) * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ import GostRandom from './gostRandom.mjs'; import GostDigest from './gostDigest.mjs'; import crypto from 'crypto'; /* * Predefined curves and params collection * * http://tools.ietf.org/html/rfc5832 * http://tools.ietf.org/html/rfc7091 * http://tools.ietf.org/html/rfc4357 * */ // var root = {}; var rootCrypto = crypto; var CryptoOperationData = ArrayBuffer; var OperationError = Error, DataError = Error, NotSupportedError = Error; // Predefined named curve collection var ECGostParams = { 'S-256-TEST': { a: 7, b: '0x5FBFF498AA938CE739B8E022FBAFEF40563F6E6A3472FC2A514C0CE9DAE23B7E', p: '0x8000000000000000000000000000000000000000000000000000000000000431', q: '0x8000000000000000000000000000000150FE8A1892976154C59CFC193ACCF5B3', x: 2, y: '0x8E2A8A0E65147D4BD6316030E16D19C85C97F0A9CA267122B96ABBCEA7E8FC8' }, 'S-256-A': { a: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94', b: 166, p: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97', q: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893', x: 1, y: '0x8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14' }, 'S-256-B': { a: '0x8000000000000000000000000000000000000000000000000000000000000C96', b: '0x3E1AF419A269A5F866A7D3C25C3DF80AE979259373FF2B182F49D4CE7E1BBC8B', p: '0x8000000000000000000000000000000000000000000000000000000000000C99', q: '0x800000000000000000000000000000015F700CFFF1A624E5E497161BCC8A198F', x: 1, y: '0x3FA8124359F96680B83D1C3EB2C070E5C545C9858D03ECFB744BF8D717717EFC' }, 'S-256-C': { a: '0x9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D7598', b: 32858, p: '0x9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D759B', q: '0x9B9F605F5A858107AB1EC85E6B41C8AA582CA3511EDDFB74F02F3A6598980BB9', x: 0, y: '0x41ECE55743711A8C3CBF3783CD08C0EE4D4DC440D4641A8F366E550DFDB3BB67' }, 'P-256': { p: '0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', a: '0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', b: '0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', x: '0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', y: '0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', q: '0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551' }, 'T-512-TEST': { a: 7, b: '0x1CFF0806A31116DA29D8CFA54E57EB748BC5F377E49400FDD788B649ECA1AC4361834013B2AD7322480A89CA58E0CF74BC9E540C2ADD6897FAD0A3084F302ADC', p: '0x4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DF1D852741AF4704A0458047E80E4546D35B8336FAC224DD81664BBF528BE6373', q: '0x4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DA82F2D7ECB1DBAC719905C5EECC423F1D86E25EDBE23C595D644AAF187E6E6DF', x: '0x24D19CC64572EE30F396BF6EBBFD7A6C5213B3B3D7057CC825F91093A68CD762FD60611262CD838DC6B60AA7EEE804E28BC849977FAC33B4B530F1B120248A9A', y: '0x2BB312A43BD2CE6E0D020613C857ACDDCFBF061E91E5F2C3F32447C259F39B2C83AB156D77F1496BF7EB3351E1EE4E43DC1A18B91B24640B6DBB92CB1ADD371E' }, 'T-512-A': { p: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7', a: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4', b: '0xE8C2505DEDFC86DDC1BD0B2B6667F1DA34B82574761CB0E879BD081CFD0B6265EE3CB090F30D27614CB4574010DA90DD862EF9D4EBEE4761503190785A71C760', q: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275', x: 3, y: '0x7503CFE87A836AE3A61B8816E25450E6CE5E1C93ACF1ABC1778064FDCBEFA921DF1626BE4FD036E93D75E6A50E3A41E98028FE5FC235F5B889A589CB5215F2A4' }, 'T-512-B': { p: '0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006F', a: '0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006C', b: '0x687D1B459DC841457E3E06CF6F5E2517B97C7D614AF138BCBF85DC806C4B289F3E965D2DB1416D217F8B276FAD1AB69C50F78BEE1FA3106EFB8CCBC7C5140116', q: '0x800000000000000000000000000000000000000000000000000000000000000149A1EC142565A545ACFDB77BD9D40CFA8B996712101BEA0EC6346C54374F25BD', x: 2, y: '0x1A8F7EDA389B094C2C071E3647A8940F3C123B697578C213BE6DD9E6C8EC7335DCB228FD1EDF4A39152CBCAAF8C0398828041055F94CEEEC7E21340780FE41BD' } }; ECGostParams['X-256-A'] = ECGostParams['S-256-A']; ECGostParams['X-256-B'] = ECGostParams['S-256-C']; ECGostParams['T-256-TEST'] = ECGostParams['S-256-TEST']; ECGostParams['T-256-A'] = ECGostParams['S-256-A']; ECGostParams['T-256-B'] = ECGostParams['S-256-B']; ECGostParams['T-256-C'] = ECGostParams['S-256-C']; var GostParams = { 'S-TEST': { modulusLength: 512, // bit length of p (512 or 1024 bits) p: '0xEE8172AE8996608FB69359B89EB82A69854510E2977A4D63BC97322CE5DC3386EA0A12B343E9190F23177539845839786BB0C345D165976EF2195EC9B1C379E3', q: '0x98915E7EC8265EDFCDA31E88F24809DDB064BDC7285DD50D7289F0AC6F49DD2D', a: '0x9e96031500c8774a869582d4afde2127afad2538b4b6270a6f7c8837b50d50f206755984a49e509304d648be2ab5aab18ebe2cd46ac3d8495b142aa6ce23e21c' }, 'S-A': { modulusLength: 1024, p: '0xB4E25EFB018E3C8B87505E2A67553C5EDC56C2914B7E4F89D23F03F03377E70A2903489DD60E78418D3D851EDB5317C4871E40B04228C3B7902963C4B7D85D52B9AA88F2AFDBEB28DA8869D6DF846A1D98924E925561BD69300B9DDD05D247B5922D967CBB02671881C57D10E5EF72D3E6DAD4223DC82AA1F7D0294651A480DF', q: '0x972432A437178B30BD96195B773789AB2FFF15594B176DD175B63256EE5AF2CF', a: '0x8FD36731237654BBE41F5F1F8453E71CA414FFC22C25D915309E5D2E62A2A26C7111F3FC79568DAFA028042FE1A52A0489805C0DE9A1A469C844C7CABBEE625C3078888C1D85EEA883F1AD5BC4E6776E8E1A0750912DF64F79956499F1E182475B0B60E2632ADCD8CF94E9C54FD1F3B109D81F00BF2AB8CB862ADF7D40B9369A' }, 'S-B': { modulusLength: 1024, p: '0xC6971FC57524B30C9018C5E621DE15499736854F56A6F8AEE65A7A404632B1BCF0349FFCAFCB0A103177971FC1612ADCDB8C8CC938C70225C8FD12AFF01B1D064E0AD6FDE6AB9159166CB9F2FC171D92F0CC7B6A6B2CD7FA342ACBE2C9315A42D576B1ECCE77A963157F3D0BD96A8EB0B0F3502AD238101B05116334F1E5B7AB', q: '0xB09D634C10899CD7D4C3A7657403E05810B07C61A688BAB2C37F475E308B0607', a: '0x3D26B467D94A3FFC9D71BF8DB8934084137264F3C2E9EB16DCA214B8BC7C872485336744934FD2EF5943F9ED0B745B90AA3EC8D70CDC91682478B664A2E1F8FB56CEF2972FEE7EDB084AF746419B854FAD02CC3E3646FF2E1A18DD4BEB3C44F7F2745588029649674546CC9187C207FB8F2CECE8E2293F68395C4704AF04BAB5' }, 'S-C': { modulusLength: 1024, p: '0x9D88E6D7FE3313BD2E745C7CDD2AB9EE4AF3C8899E847DE74A33783EA68BC30588BA1F738C6AAF8AB350531F1854C3837CC3C860FFD7E2E106C3F63B3D8A4C034CE73942A6C3D585B599CF695ED7A3C4A93B2B947B7157BB1A1C043AB41EC8566C6145E938A611906DE0D32E562494569D7E999A0DDA5C879BDD91FE124DF1E9', q: '0xFADD197ABD19A1B4653EECF7ECA4D6A22B1F7F893B641F901641FBB555354FAF', a: '0x7447ED7156310599070B12609947A5C8C8A8625CF1CF252B407B331F93D639DDD1BA392656DECA992DD035354329A1E95A6E32D6F47882D960B8F10ACAFF796D13CD9611F853DAB6D2623483E46788708493937A1A29442598AEC2E0742022563440FE9C18740ECE6765AC05FAF024A64B026E7E408840819E962E7E5F401AE3' }, 'S-D': { modulusLength: 1024, p: '0x80F102D32B0FD167D069C27A307ADAD2C466091904DBAA55D5B8CC7026F2F7A1919B890CB652C40E054E1E9306735B43D7B279EDDF9102001CD9E1A831FE8A163EED89AB07CF2ABE8242AC9DEDDDBF98D62CDDD1EA4F5F15D3A42A6677BDD293B24260C0F27C0F1D15948614D567B66FA902BAA11A69AE3BCEADBB83E399C9B5', q: '0xF0F544C418AAC234F683F033511B65C21651A6078BDA2D69BB9F732867502149', a: '0x6BCC0B4FADB3889C1E06ADD23CC09B8AB6ECDEDF73F04632595EE4250005D6AF5F5ADE44CB1E26E6263C672347CFA26F9E9393681E6B759733784CDE5DBD9A14A39369DFD99FA85CC0D10241C4010343F34A91393A706CF12677CBFA1F578D6B6CFBE8A1242CFCC94B3B653A476E145E3862C18CC3FED8257CFEF74CDB205BF1' }, 'X-A': { modulusLength: 1024, p: '0xCA3B3F2EEE9FD46317D49595A9E7518E6C63D8F4EB4D22D10D28AF0B8839F079F8289E603B03530784B9BB5A1E76859E4850C670C7B71C0DF84CA3E0D6C177FE9F78A9D8433230A883CD82A2B2B5C7A3306980278570CDB79BF01074A69C9623348824B0C53791D53C6A78CAB69E1CFB28368611A397F50F541E16DB348DBE5F', q: '0xCAE4D85F80C147704B0CA48E85FB00A9057AA4ACC44668E17F1996D7152690D9', a: '0xBE27D652F2F1E339DA734211B85B06AE4DE236AA8FBEEB3F1ADCC52CD43853777E834A6A518138678A8ADBD3A55C70A7EAB1BA7A0719548677AAF4E609FFB47F6B9D7E45B0D06D83D7ADC53310ABD85783E7317F7EC73268B6A9C08D260B85D8485696CA39C17B17F044D1E050489036ABD381C5E6BF82BA352A1AFF136601AF' }, 'X-B': { modulusLength: 1024, p: '0x9286DBDA91ECCFC3060AA5598318E2A639F5BA90A4CA656157B2673FB191CD0589EE05F4CEF1BD13508408271458C30851CE7A4EF534742BFB11F4743C8F787B11193BA304C0E6BCA25701BF88AF1CB9B8FD4711D89F88E32B37D95316541BF1E5DBB4989B3DF13659B88C0F97A3C1087B9F2D5317D557DCD4AFC6D0A754E279', q: '0xC966E9B3B8B7CDD82FF0F83AF87036C38F42238EC50A876CD390E43D67B6013F', a: '0x7E9C3096676F51E3B2F9884CF0AC2156779496F410E049CED7E53D8B7B5B366B1A6008E5196605A55E89C3190DABF80B9F1163C979FCD18328DAE5E9048811B370107BB7715F82091BB9DE0E33EE2FED6255474F8769FCE5EAFAEEF1CB5A32E0D5C6C2F0FC0B3447072947F5B4C387666993A333FC06568E534AD56D2338D729' }, 'X-C': { modulusLength: 1024, p: '0xB194036ACE14139D36D64295AE6C50FC4B7D65D8B340711366CA93F383653908EE637BE428051D86612670AD7B402C09B820FA77D9DA29C8111A8496DA6C261A53ED252E4D8A69A20376E6ADDB3BDCD331749A491A184B8FDA6D84C31CF05F9119B5ED35246EA4562D85928BA1136A8D0E5A7E5C764BA8902029A1336C631A1D', q: '0x96120477DF0F3896628E6F4A88D83C93204C210FF262BCCB7DAE450355125259', a: '0x3F1817052BAA7598FE3E4F4FC5C5F616E122CFF9EBD89EF81DC7CE8BF56CC64B43586C80F1C4F56DD5718FDD76300BE336784259CA25AADE5A483F64C02A20CF4A10F9C189C433DEFE31D263E6C9764660A731ECCAECB74C8279303731E8CF69205BC73E5A70BDF93E5BB681DAB4EEB9C733CAAB2F673C475E0ECA921D29782E' } }; // /* * BigInteger arithmetic tools * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js * */ // // Bits per one element var DB = 28, DM = (1 << DB) - 1, DV = 1 << DB, FV = Math.pow(2, 52), F1 = 52 - DB, F2 = 2 * DB - 52; function am(y, i, x, w, j, c, n) { var xl = x & 0x3fff, xh = x >> 14; while (--n >= 0) { var l = y[i] & 0x3fff; var h = y[i++] >> 14; var m = xh * l + h * xl; l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; c = (l >> 28) + (m >> 14) + xh * h; w[j++] = l & 0xfffffff; } return c; } function nbi(words) { var r = new Array(Math.ceil(words)); r.s = 0; r.t = 0; return r; } function copyTo(x, r) { for (var i = x.t - 1; i >= 0; --i) r[i] = x[i]; r.t = x.t; r.s = x.s; return r; } function copy(x) { return copyTo(x, nbi(x.t)); } function setInt(x, i) { x.t = 1; x.s = (i < 0) ? -1 : 0; if (i > 0) x[0] = i; else if (i < -1) x[0] = i + DV; else x.t = 0; return x; } function nbv(i) { var r = nbi(1); setInt(r, i); return r; } var ZERO = nbv(0), ONE = nbv(1), THREE = nbv(3); function clamp(x) { var c = x.s & DM; while (x.t > 0 && x[x.t - 1] === c) --x.t; return x; } function subTo(x, a, r) { var i = 0, c = 0, m = Math.min(a.t, x.t); while (i < m) { c += x[i] - a[i]; r[i++] = c & DM; c >>= DB; } if (a.t < x.t) { c -= a.s; while (i < x.t) { c += x[i]; r[i++] = c & DM; c >>= DB; } c += x.s; } else { c += x.s; while (i < a.t) { c -= a[i]; r[i++] = c & DM; c >>= DB; } c -= a.s; } r.s = (c < 0) ? -1 : 0; if (c < -1) r[i++] = DV + c; else if (c > 0) r[i++] = c; r.t = i; return clamp(r); } function sub(x, y) { return subTo(x, y, nbi(x.t)); } function addTo(x, a, r) { var i = 0, c = 0, m = Math.min(a.t, x.t); while (i < m) { c += x[i] + a[i]; r[i++] = c & DM; c >>= DB; } if (a.t < x.t) { c += a.s; while (i < x.t) { c += x[i]; r[i++] = c & DM; c >>= DB; } c += x.s; } else { c += x.s; while (i < a.t) { c += a[i]; r[i++] = c & DM; c = c >> DB; } c += a.s; } r.s = (c < 0) ? -1 : 0; if (c > 0) r[i++] = c; else if (c < -1) r[i++] = DV + c; r.t = i; return clamp(r); } function add(x, y) { return addTo(x, y, nbi(x.t)); } function negTo(x, r) { return subTo(ZERO, x, r); } function neg(x) { return negTo(x, nbi(x.t)); } function absTo(x, r) { return (x.s < 0) ? negTo(r) : copyTo(r); } function abs(x) { return (x.s < 0) ? neg(x) : x; } function compare(x, a) { var r = x.s - a.s; if (r !== 0) return r; var i = x.t; r = i - a.t; if (r !== 0) return (x.s < 0) ? -r : r; while (--i >= 0) if ((r = x[i] - a[i]) !== 0) return r; return 0; } function equals(x, y) { return(compare(x, y) === 0); } function min(x, y) { return(compare(x, y) < 0) ? x : y; } function max(x, y) { return(compare(x, y) > 0) ? x : y; } function nbits(x) { var r = 1, t; if ((t = x >>> 16) !== 0) { x = t; r += 16; } if ((t = x >> 8) !== 0) { x = t; r += 8; } if ((t = x >> 4) !== 0) { x = t; r += 4; } if ((t = x >> 2) !== 0) { x = t; r += 2; } if ((t = x >> 1) !== 0) { x = t; r += 1; } return r; } function dshlTo(x, n, r) { var i; for (i = x.t - 1; i >= 0; --i) r[i + n] = x[i]; for (i = n - 1; i >= 0; --i) r[i] = 0; r.t = x.t + n; r.s = x.s; return r; } function dshrTo(x, n, r) { for (var i = n; i < x.t; ++i) r[i - n] = x[i]; r.t = Math.max(x.t - n, 0); r.s = x.s; return r; } function shlTo(x, n, r) { var bs = n % DB; var cbs = DB - bs; var bm = (1 << cbs) - 1; var ds = Math.floor(n / DB), c = (x.s << bs) & DM, i; for (i = x.t - 1; i >= 0; --i) { r[i + ds + 1] = (x[i] >> cbs) | c; c = (x[i] & bm) << bs; } for (i = ds - 1; i >= 0; --i) r[i] = 0; r[ds] = c; r.t = x.t + ds + 1; r.s = x.s; return clamp(r); } function shrTo(x, n, r) { r.s = x.s; var ds = Math.floor(n / DB); if (ds >= x.t) { r.t = 0; return; } var bs = n % DB; var cbs = DB - bs; var bm = (1 << bs) - 1; r[0] = x[ds] >> bs; for (var i = ds + 1; i < x.t; ++i) { r[i - ds - 1] |= (x[i] & bm) << cbs; r[i - ds] = x[i] >> bs; } if (bs > 0) r[x.t - ds - 1] |= (x.s & bm) << cbs; r.t = x.t - ds; return clamp(r); } function shl(x, n) { var r = nbi(x.t); if (n < 0) shrTo(x, -n, r); else shlTo(x, n, r); return r; } function shr(x, n) { var r = nbi(x.t); if (n < 0) shlTo(x, -n, r); else shrTo(x, n, r); return r; } function bitLength(x) { if (x.t <= 0) return 0; return DB * (x.t - 1) + nbits(x[x.t - 1] ^ (x.s & DM)); } function mulTo(b, a, r) { var x = abs(b), y = abs(a); var i = x.t; r.t = i + y.t; while (--i >= 0) r[i] = 0; for (i = 0; i < y.t; ++i) r[i + x.t] = am(x, 0, y[i], r, i, 0, x.t); r.s = 0; if (b.s !== a.s) subTo(ZERO, r, r); return clamp(r); } function mul(x, y) { return mulTo(x, y, nbi(x.t + y.t)); } function sqrTo(a, r) { var x = abs(a); var i = r.t = 2 * x.t; while (--i >= 0) r[i] = 0; for (i = 0; i < x.t - 1; ++i) { var c = am(x, i, x[i], r, 2 * i, 0, 1); if ((r[i + x.t] += am(x, i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) { r[i + x.t] -= x.DV; r[i + x.t + 1] = 1; } } if (r.t > 0) r[r.t - 1] += am(x, i, x[i], r, 2 * i, 0, 1); r.s = 0; return clamp(r); } function sqr(a) { return sqrTo(a, nbi(a.t * 2)); } function divRemTo(n, m, q, r) { var pm = abs(m); if (pm.t <= 0) throw new OperationError('Division by zero'); var pt = abs(n); if (pt.t < pm.t) { if (q) setInt(q, 0); if (r) copyTo(n, r); return q; } if (!r) r = nbi(m.t); var y = nbi(m.t), ts = n.s, ms = m.s; var nsh = DB - nbits(pm[pm.t - 1]); if (nsh > 0) { shlTo(pm, nsh, y); shlTo(pt, nsh, r); } else { copyTo(pm, y); copyTo(pt, r); } var ys = y.t; var y0 = y[ys - 1]; if (y0 === 0) return q; var yt = y0 * (1 << F1) + ((ys > 1) ? y[ys - 2] >> F2 : 0); var d1 = FV / yt, d2 = (1 << F1) / yt, e = 1 << F2; var i = r.t, j = i - ys, t = !q ? nbi(Math.max(n.t - m.t, 1)) : q; dshlTo(y, j, t); if (compare(r, t) >= 0) { r[r.t++] = 1; subTo(r, t, r); } dshlTo(ONE, ys, t); subTo(t, y, y); while (y.t < ys) y[y.t++] = 0; while (--j >= 0) { var qd = (r[--i] === y0) ? DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); if ((r[i] += am(y, 0, qd, r, j, 0, ys)) < qd) { dshlTo(y, j, t); subTo(r, t, r); while (r[i] < --qd) subTo(r, t, r); } } if (q) { dshrTo(r, ys, q); if (ts !== ms) subTo(ZERO, q, q); } r.t = ys; clamp(r); if (nsh > 0) shrTo(r, nsh, r); if (ts < 0) subTo(ZERO, r, r); return q; } function modTo(b, a, r) { divRemTo(abs(b), a, null, r); if (b.s < 0 && compare(r, ZERO) > 0) subTo(a, r, r); return r; } function mod(b, a) { return modTo(b, a, nbi(a.t)); } function div(b, a) { return divRemTo(b, a, nbi(Math.max(b.t - a.t, 1)), null); } function isEven(x) { return ((x.t > 0) ? (x[0] & 1) : x.s) === 0; } function isZero(x) { return equals(x, ZERO); } function sig(x) { if (x.s < 0) return -1; else if (x.t <= 0 || (x.t === 1 && x[0] <= 0)) return 0; else return 1; } function invMod(x, m) { var ac = isEven(m); if ((isEven(x) && ac) || sig(m) === 0) return ZERO; var u = copy(m), v = copy(x); var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); while (sig(u) !== 0) { while (isEven(u)) { shrTo(u, 1, u); if (ac) { if (!isEven(a) || !isEven(b)) { addTo(a, x, a); subTo(b, m, b); } shrTo(a, 1, a); } else if (!isEven(b)) subTo(b, m, b); shrTo(b, 1, b); } while (isEven(v)) { shrTo(v, 1, v); if (ac) { if (!isEven(c) || !isEven(d)) { addTo(c, x, c); subTo(d, m, d); } shrTo(c, 1, c); } else if (!isEven(d)) subTo(d, m, d); shrTo(d, 1, d); } if (compare(u, v) >= 0) { subTo(u, v, u); if (ac) subTo(a, c, a); subTo(b, d, b); } else { subTo(v, u, v); if (ac) subTo(c, a, c); subTo(d, b, d); } } if (compare(v, ONE) !== 0) return ZERO; if (compare(d, m) >= 0) return subtract(d, m); if (sig(d) < 0) addTo(d, m, d); else return d; if (sig(d) < 0) return add(d, m); else return d; } function testBit(x, n) { var j = Math.floor(n / DB); if (j >= x.t) return (x.s !== 0); return ((x[j] & (1 << (n % DB))) !== 0); } function nothing(x) { return x; } function extend(c, o) { for (var i in o) c.prototype[i] = o[i]; } // /* * Classic, Barret, Mongomery reductions, optimized ExpMod algorithms * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn2.js * */ // // Classic reduction var Classic = function (m) { this.m = m; }; extend(Classic, { convert: function (x) { if (x.s < 0 || compare(x, this.m) >= 0) return mod(x, this.m); else return x; }, revert: nothing, reduce: function (x) { modTo(x, this.m, x); }, sqrTo: function (x, r) { sqrTo(x, r); this.reduce(r); }, mulTo: function (x, y, r) { mulTo(x, y, r); this.reduce(r); } }); function invDig(a) { if (a.t < 1) return 0; var x = a[0]; if ((x & 1) === 0) return 0; var y = x & 3; y = (y * (2 - (x & 0xf) * y)) & 0xf; y = (y * (2 - (x & 0xff) * y)) & 0xff; y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; y = (y * (2 - x * y % DV)) % DV; return (y > 0) ? DV - y : -y; } // Montgomery reduction var Montgomery = function (m) { this.m = m; this.mp = invDig(m); this.mpl = this.mp & 0x7fff; this.mph = this.mp >> 15; this.um = (1 << (DB - 15)) - 1; this.mt2 = 2 * m.t; }; extend(Montgomery, { // xR mod m convert: function (x) { var r = nbi(x.t); dshlTo(abs(x), this.m.t, r); divRemTo(r, this.m, null, r); if (x.s < 0 && compare(r, ZERO) > 0) subTo(this.m, r, r); return r; }, // x/R mod m revert: function (x) { var r = nbi(x.t); copyTo(x, r); this.reduce(r); return r; }, // x = x/R mod m (HAC 14.32) reduce: function (x) { while (x.t <= this.mt2) x[x.t++] = 0; for (var i = 0; i < this.m.t; ++i) { var j = x[i] & 0x7fff; var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & DM; j = i + this.m.t; x[j] += am(this.m, 0, u0, x, i, 0, this.m.t); while (x[j] >= DV) { x[j] -= DV; x[++j]++; } } clamp(x); dshrTo(x, this.m.t, x); if (compare(x, this.m) >= 0) subTo(x, this.m, x); }, // r = "x^2/R mod m"; x != r sqrTo: function (x, r) { sqrTo(x, r); this.reduce(r); }, // r = "xy/R mod m"; x,y != r mulTo: function (x, y, r) { mulTo(x, y, r); this.reduce(r); } }); function dAddOffset(x, n, w) { if (n === 0) return; while (x.t <= w) x[x.t++] = 0; x[w] += n; while (x[w] >= DV) { x[w] -= DV; if (++w >= x.t) x[x.t++] = 0; ++x[w]; } } function mulLowerTo(x, a, n, r) { var i = Math.min(x.t + a.t, n); r.s = 0; // assumes a,x >= 0 r.t = i; while (i > 0) r[--i] = 0; var j; for (j = r.t - x.t; i < j; ++i) r[i + x.t] = am(x, 0, a[i], r, i, 0, x.t); for (j = Math.min(a.t, n); i < j; ++i) am(x, 0, a[i], r, i, 0, n - i); return clamp(r); } function mulUpperTo(x, a, n, r) { --n; var i = r.t = x.t + a.t - n; r.s = 0; // assumes a,x >= 0 while (--i >= 0) r[i] = 0; for (i = Math.max(n - x.t, 0); i < a.t; ++i) r[x.t + i - n] = am(x, n - i, a[i], r, 0, 0, x.t + i - n); clamp(r); return dshrTo(r, 1, r); } // Barrett modular reduction function Barrett(m) { // setup Barrett this.r2 = nbi(2 * m.t); this.q3 = nbi(2 * m.t); dshlTo(ONE, 2 * m.t, this.r2); this.mu = div(this.r2, m); this.m = m; } extend(Barrett, { convert: function (x) { if (x.s < 0 || x.t > 2 * this.m.t) return mod(x, this.m); else if (compare(x, this.m) < 0) return x; else { var r = nbi(x.t); copyTo(x, r); this.reduce(r); return r; } }, revert: function (x) { return x; }, // x = x mod m (HAC 14.42) reduce: function (x) { dshrTo(x, this.m.t - 1, this.r2); if (x.t > this.m.t + 1) { x.t = this.m.t + 1; clamp(x); } mulUpperTo(this.mu, this.r2, this.m.t + 1, this.q3); mulLowerTo(this.m, this.q3, this.m.t + 1, this.r2); while (compare(x, this.r2) < 0) dAddOffset(x, 1, this.m.t + 1); subTo(x, this.r2, x); while (compare(x, this.m) >= 0) subTo(x, this.m, x); }, // r = x^2 mod m; x != r sqrTo: function (x, r) { sqrTo(x, r); this.reduce(r); }, // r = x*y mod m; x,y != r mulTo: function (x, y, r) { mulTo(x, y, r); this.reduce(r); } }); // x^e % m (HAC 14.85) function expMod(x, e, m) { var i = bitLength(e), k, r = nbv(1), z; if (i <= 0) return r; else if (i < 18) k = 1; else if (i < 48) k = 3; else if (i < 144) k = 4; else if (i < 768) k = 5; else k = 6; if (i < 8) z = new Classic(m); else if (isEven(m)) z = new Barrett(m); else z = new Montgomery(m); // precomputation var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; g[1] = z.convert(x); if (k > 1) { var g2 = nbi(m.t * 2); z.sqrTo(g[1], g2); while (n <= km) { g[n] = nbi(m.t * 2); z.mulTo(g2, g[n - 2], g[n]); n += 2; } } var j = e.t - 1, w, is1 = true, r2 = nbi(m.t * 2), t; i = nbits(e[j]) - 1; while (j >= 0) { if (i >= k1) w = (e[j] >> (i - k1)) & km; else { w = (e[j] & ((1 << (i + 1)) - 1)) << (k1 - i); if (j > 0) w |= e[j - 1] >> (DB + i - k1); } n = k; while ((w & 1) == 0) { w >>= 1; --n; } if ((i -= n) < 0) { i += DB; --j; } if (is1) { // ret == 1, don't bother squaring or multiplying it copyTo(g[w], r); is1 = false; } else { while (n > 1) { z.sqrTo(r, r2); z.sqrTo(r2, r); n -= 2; } if (n > 0) z.sqrTo(r, r2); else { t = r; r = r2; r2 = t; } z.mulTo(r2, g[w], r); } while (j >= 0 && (e[j] & (1 << i)) == 0) { z.sqrTo(r, r2); t = r; r = r2; r2 = t; if (--i < 0) { i = DB - 1; --j; } } } return z.revert(r); } // /* * EC Field Elements, Points, Curves * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/ec.js * */ // // EC Field Elemets function newFE(a, x) { a.r.reduce(x); x.q = a.q; x.r = a.r; return x; } function copyFE(a, x) { x.q = a.q; x.r = a.r; return x; } function negFE(a) { return copyFE(a, sub(a.q, a)); } function addFE(a, b) { var r = add(a, b); if (compare(r, a.q) > 0) subTo(r, a.q, r); return copyFE(a, r); } function subFE(a, b) { var r = sub(a, b); if (r.s < 0) addTo(a.q, r, r); return copyFE(a, r); } function mulFE(a, b) { return newFE(a, mul(a, b)); } function sqrFE(a) { return newFE(a, sqr(a)); } function shlFE(a, i) { return newFE(a, shl(a, i)); } function invFE(a) { return copyFE(a, invMod(a, a.q)); } // EC Points function newEC(curve, x, y, z) { return { curve: curve, x: x, y: y, z: z || newFE(curve, ONE) }; } function getX(point) { if (!point.zinv) point.zinv = invFE(point.z); return mulFE(point.x, point.zinv); } function getY(point) { if (!point.zinv) point.zinv = invFE(point.z); return mulFE(point.y, point.zinv); } function isInfinity(a) { if ((!a.x) && (!a.y)) return true; return isZero(a.z) && !isZero(a.y); } function getInfinity(a) { return a.curve.infinity; } function equalsEC(a, b) { if (a === b) return true; if (isInfinity(a)) return isInfinity(b); if (isInfinity(b)) return isInfinity(a); var u, v; // u = Y2 * Z1 - Y1 * Z2 u = subFE(mulFE(b.y, a.z), mulFE(a.y, b.z)); if (!isZero(u)) return false; // v = X2 * Z1 - X1 * Z2 v = subFE(mulFE(b.x, a.z), mulFE(a.x, b.z)); return isZero(v); } function negEC(a) { return newEC(a.curve, a.x, negFE(a.y), a.z); } function addEC(a, b) { if (isInfinity(a)) return b; if (isInfinity(b)) return a; // u = Y2 * Z1 - Y1 * Z2 var u = subFE(mulFE(b.y, a.z), mulFE(a.y, b.z)); // v = X2 * Z1 - X1 * Z2 var v = subFE(mulFE(b.x, a.z), mulFE(a.x, b.z)); if (isZero(v)) { if (isZero(u)) { return twiceEC(a); // a == b, so double } return getInfinity(a); // a = -b, so infinity } var x1 = a.x; var y1 = a.y; var v2 = sqrFE(v); var v3 = mulFE(v2, v); var x1v2 = mulFE(x1, v2); var zu2 = mulFE(sqrFE(u), a.z); // x3 = v * (z2 * (z1 * u^2 - 2 * x1 * v^2) - v^3) var x3 = mulFE(subFE(mulFE(subFE(zu2, shlFE(x1v2, 1)), b.z), v3), v); // y3 = z2 * (3 * x1 * u * v^2 - y1 * v^3 - z1 * u^3) + u * v^3 var y3 = addFE(mulFE(subFE(subFE(mulFE(mulFE(x1v2, THREE), u), mulFE(y1, v3)), mulFE(zu2, u)), b.z), mulFE(u, v3)); // z3 = v^3 * z1 * z2 var z3 = mulFE(mulFE(v3, a.z), b.z); return newEC(a.curve, x3, y3, z3); } function twiceEC(b) { if (isInfinity(b)) return b; if (sig(b.y) === 0) return getInfinity(b); var x1 = b.x; var y1 = b.y; var y1z1 = mulFE(y1, b.z); var y1sqz1 = mulFE(y1z1, y1); var a = b.curve.a; // w = 3 * x1^2 + a * z1^2 var w = mulFE(sqrFE(x1), THREE); if (!isZero(a)) { w = addFE(w, mulFE(sqrFE(b.z), a)); } // x3 = 2 * y1 * z1 * (w^2 - 8 * x1 * y1^2 * z1) var x3 = mulFE(shlFE(subFE(sqrFE(w), mulFE(shlFE(x1, 3), y1sqz1)), 1), y1z1); // y3 = 4 * y1^2 * z1 * (3 * w * x1 - 2 * y1^2 * z1) - w^3 var y3 = subFE(mulFE(shlFE(subFE(mulFE(mulFE(w, THREE), x1), shlFE(y1sqz1, 1)), 2), y1sqz1), mulFE(sqrFE(w), w)); // z3 = 8 * (y1 * z1)^3 var z3 = shlFE(mulFE(sqrFE(y1z1), y1z1), 3); return newEC(b.curve, x3, y3, z3); } // Simple NAF (Non-Adjacent Form) multiplication algorithm function mulEC(a, k) { if (isInfinity(a)) return a; if (sig(k) === 0) return getInfinity(a); var e = k; var h = mul(e, THREE); var neg = negEC(a); var R = a; var i; for (i = bitLength(h) - 2; i > 0; --i) { R = twiceEC(R); var hBit = testBit(h, i); var eBit = testBit(e, i); if (hBit !== eBit) { R = addEC(R, hBit ? a : neg); } } return R; } function mul2AndAddEC(a, k) { var nbits = bitLength(k); var R = a, Q = getInfinity(a); for (var i = 0; i < nbits - 1; i++) { if (testBit(k, i) === 1) Q = addEC(Q, R); R = twiceEC(R); } if (testBit(k, nbits - 1) === 1) Q = addEC(Q, R); return Q; } // Compute a*j + x*k (simultaneous multiplication) function mulTwoEC(a, j, x, k) { var i; if (bitLength(j) > bitLength(k)) i = bitLength(j) - 1; else i = bitLength(k) - 1; var R = getInfinity(a); var both = addEC(a, x); while (i >= 0) { R = twiceEC(R); if (testBit(j, i)) { if (testBit(k, i)) { R = addEC(R, both); } else { R = addEC(R, a); } } else { if (testBit(k, i)) { R = addEC(R, x); } } --i; } return R; } // EC Curve function newCurve(q, a, b) { var curve = {}; curve.q = q; curve.r = new Barrett(q); curve.a = newFE(curve, a); curve.b = newFE(curve, b); curve.infinity = newEC(curve); return curve; } // /* * Converion tools (hex, binary) * */ // function atobi(d) { var k = 8; var a = new Uint8Array(d); var r = nbi(a.length * 8 / DB); r.t = 0; r.s = 0; var sh = 0; for (var i = 0, n = a.length; i < n; i++) { var x = a[i]; if (sh === 0) r[r.t++] = x; else if (sh + k > DB) { r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; r[r.t++] = (x >> (DB - sh)); } else r[r.t - 1] |= x << sh; sh += k; if (sh >= DB) sh -= DB; } return clamp(r); } function bitoa(s, bitLength) { var k = 8; var km = (1 << k) - 1, d, m = false, r = [], i = s.t; var p = DB - (i * DB) % k; if (i-- > 0) { if (p < DB && (d = s[i] >> p) > 0) { m = true; r.push(d); } while (i >= 0) { if (p < k) { d = (s[i] & ((1 << p) - 1)) << (k - p); d |= s[--i] >> (p += DB - k); } else { d = (s[i] >> (p -= k)) & km; if (p <= 0) { p += DB; --i; } } if (d > 0) m = true; if (m) r.push(d); } } var r8 = new Uint8Array(bitLength ? bitLength / 8 : r.length); if (m) r8.set(r.reverse()); return r8.buffer; } function htobi(s) { if (typeof s === 'number' || s instanceof Number) return nbv(s); s = s.replace(/[^\-A-Fa-f0-9]/g, ''); if (!s) s = '0'; var k = 4; var r = nbi(s.length / 7); var i = s.length, mi = false, sh = 0; while (--i >= 0) { var c = s.charAt(i); if (c === '-') { mi = true; continue; } var x = parseInt(s.charAt(i), 16); mi = false; if (sh === 0) r[r.t++] = x; else if (sh + k > DB) { r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; r[r.t++] = (x >> (DB - sh)); } else r[r.t - 1] |= x << sh; sh += k; if (sh >= DB) sh -= DB; } if (mi) subTo(ZERO, r, r); return clamp(r); } function bitoh(x) { if (x.s < 0) return "-" + bitoh(negTo(x, nbi(x.t))); var k = 4; var km = (1 << k) - 1, d, m = false, r = "", i = x.t; var p = DB - (i * DB) % k; if (i-- > 0) { if (p < DB && (d = x[i] >> p) > 0) { m = true; r = d.toString(16); } while (i >= 0) { if (p < k) { d = (x[i] & ((1 << p) - 1)) << (k - p); d |= x[--i] >> (p += DB - k); } else { d = (x[i] >> (p -= k)) & km; if (p <= 0) { p += DB; --i; } } if (d > 0) m = true; if (m) r += d.toString(16); } } return "0x" + (m ? r : "0"); } // biginteger to big-endian integer bytearray function bitoi(s) { var i = s.t, r = []; r[0] = s.s; var p = DB - (i * DB) % 8, d, k = 0; if (i-- > 0) { if (p < DB && (d = s[i] >> p) !== (s.s & DM) >> p) r[k++] = d | (s.s << (DB - p)); while (i >= 0) { if (p < 8) { d = (s[i] & ((1 << p) - 1)) << (8 - p); d |= s[--i] >> (p += DB - 8); } else { d = (s[i] >> (p -= 8)) & 0xff; if (p <= 0) { p += DB; --i; } } if ((d & 0x80) !== 0) d |= -256; if (k === 0 && (s.s & 0x80) !== (d & 0x80)) ++k; if (k > 0 || d !== s.s) r[k++] = d; } } return new Uint8Array(r).buffer; } // big-endian integer bytearray to biginteger function itobi(d) { var k = 8, s = new Uint8Array(d), r = nbi(s.length / 7); r.t = 0; r.s = 0; var i = s.length, sh = 0; while (--i >= 0) { var x = s[i] & 0xff; if (sh === 0) r[r.t++] = x; else if (sh + k > DB) { r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; r[r.t++] = (x >> (DB - sh)); } else r[r.t - 1] |= x << sh; sh += k; if (sh >= DB) sh -= DB; } if ((s[0] & 0x80) !== 0) { r.s = -1; if (sh > 0) r[r.t - 1] |= ((1 << (DB - sh)) - 1) << sh; } return clamp(r); } // Swap bytes in buffer function swap(s) { var src = new Uint8Array(s), dst = new Uint8Array(src.length); for (var i = 0, n = src.length; i < n; i++) dst[n - i - 1] = src[i]; return dst.buffer; } // Calculate hash of data function hash(d) { if (this.hash) d = this.hash.digest(d); // Swap hash for SignalCom if (this.procreator === 'SC' || (this.procreator === 'VN' && this.hash.version === 2012)) d = swap(d); return d; } // Check buffer function buffer(d) { if (d instanceof CryptoOperationData) return d; else if (d && d?.buffer instanceof CryptoOperationData) return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; else throw new DataError('CryptoOperationData or CryptoOperationDataView required'); } // Check double buffer function to2(d) { var b = buffer(d); if (b.byteLength % 2 > 0) throw new DataError('Buffer length must be even'); var n = b.byteLength / 2; return [atobi(new Uint8Array(b, 0, n)), atobi(new Uint8Array(b, n, n))]; } function from2(x, y, bitLength) { var a = bitoa(x, bitLength), b = bitoa(y, bitLength), d = new Uint8Array(a.byteLength + b.byteLength); d.set(new Uint8Array(a)); d.set(new Uint8Array(b), a.byteLength); return d.buffer; } function getSeed(length) { GostRandom = GostRandom || root.GostRandom; var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; if (randomSource.getRandomValues) { var d = new Uint8Array(Math.ceil(length / 8)); randomSource.getRandomValues(d); return d; } else throw new NotSupportedError('Random generator not found'); } // /** * Algorithm name GOST R 34.10

* * The sign method returns sign data generated with the supplied privateKey.
* * @memberOf GostSign * @method sign * @instance * @param {(CryptoOperationData|TypedArray)} privateKey Private key * @param {(CryptoOperationData|TypedArray)} data Data * @returns {CryptoOperationData} Signature */ function sign(privateKey, data) // { // Stage 1 var b = buffer(data); var alpha = atobi(hash.call(this, b)); var q = this.q; var x = mod(atobi(buffer(privateKey)), q); // Stage 2 var e = mod(alpha, q); if (isZero(e)) e = ONE; var s = ZERO; while (isZero(s)) { var r = ZERO; while (isZero(r)) { // Stage 3 var k = mod(atobi(this.ukm || getSeed(this.bitLength)), q); // pseudo random 0 < k < q // Stage 4 if (this.curve) { // Gost R 34.10-2001 || Gost R 34.10-2012 var P = this.P; var C = mulEC(P, k); r = mod(getX(C), q); } else { // Gost R 34.10-94 var p = this.p, a = this.a; r = mod(expMod(a, k, p), q); } } // Stage 5 s = mod(add(mul(r, x), mul(k, e)), q); } // Stage 6 // console.log('s', bitoh(s)); // console.log('r', bitoh(r)); var zetta; // Integer structure for SignalCom algorithm if (this.procreator === 'SC') { zetta = { r: bitoh(r), s: bitoh(s) }; } else { zetta = from2(r, s, this.bitLength); // Swap bytes for CryptoPro algorithm if (this.procreator === 'CP' || this.procreator === 'VN') zetta = swap(zetta); } return zetta; } // /** * Algorithm name GOST R 34.10

* * The verify method returns signature verification for the supplied publicKey.
* * @memberOf GostSign * @method sign * @instance * @param {(CryptoOperationData|TypedArray)} publicKey Public key * @param {(CryptoOperationData|TypedArray)} signature Signature * @param {(CryptoOperationData|TypedArray)} data Data * @returns {boolean} Signature verified = true */ function verify(publicKey, signature, data) // { // Stage 1 var q = this.q; var r, s; // Ready int for SignalCom algorithm if (this.procreator === 'SC') { r = htobi(signature.r); s = htobi(signature.s); } else { if (this.procreator === 'CP' || this.procreator === 'VN') signature = swap(signature); var zetta = to2(signature); // Swap bytes for CryptoPro algorithm s = zetta[1]; // first 32 octets contain the big-endian representation of s r = zetta[0]; // and second 32 octets contain the big-endian representation of r } if (compare(r, q) >= 0 || compare(s, q) >= 0) return false; // Stage 2 var b = buffer(data); var alpha = atobi(hash.call(this, b)); // Stage 3 var e = mod(alpha, q); if (isZero(e) === 0) e = ONE; // Stage 4 var v = invMod(e, q); // Stage 5 var z1 = mod(mul(s, v), q); var z2 = sub(q, mod(mul(r, v), q)); // Stage 6 if (this.curve) { // Gost R 34.10-2001 || Gost R 34.10-2012 var k2 = to2(publicKey), curve = this.curve, P = this.P, x = newFE(curve, k2[0]), // first 32 octets contain the little-endian representation of x y = newFE(curve, k2[1]), // and second 32 octets contain the little-endian representation of y. Q = new newEC(curve, x, y); // This corresponds to the binary representation of (256||256) var C = mulTwoEC(P, z1, Q, z2); var R = mod(getX(C), q); } else { // Gost R 34.10-94 var p = this.p, a = this.a; var y = atobi(publicKey); var R = mod(mod(mul(expMod(a, z1, p), expMod(y, z2, p)), p), q); } // Stage 7 return (compare(R, r) === 0); } // /** * Algorithm name GOST R 34.10

* * The generateKey method returns a new generated key pair using the specified * AlgorithmIdentifier. * * @memberOf GostSign * @method generateKey * @instance * @returns {Object} Object with two CryptoOperationData members: privateKey and publicKey */ function generateKey() // { var curve = this.curve; if (curve) { var Q = curve.infinity; while (isInfinity(Q)) { // Generate random private key var d = ZERO; if (this.ukm) { d = atobi(this.ukm); } else { while (isZero(d)) d = mod(atobi(getSeed(this.bitLength)), this.q); // 0 < d < q } // Calculate public key Q = mulEC(this.P, d); var x = getX(Q), y = getY(Q); // console.log('d', bitoh(d)); // console.log('x', bitoh(x)); // console.log('y', bitoh(y)); } // Return result return { privateKey: bitoa(d, this.bitLength), publicKey: from2(x, y, this.bitLength) // This corresponds to the binary representation of (256||256) }; } else throw new NotSupportedError('Key generation for GOST R 34.10-94 not supported'); } // /** * Algorithm name GOST R 34.10 mode MASK

* * The generateMaskKey method returns a new generated key mask using for wrapping. * * @memberOf GostSign * @method generateMaskKey * @instance * @returns {Object} Object with two CryptoOperationData members: privateKey and publicKey */ function generateMaskKey() // { var curve = this.curve; if (curve) { // Generate random private key var d = ZERO; while (isZero(d)) d = mod(atobi(getSeed(this.bitLength)), this.q); // 0 < d < q // Return result return bitoa(d, this.bitLength); } else throw new NotSupportedError('Key generation for GOST R 34.10-94 not supported'); } // /** * Algorithm name GOST R 34.10

* * Unwrap private key from private key and ukm (mask) * * @memberOf GostSign * @method unwrap * @instance * @param {(CryptoOperationData|TypedArray)} baseKey Unwrapping key * @param {(CryptoOperationData|TypedArray)} data Wrapped key * @returns {Object} CryptoOperationData unwrapped privateKey */ function unwrapKey(baseKey, data) // { var curve = this.curve; if (curve) { var q = this.q; var x = mod(atobi(buffer(data)), q); var y = mod(atobi(buffer(baseKey)), q); var z = this.procreator === 'VN' ? mod(mul(x, y), q) : mod(mul(x, invMod(y, q)), q); return bitoa(z); } else throw new NotSupportedError('Key wrapping GOST R 34.10-94 not supported'); } // /** * Algorithm name GOST R 34.10

* * Wrap private key with private key and ukm (mask) * * @memberOf GostSign * @method unwrap * @instance * @param {(CryptoOperationData|TypedArray)} baseKey Wrapping key * @param {(CryptoOperationData|TypedArray)} data Key * @returns {Object} CryptoOperationData unwrapped privateKey */ function wrapKey(baseKey, data) // { var curve = this.curve; if (curve) { var q = this.q; var x = mod(atobi(buffer(data)), q); var y = mod(atobi(buffer(baseKey)), q); var z = this.procreator === 'VN' ? mod(mul(x, invMod(y, q)), q) : mod(mul(x, y), q); return bitoa(z); } else throw new NotSupportedError('Key wrapping GOST R 34.10-94 not supported'); } // /** * Algorithm name GOST R 34.10

* * @memberOf GostSign * @method derive * @instance * @private * @param {CryptoOperationData} baseKey Key for deriviation * @returns {CryptoOperationData} */ function derive(baseKey) // { var k, ukm = atobi(this.ukm); var q = this.q; var x = mod(atobi(buffer(baseKey)), q); if (this.curve) { // 1) Let K(x,y,UKM) = ((UKM*x)(mod q)) . (y.P) (512 bit), where // x - sender’s private key (256 bit) // x.P - sender’s public key (512 bit) // y - recipient’s private key (256 bit) // y.P - recipient’s public key (512 bit) // UKM - non-zero integer, produced as in step 2 p. 6.1 [GOSTR341001] // P - base point on the elliptic curve (two 256-bit coordinates) // UKM*x - x multiplied by UKM as integers // x.P - a multiple point var K = mulEC(this.peer_Q, mod(mul(ukm, x), q)); k = from2(getX(K), getY(K), // This corresponds to the binary representation of (256||256) this.bitLength); } else { // 1) Let K(x,y) = a^(x*y) (mod p), where // x - sender’s private key, a^x - sender’s public key // y - recipient’s private key, a^y - recipient’s public key // a, p - parameters var p = this.p, a = this.a; k = bitoa(expMod(this.peer_y, x, p)); } // 2) Calculate a 256-bit hash of K(x,y,UKM): // KEK(x,y,UKM) = gostSign (K(x,y,UKM) return hash.call(this, k); } // /** * Algorithm name GOST R 34.10

* * The deriveBits method returns length bits on baseKey. * * @memberOf GostSign * @method deriveBits * @instance * @param {(CryptoOperationData|TypedArray)} baseKey Key for deriviation * @param {number} length output bit-length * @returns {CryptoOperationData} result */ function deriveBits(baseKey, length) // { if (length < 8 || length > this.bitLength || length % 8 > 0) throw new DataError('Length must be no more than ' + this.bitLength + ' bits and multiple of 8'); var n = length / 8, b = derive.call(this, baseKey), r = new Uint8Array(n); r.set(new Uint8Array(b, 0, n)); return r.buffer; } // /** * Algorithm name GOST R 34.10

* * The deriveKey method returns 256 bit Key encryption key on baseKey. * * This algorithm creates a key encryption key (KEK) using 64 bit UKM, * the sender’s private key, and the recipient’s public key (or the * reverse of the latter pair * * @memberOf GostSign * @method deriveKey * @instance * @param {(CryptoOperationData|TypedArray)} baseKey Key for deriviation * @returns {CryptoOperationData} result */ function deriveKey(baseKey) // { var b = derive.call(this, baseKey), r = new Uint8Array(32); r.set(new Uint8Array(b, 0, 32)); return r.buffer; } // /** * Gost R 34.10 universal object

* * References: {@link http://tools.ietf.org/html/rfc6986} and {@link http://tools.ietf.org/html/rfc5831}

* * Normalized algorithm identifier common parameters: * *
    *
  • name Algorithm name 'GOST R 34.10'
  • *
  • version Algorithm version *
      *
    • 1994 - Old-style GOST R 34.10-94 ExpMod algorithm with GOST R 34.11-94 hash
    • *
    • 2001 - GOST R 34.10-2001 Eliptic curve algorithm with old GOST R 34.11-94 hash
    • *
    • 2012 - GOST R 34.10-2012 Eliptic curve algorithm with GOST R 34.11-12 hash, default mode
    • *
    *
  • *
  • length Length of hash and signature. Key length == hash length for EC algorithms and 2 * hash length for ExpMod algorithm *
      *
    • GOST R 34.10-256 - 256 bits digest, default mode
    • *
    • GOST R 34.10-512 - 512 bits digest only for GOST R 34.11-2012 hash
    • *
    *
  • *
  • mode Algorithm mode *
      *
    • SIGN Digital signature mode (default)
    • *
    • DH Diffie-Hellman key generation and key agreement mode
    • *
    *
  • *
  • sBox Paramset sBox for GOST 34.11-94. Used only if version = 1994 or 2001
  • *
* * Supported algorithms, modes and parameters: * *
    *
  • Sign/Verify mode (SIGN)
  • *
  • DeriveKey/DeriveBits mode (DH) *
      *
    • {@link CryptoOperationData} ukm User key material. Default - random generated value
    • *
    • {@link CryptoOperationData} public The peer's EC public key data
    • *
    *
  • *
  • GenerateKey mode (SIGN and DH) version = 1994 *
      *
    • namedParam Paramset for key generation algorithm. If specified no additianal parameters required
    • *
    * Additional parameters, if namedParam not specified *
      *
    • modulusLength Bit length of p (512 or 1024 bits). Default = 1024
    • *
    • p {@link CryptoOperationData} Modulus, prime number, 2^(t-1) *
    • q {@link CryptoOperationData} Order of cyclic group, prime number, 2^254 *
    • a {@link CryptoOperationData} Generator, integer, 1 *
    *
  • *
  • GenerateKey mode (SIGN and DH) version = 2001 or 2012 *
      *
    • namedCurve Paramset for key generation algorithm. If specified no additianal parameters required
    • *
    * Additional EC parameters, if namedCurve not specified *
      *
    • p {@link CryptoOperationData} Prime number - elliptic curve modulus
    • *
    • a {@link CryptoOperationData} Coefficients a of the elliptic curve E
    • *
    • b {@link CryptoOperationData} Coefficients b of the elliptic curve E
    • *
    • q {@link CryptoOperationData} Prime number - order of cyclic group
    • *
    • x {@link CryptoOperationData} Base point p x-coordinate
    • *
    • y {@link CryptoOperationData} Base point p y-coordinate
    • *
    *
  • *
* * @class GostSign * @param {AlgorithmIndentifier} algorithm */ function GostSign(algorithm) // { algorithm = algorithm || {}; this.name = (algorithm.name || 'GOST R 34.10') + '-' + ((algorithm.version || 2012) % 100) + '-' + (algorithm.length || 256) + (((algorithm.mode || 'SIGN') !== 'SIGN') ? '-' + algorithm.mode : '') + (typeof algorithm.namedParam === 'string' ? '/' + algorithm.namedParam : '') + (typeof algorithm.namedCurve === 'string' ? '/' + algorithm.namedCurve : '') + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); var version = algorithm.version || 2012; // Functions switch (algorithm.mode || 'SIGN') { case 'SIGN': this.sign = sign; this.verify = verify; this.generateKey = generateKey; break; case 'DH': this.deriveBits = deriveBits; this.deriveKey = deriveKey; this.generateKey = generateKey; break; case 'MASK': this.wrapKey = wrapKey; this.unwrapKey = unwrapKey; this.generateKey = generateMaskKey; break; } // Define parameters if (version === 1994) { // Named or parameters algorithm var param = algorithm.param; if (!param) param = GostParams[this.namedParam = (algorithm.namedParam || 'S-A').toUpperCase()]; this.modulusLength = algorithm.modulusLength || param.modulusLength || 1024; this.p = htobi(param.p); this.q = htobi(param.q); this.a = htobi(param.a); // Public key for derive if (algorithm['public']) this.peer_y = atobi(algorithm['public']); } else { // Named or parameters algorithm var param = algorithm.curve; if (!param) param = ECGostParams[this.namedCurve = (algorithm.namedCurve || 'S-256-A').toUpperCase()]; var curve = this.curve = newCurve(htobi(param.p), htobi(param.a), htobi(param.b)); this.P = newEC(curve, newFE(curve, htobi(param.x)), newFE(curve, htobi(param.y))); this.q = htobi(param.q); // Public key for derive if (algorithm['public']) { var k2 = to2(algorithm['public']); this.peer_Q = new newEC(this.curve, // This corresponds to the binary representation of (256||256) newFE(this.curve, k2[0]), // first 32 octets contain the little-endian representation of x newFE(this.curve, k2[1])); // and second 32 octets contain the little-endian representation of y. } } // Check bit length var hashLen, keyLen; if (this.curve) { keyLen = algorithm.length || bitLength(this.q); if (keyLen > 508 && keyLen <= 512) keyLen = 512; else if (keyLen > 254 && keyLen <= 256) keyLen = 256; else throw new NotSupportedError('Support keys only 256 or 512 bits length'); hashLen = keyLen; } else { keyLen = algorithm.modulusLength || bitLength(this.p); if (keyLen > 1016 && keyLen <= 1024) keyLen = 1024; else if (keyLen > 508 && keyLen <= 512) keyLen = 512; else throw new NotSupportedError('Support keys only 512 or 1024 bits length'); hashLen = 256; } this.bitLength = hashLen; this.keyLength = keyLen; // Algorithm proceator for result conversion this.procreator = algorithm.procreator; // Hash function definition var hash = algorithm.hash; if (hash) { if (typeof hash === 'string' || hash instanceof String) hash = {name: hash}; if (algorithm.version === 1994 || algorithm.version === 2001) { hash.version = 1994; hash.length = 256; hash.sBox = algorithm.sBox || hash.sBox; } else { hash.version = 2012; hash.length = hashLen; } hash.procreator = hash.procreator || algorithm.procreator; if (!GostDigest) GostDigest = root.GostDigest; if (!GostDigest) throw new NotSupportedError('Object GostDigest not found'); this.hash = new GostDigest(hash); } // Pregenerated seed for key exchange algorithms if (algorithm.ukm) // Now don't check size this.ukm = algorithm.ukm; } // export default GostSign; ================================================ FILE: src/core/vendor/remove-exif.mjs ================================================ /* piexifjs The MIT License (MIT) Copyright (c) 2014, 2015 hMatoba(https://github.com/hMatoba) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Utils from "../Utils.mjs"; // Param jpeg should be a binaryArray export function removeEXIF(jpeg) { // Convert binaryArray to char string jpeg = Utils.byteArrayToChars(jpeg); if (jpeg.slice(0, 2) != "\xff\xd8") { throw ("Given data is not jpeg."); } var segments = splitIntoSegments(jpeg); if (segments[1].slice(0, 2) == "\xff\xe1" && segments[1].slice(4, 10) == "Exif\x00\x00") { segments = [segments[0]].concat(segments.slice(2)); } else if (segments[2].slice(0, 2) == "\xff\xe1" && segments[2].slice(4, 10) == "Exif\x00\x00") { segments = segments.slice(0, 2).concat(segments.slice(3)); } else { throw ("Exif not found."); } var new_data = segments.join(""); // Convert back to binaryArray new_data = Utils.strToCharcode(new_data); return new_data; }; function splitIntoSegments(data) { if (data.slice(0, 2) != "\xff\xd8") { throw ("Given data isn't JPEG."); } var head = 2; var segments = ["\xff\xd8"]; while (true) { if (data.slice(head, head + 2) == "\xff\xda") { segments.push(data.slice(head)); break; } else { var length = unpack(">H", data.slice(head + 2, head + 4))[0]; var endPoint = head + length + 2; segments.push(data.slice(head, endPoint)); head = endPoint; } if (head >= data.length) { throw ("Wrong JPEG data."); } } return segments; } function unpack(mark, str) { if (typeof(str) != "string") { throw ("'unpack' error. Got invalid type argument."); } var l = 0; for (var markPointer = 1; markPointer < mark.length; markPointer++) { if (mark[markPointer].toLowerCase() == "b") { l += 1; } else if (mark[markPointer].toLowerCase() == "h") { l += 2; } else if (mark[markPointer].toLowerCase() == "l") { l += 4; } else { throw ("'unpack' error. Got invalid mark."); } } if (l != str.length) { throw ("'unpack' error. Mismatch between symbol and string length. " + l + ":" + str.length); } var littleEndian; if (mark[0] == "<") { littleEndian = true; } else if (mark[0] == ">") { littleEndian = false; } else { throw ("'unpack' error."); } var unpacked = []; var strPointer = 0; var p = 1; var val = null; var c = null; var length = null; var sliced = ""; while (c = mark[p]) { if (c.toLowerCase() == "b") { length = 1; sliced = str.slice(strPointer, strPointer + length); val = sliced.charCodeAt(0); if ((c == "b") && (val >= 0x80)) { val -= 0x100; } } else if (c == "H") { length = 2; sliced = str.slice(strPointer, strPointer + length); if (littleEndian) { sliced = sliced.split("").reverse().join(""); } val = sliced.charCodeAt(0) * 0x100 + sliced.charCodeAt(1); } else if (c.toLowerCase() == "l") { length = 4; sliced = str.slice(strPointer, strPointer + length); if (littleEndian) { sliced = sliced.split("").reverse().join(""); } val = sliced.charCodeAt(0) * 0x1000000 + sliced.charCodeAt(1) * 0x10000 + sliced.charCodeAt(2) * 0x100 + sliced.charCodeAt(3); if ((c == "l") && (val >= 0x80000000)) { val -= 0x100000000; } } else { throw ("'unpack' error. " + c); } unpacked.push(val); strPointer += length; p += 1; } return unpacked; } ================================================ FILE: src/node/File.mjs ================================================ /** * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import { detectFileType } from "../core/lib/FileType.mjs"; /** * FileShim * * Create a class that behaves like the File object in the Browser so that * operations that use the File object still work. * * File doesn't write to disk, but it would be easy to do so with e.gfs.writeFile. */ class File { /** * Constructor * * https://w3c.github.io/FileAPI/#file-constructor * * @param {String|Array|ArrayBuffer|Buffer|[File]} bits - file content * @param {String} name (optional) - file name * @param {Object} stats (optional) - file stats e.g. lastModified */ constructor(data, name="", stats={}) { if (!Array.isArray(data)) { data = [data]; } const buffers = data.map((d) => { if (d instanceof File) { return Buffer.from(d.data); } if (d instanceof ArrayBuffer) { return Buffer.from(d); } return Buffer.from(d); }); const totalLength = buffers.reduce((p, c) => p + c.length, 0); this.data = Buffer.concat(buffers, totalLength); this.name = name; this.lastModified = stats.lastModified || Date.now(); const types = detectFileType(this.data); if (types.length) { this.type = types[0].mime; } else { this.type = "application/unknown"; } } /** * size property */ get size() { return this.data.length; } /** * Return lastModified as Date */ get lastModifiedDate() { return new Date(this.lastModified); } } export default File; ================================================ FILE: src/node/NodeDish.mjs ================================================ /** * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import util from "util"; import Dish from "../core/Dish.mjs"; /** * Subclass of Dish for use in the Node.js environment. * * Adds some helper functions and improves coercion for Node.js logging. */ class NodeDish extends Dish { /** * Create a Dish * @param {any} inputOrDish - The dish input * @param {String|Number} - The dish type, as enum or string */ constructor(inputOrDish=null, type=null) { // Allow `fs` file input: // Any node fs Buffers transformed to array buffer // Use Array.from as Uint8Array doesnt pass instanceof Array test if (Buffer.isBuffer(inputOrDish)) { inputOrDish = Array.from(inputOrDish); type = Dish.BYTE_ARRAY; } super(inputOrDish, type); } /** * Apply the inputted operation to the dish. * * @param {WrappedOperation} operation the operation to perform * @param {*} args - any arguments for the operation * @returns {Dish} a new dish with the result of the operation. */ apply(operation, args=null) { return operation(this, args); } /** * alias for get * @param args see get args */ to(...args) { return this.get(...args); } /** * Avoid coercion to a String primitive. */ toString() { return this.presentAs(Dish.typeEnum("string")); } /** * What we want to log to the console. */ [util.inspect.custom](depth, options) { return this.presentAs(Dish.typeEnum("string")); } /** * Backwards compatibility for node v6 * Log only the value to the console in node. */ inspect() { return this.presentAs(Dish.typeEnum("string")); } /** * Avoid coercion to a Number primitive. */ valueOf() { return this.presentAs(Dish.typeEnum("number")); } } export default NodeDish; ================================================ FILE: src/node/NodeRecipe.mjs ================================================ /** * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import {operations} from "./index.mjs"; import { sanitise } from "./apiUtils.mjs"; /** * Similar to core/Recipe, Recipe controls a list of operations and * the NodeDish the operate on. However, this Recipe is for the node * environment. */ class NodeRecipe { /** * Recipe constructor * @param recipeConfig */ constructor(recipeConfig) { this._parseConfig(recipeConfig); } /** * Validate an ingredient & coerce to operation if necessary. * @param {String | Function | Object} ing * @returns {Function || Object} The operation, or an object with the * operation and its arguments * @throws {TypeError} If it cannot find the operation in chef's list of operations. */ _validateIngredient(ing) { // CASE operation name given. Find operation and validate if (typeof ing === "string") { const op = operations.find((op) => { return sanitise(op.opName) === sanitise(ing); }); if (op) { // Need to validate against case 2 return this._validateIngredient(op); } else { throw new TypeError(`Couldn't find an operation with name '${ing}'.`); } // CASE operation given. Check its a chef operation and check its not flowcontrol } else if (typeof ing === "function") { if (ing.flowControl) { throw new TypeError(`flowControl operations like ${ing.opName} are not currently allowed in recipes for chef.bake in the Node API`); } if (operations.includes(ing)) { return ing; } else { throw new TypeError("Inputted function not a Chef operation."); } // CASE: op, maybe with configuration } else if (ing.op) { const sanitisedOp = this._validateIngredient(ing.op); if (ing.args) { return {op: sanitisedOp, args: ing.args}; } return sanitisedOp; } else { throw new TypeError("Recipe can only contain function names or functions"); } } /** * Parse an opList from a recipeConfig and assign it to the recipe's opList. * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig */ _parseConfig(recipeConfig) { if (!recipeConfig) { this.opList = []; return; } if (!Array.isArray(recipeConfig)) { recipeConfig = [recipeConfig]; } this.opList = recipeConfig.map((ing) => this._validateIngredient(ing)); } /** * Run the dish through each operation, one at a time. * @param {NodeDish} dish * @returns {NodeDish} */ execute(dish) { return this.opList.reduce((prev, curr) => { // CASE where opList item is op and args if (Object.prototype.hasOwnProperty.call(curr, "op") && Object.prototype.hasOwnProperty.call(curr, "args")) { return curr.op(prev, curr.args); } // CASE opList item is just op. return curr(prev); }, dish); } } export default NodeRecipe; ================================================ FILE: src/node/api.mjs ================================================ /** * Wrap operations for consumption in Node. * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ /* eslint no-console: ["off"] */ import NodeDish from "./NodeDish.mjs"; import NodeRecipe from "./NodeRecipe.mjs"; import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"}; import { sanitise, removeSubheadingsFromArray, sentenceToCamelCase } from "./apiUtils.mjs"; import ExcludedOperationError from "../core/errors/ExcludedOperationError.mjs"; /** * transformArgs * * Take the default args array and update with any user-defined * operation arguments. Allows user to define arguments in object style, * with accommodating name matching. Using named args in the API is more * clear to the user. * * Argument name matching is case and space insensitive * @private * @param {Object[]} originalArgs - the operation-s args list * @param {Object} newArgs - any inputted args */ function transformArgs(opArgsList, newArgs) { if (newArgs && Array.isArray(newArgs)) { return newArgs; } // Filter out arg values that are list subheadings - they are surrounded in []. // See Strings op for example. const opArgs = Object.assign([], opArgsList).map((a) => { if (Array.isArray(a.value)) { a.value = removeSubheadingsFromArray(a.value); } return a; }); // Reconcile object style arg info to fit operation args shape. if (newArgs) { Object.keys(newArgs).map((key) => { const index = opArgs.findIndex((arg) => { return arg.name.toLowerCase().replace(/ /g, "") === key.toLowerCase().replace(/ /g, ""); }); if (index > -1) { const argument = opArgs[index]; if (argument.type === "toggleString") { if (typeof newArgs[key] === "string") { argument.string = newArgs[key]; } else { argument.string = newArgs[key].string; argument.option = newArgs[key].option; } } else if (argument.type === "editableOption") { // takes key: "option", key: {name, val: "string"}, key: {name, val: [...]} argument.value = typeof newArgs[key] === "string" ? newArgs[key]: newArgs[key].value; } else { argument.value = newArgs[key]; } } }); } // Sanitise args return opArgs.map((arg) => { if (arg.type === "option") { // pick default option if not already chosen return typeof arg.value === "string" ? arg.value : arg.value[arg.defaultIndex ?? 0]; } if (arg.type === "editableOption") { return typeof arg.value === "string" ? arg.value : arg.value[arg.defaultIndex ?? 0].value; } if (arg.type === "toggleString") { // ensure string and option exist when user hasn't defined arg.string = arg.string || ""; arg.option = arg.option || arg.toggleValues[0]; return arg; } return arg.value; }); } /** * Ensure an input is a SyncDish object. * @param input */ function ensureIsDish(input) { if (!input) { return new NodeDish(); } if (input instanceof NodeDish) { return input; } else { return new NodeDish(input); } } /** * prepareOp: transform args, make input the right type. * Also convert any Buffers to ArrayBuffers. * @param opInstance - instance of the operation * @param input - operation input * @param args - operation args */ function prepareOp(opInstance, input, args) { const dish = ensureIsDish(input); // Transform object-style args to original args array const transformedArgs = transformArgs(opInstance.args, args); const transformedInput = dish.get(opInstance.inputType); return {transformedInput, transformedArgs}; } /** * createArgInfo * * Create an object of options for each argument in the given operation * * Argument names are converted to camel case for consistency. * * @param {Operation} op - the operation to extract args from * @returns {{}} - arrays of options for args. */ function createArgInfo(op) { const result = {}; op.args.forEach((a) => { if (a.type === "option" || a.type === "editableOption") { result[sentenceToCamelCase(a.name)] = { type: a.type, options: removeSubheadingsFromArray(a.value) }; } else if (a.type === "toggleString") { result[sentenceToCamelCase(a.name)] = { type: a.type, value: a.value, toggleValues: removeSubheadingsFromArray(a.toggleValues), }; } else { result[sentenceToCamelCase(a.name)] = { type: a.type, value: a.value, }; } }); return result; } /** * Wrap an operation to be consumed by node API. * Checks to see if run function is async or not. * new Operation().run() becomes operation() * Perform type conversion on input * @private * @param {Operation} Operation * @returns {Function} The operation's run function, wrapped in * some type conversion logic */ export function _wrap(OpClass) { // Check to see if class's run function is async. const opInstance = new OpClass(); const isAsync = opInstance.run.constructor.name === "AsyncFunction"; const isFlowControl = opInstance.flowControl; let wrapped; // If async, wrap must be async. if (isAsync) { /** * Async wrapped operation run function * @param {*} input * @param {Object | String[]} args - either in Object or normal args array * @returns {Promise} operation's output, on a Dish. * @throws {OperationError} if the operation throws one. */ wrapped = async (input, args=null) => { const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args); // SPECIAL CASE for Magic. Other flowControl operations will // not work because the opList is not passed in. if (isFlowControl) { opInstance.ingValues = transformedArgs; const state = { progress: 0, dish: ensureIsDish(transformedInput), opList: [opInstance], }; const updatedState = await opInstance.run(state); return new NodeDish({ value: updatedState.dish.value, type: opInstance.outputType, }); } const result = await opInstance.run(transformedInput, transformedArgs); return new NodeDish({ value: result, type: opInstance.outputType, }); }; } else { /** * wrapped operation run function * @param {*} input * @param {Object | String[]} args - either in Object or normal args array * @returns {SyncDish} operation's output, on a Dish. * @throws {OperationError} if the operation throws one. */ wrapped = (input, args=null) => { const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args); const result = opInstance.run(transformedInput, transformedArgs); return new NodeDish({ value: result, type: opInstance.outputType, }); }; } // used in chef.help wrapped.opName = OpClass.name; wrapped.args = createArgInfo(opInstance); // Used in NodeRecipe to check for flowControl ops wrapped.flowControl = isFlowControl; return wrapped; } /** * help: Give information about operations matching the given search term, * or inputted operation. * * @param {String || wrapped operation} input - the name of the operation to get help for. * Case and whitespace are ignored in search. * @returns {Object[]} Config of matching operations. */ export function help(input) { let searchTerm = false; if (typeof input === "string") { searchTerm = input; } else if (typeof input === "function") { searchTerm = input.opName; } if (!searchTerm) { return null; } let exactMatchExists = false; // Look for matches in operation name and description, listing name // matches first. const matches = Object.keys(OperationConfig) // hydrate operation: swap op name for op config object (with name) .map((m) => { const hydrated = OperationConfig[m]; hydrated.name = m; // flag up an exact name match. Only first exact match counts. if (!exactMatchExists) { exactMatchExists = sanitise(hydrated.name) === sanitise(searchTerm); } // Return hydrated along with what type of match it was return { hydrated, nameExactMatch: sanitise(hydrated.name) === sanitise(searchTerm), nameMatch: sanitise(hydrated.name).includes(sanitise(searchTerm)), descMatch: sanitise(hydrated.description).includes(sanitise(searchTerm)) }; }) // Filter out non-matches. If exact match exists, filter out all others. .filter((result) => { if (exactMatchExists) { return !!result.nameExactMatch; } return result.nameMatch || result.descMatch; }) // sort results with name match first .sort((a, b) => { const aInt = a.nameMatch ? 1 : 0; const bInt = b.nameMatch ? 1 : 0; return bInt - aInt; }) // extract just the hydrated config .map(result => result.hydrated); if (matches && matches.length) { // console.log(`${matches.length} result${matches.length > 1 ? "s" : ""} found.`); return matches; } // console.log("No results found."); return null; } /** * bake * * @param {*} input - some input for a recipe. * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig - * An operation, operation name, or an array of either. * @returns {NodeDish} of the result * @throws {TypeError} if invalid recipe given. */ export function bake(input, recipeConfig) { const recipe = new NodeRecipe(recipeConfig); const dish = ensureIsDish(input); return recipe.execute(dish); } /** * explainExcludedFunction * * Explain that the given operation is not included in the Node.js version. * @param {String} name - name of operation */ export function _explainExcludedFunction(name) { /** * Throw new error type with useful message. */ const func = () => { throw new ExcludedOperationError(`Sorry, the ${name} operation is not available in the Node.js version of CyberChef.`); }; // Add opName prop so NodeRecipe can handle it, just like wrap does. func.opName = name; return func; } ================================================ FILE: src/node/apiUtils.mjs ================================================ /** * Utility functions for the node environment * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ /** * someName => Somename * * @param {String} str = string to be altered * @returns {String} */ const capitalise = function capitalise(str) { // Don't edit names that start with 2+ caps if (/^[A-Z0-9]{2,}/g.test(str)) { return str; } // reserved. Don't change for now. if (str === "Return") { return str; } return `${str.charAt(0).toUpperCase()}${str.substr(1).toLowerCase()}`; }; /** * SomeName => someName * @param {String} name - string to be altered * @returns {String} decapitalised */ export function decapitalise(str) { // Don't decapitalise str that start with 2+ caps if (/^[A-Z0-9]{2,}/g.test(str)) { return str; } // reserved. Don't change for now. if (str === "Return") { return str; } return `${str.charAt(0).toLowerCase()}${str.substr(1)}`; } /** * Remove strings surrounded with [] from the given array. */ export function removeSubheadingsFromArray(array) { if (Array.isArray(array)) { return array.filter((i) => { if (typeof i === "string") { return !i.match(/^\[[\s\S]*\]$/); } return true; }); } } /** * Remove spaces, make lower case. * @param str */ export function sanitise(str) { return str.replace(/[/\s.-]/g, "").toLowerCase(); } /** * something like this => somethingLikeThis * ABC a sentence => ABCASentence */ export function sentenceToCamelCase(str) { return str.split(" ") .map((s, index) => { if (index === 0) { return decapitalise(s); } return capitalise(s); }) .reduce((prev, curr) => `${prev}${curr}`, ""); } ================================================ FILE: src/node/config/excludedOperations.mjs ================================================ /** * Operations to exclude from the Node API * * @author d98762656 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ export default [ // This functionality can be done more easily using JavaScript "Fork", "Merge", "Jump", "ConditionalJump", "Label", "Comment", // esprima doesn't work in .mjs "JavaScriptBeautify", "JavaScriptMinify", "JavaScriptParser", // Irrelevant in Node console "SyntaxHighlighter", ]; ================================================ FILE: src/node/config/scripts/generateNodeIndex.mjs ================================================ /** * This script generates the exports functionality for the node API. * * it exports chef as default, but all the wrapped operations as * other top level exports. * * @author d98762656 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ /* eslint no-console: 0 */ import fs from "fs"; import path from "path"; import * as operations from "../../../core/operations/index.mjs"; import { decapitalise } from "../../apiUtils.mjs"; import excludedOperations from "../excludedOperations.mjs"; const includedOperations = Object.keys(operations).filter((op => excludedOperations.indexOf(op) === -1)); const dir = path.join(`${process.cwd()}/src/node`); if (!fs.existsSync(dir)) { console.log("\nCWD: " + process.cwd()); console.log("Error: generateNodeIndex.mjs should be run from the project root"); console.log("Example> node --experimental-modules src/node/config/scripts/generateNodeIndex.mjs"); process.exit(1); } let code = `/** * THIS FILE IS AUTOMATICALLY GENERATED BY src/node/config/scripts/generateNodeIndex.mjs * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ /* eslint camelcase: 0 */ import NodeDish from "./NodeDish.mjs"; import { _wrap, help, bake, _explainExcludedFunction } from "./api.mjs"; import File from "./File.mjs"; import { OperationError, DishError, ExcludedOperationError } from "../core/errors/index.mjs"; import { // import as core_ to avoid name clashes after wrap. `; includedOperations.forEach((op) => { // prepend with core_ to avoid name collision later. code += ` ${op} as core_${op},\n`; }); code +=` } from "../core/operations/index.mjs"; global.File = File; /** * generateChef * * Creates decapitalised, wrapped ops in chef object for default export. */ function generateChef() { return { `; includedOperations.forEach((op) => { code += ` "${decapitalise(op)}": _wrap(core_${op}),\n`; }); excludedOperations.forEach((op) => { code += ` "${decapitalise(op)}": _explainExcludedFunction("${op}"),\n`; }); code += ` }; } const chef = generateChef(); // Add some additional features to chef object. chef.help = help; chef.Dish = NodeDish; // Define consts here so we can add to top-level export - wont allow // export of chef property. `; Object.keys(operations).forEach((op) => { code += `const ${decapitalise(op)} = chef.${decapitalise(op)};\n`; }); code +=` // Define array of all operations to create register for bake. const operations = [\n`; Object.keys(operations).forEach((op) => { code += ` ${decapitalise(op)},\n`; }); code += `]; chef.bake = bake; export default chef; // Operations as top level exports. export { operations, `; Object.keys(operations).forEach((op) => { code += ` ${decapitalise(op)},\n`; }); code += " NodeDish as Dish,\n"; code += " bake,\n"; code += " help,\n"; code += " OperationError,\n"; code += " ExcludedOperationError,\n"; code += " DishError,\n"; code += "};\n"; fs.writeFileSync( path.join(dir, "./index.mjs"), code ); ================================================ FILE: src/node/repl.mjs ================================================ /** * Create a REPL server for chef * * * @author d98762656 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import chef from "./index.mjs"; import repl from "repl"; /* eslint no-console: ["off"] */ console.log(` ______ __ ________ ____ / ____/_ __/ /_ ___ _____/ ____/ /_ ___ / __/ / / / / / / __ \\/ _ \\/ ___/ / / __ \\/ _ \\/ /_ / /___/ /_/ / /_/ / __/ / / /___/ / / / __/ __/ \\____/\\__, /_.___/\\___/_/ \\____/_/ /_/\\___/_/ /____/ `); const replServer = repl.start({ prompt: "chef > ", }); global.File = chef.File; Object.keys(chef).forEach((key) => { if (key !== "operations") { replServer.context[key] = chef[key]; } }); ================================================ FILE: src/node/wrapper.js ================================================ /** * Export the main ESM module as CommonJS * * * @author d98762656 [d98762625@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ module.exports = (async () => await import("./index.mjs"))(); module.exports.File = (async () => await import("./File.mjs"))(); ================================================ FILE: src/web/App.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Utils, { debounce } from "../core/Utils.mjs"; import {fromBase64} from "../core/lib/Base64.mjs"; import Manager from "./Manager.mjs"; import HTMLCategory from "./HTMLCategory.mjs"; import HTMLOperation from "./HTMLOperation.mjs"; import Split from "split.js"; import moment from "moment-timezone"; import cptable from "codepage"; /** * HTML view for CyberChef responsible for building the web page and dealing with all user * interactions. */ class App { /** * App constructor. * * @param {CatConf[]} categories - The list of categories and operations to be populated. * @param {Object.} operations - The list of operation configuration objects. * @param {String[]} defaultFavourites - A list of default favourite operations. * @param {Object} options - Default setting for app options. */ constructor(categories, operations, defaultFavourites, defaultOptions) { this.categories = categories; this.operations = operations; this.dfavourites = defaultFavourites; this.doptions = defaultOptions; this.options = Object.assign({}, defaultOptions); this.manager = new Manager(this); this.baking = false; this.autoBake_ = false; this.progress = 0; this.ingId = 0; this.appLoaded = false; this.workerLoaded = false; this.waitersLoaded = false; this.snackbars = []; } /** * This function sets up the stage and creates listeners for all events. * * @fires Manager#appstart */ setup() { document.dispatchEvent(this.manager.appstart); this.initialiseSplitter(); this.loadLocalStorage(); this.manager.options.applyPreferredColorScheme(); this.populateOperationsList(); this.manager.setup(); this.manager.output.saveBombe(); this.adjustComponentSizes(); this.setCompileMessage(); this.uriParams = this.getURIParams(); log.debug("App loaded"); this.appLoaded = true; this.loaded(); } /** * Fires once all setup activities have completed. * * @fires Manager#apploaded */ loaded() { // Check that both the app and the worker have loaded successfully, and that // we haven't already loaded before attempting to remove the loading screen. if (!this.workerLoaded || !this.appLoaded || !this.waitersLoaded || !document.getElementById("loader-wrapper")) return; // Load state from URI this.loadURIParams(this.uriParams); // Trigger CSS animations to remove preloader document.body.classList.add("loaded"); // Wait for animations to complete then remove the preloader and loaded style // so that the animations for existing elements don't play again. setTimeout(function() { document.getElementById("loader-wrapper").remove(); document.body.classList.remove("loaded"); // Bake initial input this.manager.input.bakeAll(); }.bind(this), 1000); // Clear the loading message interval clearInterval(window.loadingMsgsInt); // Remove the loading error handler window.removeEventListener("error", window.loadingErrorHandler); document.dispatchEvent(this.manager.apploaded); this.manager.input.calcMaxTabs(); this.manager.output.calcMaxTabs(); } /** * An error handler for displaying the error to the user. * * @param {Error} err * @param {boolean} [logToConsole=false] */ handleError(err, logToConsole) { if (logToConsole) log.error(err); const msg = err.displayStr || err.toString(); this.alert(Utils.escapeHtml(msg), this.options.errorTimeout, !this.options.showErrors); } /** * Asks the ChefWorker to bake the current input using the current recipe. * * @param {boolean} [step] - Set to true if we should only execute one operation instead of the * whole recipe. */ bake(step=false) { if (this.baking) return; // Reset attemptHighlight flag this.options.attemptHighlight = true; // Remove all current indicators this.manager.recipe.updateBreakpointIndicator(false); this.manager.worker.bake( this.getRecipeConfig(), // The configuration of the recipe this.options, // Options set by the user this.progress, // The current position in the recipe step // Whether or not to take one step or execute the whole recipe ); } /** * Runs Auto Bake if it is set. */ autoBake() { if (this.baking) { this.manager.worker.cancelBakeForAutoBake(); this.baking = false; } if (this.autoBake_) { log.debug("Auto-baking"); this.manager.worker.bakeInputs({ nums: [this.manager.tabs.getActiveTab("input")], step: false }); } else { this.manager.controls.showStaleIndicator(); } } /** * Executes the next step of the recipe. */ step() { if (this.baking) return; // Reset status using cancelBake this.manager.worker.cancelBake(true, false); const activeTab = this.manager.tabs.getActiveTab("input"); if (activeTab === -1) return; let progress = 0; if (this.manager.output.outputs[activeTab].progress !== false) { log.error(this.manager.output.outputs[activeTab]); progress = this.manager.output.outputs[activeTab].progress; } this.manager.input.inputWorker.postMessage({ action: "step", data: { activeTab: activeTab, progress: progress + 1 } }); } /** * Runs a silent bake, forcing the browser to load and cache all the relevant JavaScript code needed * to do a real bake. * * The output will not be modified (hence "silent" bake). This will only actually execute the recipe * if auto-bake is enabled, otherwise it will just wake up the ChefWorker with an empty recipe. */ silentBake() { let recipeConfig = []; if (this.autoBake_) { // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled // for a good reason. recipeConfig = this.getRecipeConfig(); } this.manager.worker.silentBake(recipeConfig); } /** * Sets the user's input data. * * @param {string} input - The string to set the input to */ setInput(input) { // Get the currently active tab. // If there isn't one, assume there are no inputs so use inputNum of 1 let inputNum = this.manager.tabs.getActiveTab("input"); if (inputNum === -1) inputNum = 1; this.manager.input.updateInputValue(inputNum, input); this.manager.input.inputWorker.postMessage({ action: "setInput", data: { inputNum: inputNum, silent: true } }); } /** * Populates the operations accordion list with the categories and operations specified in the * view constructor. * * @fires Manager#oplistcreate */ populateOperationsList() { // Move edit button away before we overwrite it document.body.appendChild(document.getElementById("edit-favourites")); let html = ""; let i; for (i = 0; i < this.categories.length; i++) { const catConf = this.categories[i], selected = i === 0, cat = new HTMLCategory(catConf.name, selected); for (let j = 0; j < catConf.ops.length; j++) { const opName = catConf.ops[j]; if (!(opName in this.operations)) { log.warn(`${opName} could not be found.`); continue; } const op = new HTMLOperation(opName, this.operations[opName], this, this.manager); cat.addOperation(op); } html += cat.toHtml(); } document.getElementById("categories").innerHTML = html; const opLists = document.querySelectorAll("#categories .op-list"); for (i = 0; i < opLists.length; i++) { opLists[i].dispatchEvent(this.manager.oplistcreate); } // Add edit button to first category (Favourites) const favCat = document.querySelector("#categories a"); favCat.appendChild(document.getElementById("edit-favourites")); favCat.setAttribute("data-help-title", "Favourite operations"); favCat.setAttribute("data-help", `

This category displays your favourite operations.

  • To add: drag an operation over the Favourites category
  • To reorder: Click on the 'Edit favourites' button and drag operations up and down in the list provided
  • To remove: Click on the 'Edit favourites' button and hit the delete button next to the operation you want to remove
`); } /** * Sets up the adjustable splitter to allow the user to resize areas of the page. * * @param {boolean} [minimise=false] - Set this flag if attempting to minimise frames to 0 width */ initialiseSplitter(minimise=false) { if (this.columnSplitter) this.columnSplitter.destroy(); if (this.ioSplitter) this.ioSplitter.destroy(); this.columnSplitter = Split(["#operations", "#recipe", "#IO"], { sizes: [20, 30, 50], minSize: minimise ? [0, 0, 0] : [240, 310, 450], gutterSize: 4, expandToMin: true, onDrag: debounce(function() { this.adjustComponentSizes(); }, 50, "dragSplitter", this, []) }); this.ioSplitter = Split(["#input", "#output"], { direction: "vertical", gutterSize: 4, minSize: minimise ? [0, 0] : [100, 100] }); this.adjustComponentSizes(); } /** * Loads the information previously saved to the HTML5 local storage object so that user options * and favourites can be restored. */ loadLocalStorage() { // Load options let lOptions; if (this.isLocalStorageAvailable() && localStorage.options !== undefined) { lOptions = JSON.parse(localStorage.options); } this.manager.options.load(lOptions); // Load favourites this.loadFavourites(); } /** * Loads the user's favourite operations from the HTML5 local storage object and populates the * Favourites category with them. * If the user currently has no saved favourites, the defaults from the view constructor are used. */ loadFavourites() { let favourites; if (this.isLocalStorageAvailable()) { favourites = localStorage?.favourites?.length > 2 ? JSON.parse(localStorage.favourites) : this.dfavourites; favourites = this.validFavourites(favourites); this.saveFavourites(favourites); } else { favourites = this.dfavourites; } const favCat = this.categories.filter(function(c) { return c.name === "Favourites"; })[0]; if (favCat) { favCat.ops = favourites; } else { this.categories.unshift({ name: "Favourites", ops: favourites }); } } /** * Filters the list of favourite operations that the user had stored and removes any that are no * longer available. The user is notified if this is the case. * @param {string[]} favourites - A list of the user's favourite operations * @returns {string[]} A list of the valid favourites */ validFavourites(favourites) { const validFavs = []; for (let i = 0; i < favourites.length; i++) { if (favourites[i] in this.operations) { validFavs.push(favourites[i]); } else { this.alert(`The operation "${Utils.escapeHtml(favourites[i])}" is no longer available. ` + "It has been removed from your favourites."); } } return validFavs; } /** * Saves a list of favourite operations to the HTML5 local storage object. * * @param {string[]} favourites - A list of the user's favourite operations */ saveFavourites(favourites) { if (!this.isLocalStorageAvailable()) { this.alert( "Your security settings do not allow access to local storage so your favourites cannot be saved.", 5000 ); return false; } localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites))); } /** * Resets favourite operations back to the default as specified in the view constructor and * refreshes the operation list. */ resetFavourites() { this.saveFavourites(this.dfavourites); this.loadFavourites(); this.populateOperationsList(); this.manager.recipe.initialiseOperationDragNDrop(); } /** * Adds an operation to the user's favourites. * * @param {string} name - The name of the operation */ addFavourite(name) { const favourites = JSON.parse(localStorage.favourites); if (favourites.indexOf(name) >= 0) { this.alert(`'${name}' is already in your favourites`, 3000); return; } favourites.push(name); this.saveFavourites(favourites); this.loadFavourites(); this.populateOperationsList(); this.manager.recipe.initialiseOperationDragNDrop(); } /** * Gets the URI params from the window and parses them to extract the actual values. * * @returns {object} */ getURIParams() { // Load query string or hash from URI (depending on which is populated) // We prefer getting the hash by splitting the href rather than referencing // location.hash as some browsers (Firefox) automatically URL decode it, // which cause issues. const params = window.location.search || window.location.href.split("#")[1] || window.location.hash; const parsedParams = Utils.parseURIParams(params); return parsedParams; } /** * Searches the URI parameters for recipe and input parameters. * If recipe is present, replaces the current recipe with the recipe provided in the URI. * If input is present, decodes and sets the input to the one provided in the URI. * If character encodings are present, sets them appropriately. * If theme is present, uses the theme. * * @param {Object} params * @fires Manager#statechange */ loadURIParams(params=this.getURIParams()) { this.uriParams = params; // Read in recipe from URI params if (this.uriParams.recipe) { try { const recipeConfig = Utils.parseRecipeConfig(this.uriParams.recipe); this.setRecipeConfig(recipeConfig); } catch (err) {} } else if (this.uriParams.op) { // If there's no recipe, look for single operations this.manager.recipe.clearRecipe(); // Search for nearest match and add it const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false); if (matchedOps.length) { this.manager.recipe.addOperation(matchedOps[0].name); } // Populate search with the string const search = document.getElementById("search"); search.value = this.uriParams.op; search.dispatchEvent(new Event("search")); } // Input Character Encoding // Must be set before the input is loaded if (this.uriParams.ienc) { this.manager.input.chrEncChange(parseInt(this.uriParams.ienc, 10), true, true); } // Output Character Encoding if (this.uriParams.oenc) { this.manager.output.chrEncChange(parseInt(this.uriParams.oenc, 10), true); } // Input EOL sequence if (this.uriParams.ieol) { this.manager.input.eolChange(this.uriParams.ieol, true); } // Output EOL sequence if (this.uriParams.oeol) { this.manager.output.eolChange(this.uriParams.oeol, true); } // Read in input data from URI params if (this.uriParams.input) { try { let inputVal; const inputChrEnc = this.manager.input.getChrEnc(); const inputData = fromBase64(this.uriParams.input, null, "byteArray"); if (inputChrEnc > 0) { inputVal = cptable.utils.decode(inputChrEnc, inputData); } else { inputVal = Utils.byteArrayToChars(inputData); } this.setInput(inputVal); } catch (err) {} } // Read in theme from URI params if (this.uriParams.theme) { this.manager.options.changeTheme(Utils.escapeHtml(this.uriParams.theme)); } else { this.manager.options.applyPreferredColorScheme(); } window.dispatchEvent(this.manager.statechange); } /** * Returns the next ingredient ID and increments it for next time. * * @returns {number} */ nextIngId() { return this.ingId++; } /** * Gets the current recipe configuration. * * @returns {Object[]} */ getRecipeConfig() { return this.manager.recipe.getConfig(); } /** * Given a recipe configuration, sets the recipe to that configuration. * * @fires Manager#statechange * @param {Object[]} recipeConfig - The recipe configuration */ setRecipeConfig(recipeConfig) { document.getElementById("rec-list").innerHTML = null; for (let i = 0; i < recipeConfig.length; i++) { const item = this.manager.recipe.addOperation(recipeConfig[i].op); // Populate arguments log.debug(`Populating arguments for ${recipeConfig[i].op}`); const args = item.querySelectorAll(".arg"); for (let j = 0; j < args.length; j++) { if (recipeConfig[i].args[j] === undefined) continue; if (args[j].getAttribute("type") === "checkbox") { // checkbox args[j].checked = recipeConfig[i].args[j]; } else if (args[j].classList.contains("toggle-string")) { // toggleString args[j].value = recipeConfig[i].args[j].string; args[j].parentNode.parentNode.querySelector("button").innerHTML = Utils.escapeHtml(recipeConfig[i].args[j].option); } else { // all others args[j].value = recipeConfig[i].args[j]; } } // Set disabled and breakpoint if (recipeConfig[i].disabled) { item.querySelector(".disable-icon").click(); } if (recipeConfig[i].breakpoint) { item.querySelector(".breakpoint").click(); } this.manager.recipe.triggerArgEvents(item); this.progress = 0; } } /** * Resets the splitter positions to default. */ resetLayout() { this.columnSplitter.setSizes([20, 30, 50]); this.ioSplitter.setSizes([50, 50]); this.adjustComponentSizes(); } /** * Adjust components to fit their containers. */ adjustComponentSizes() { this.manager.recipe.adjustWidth(); this.manager.input.calcMaxTabs(); this.manager.output.calcMaxTabs(); this.manager.controls.calcControlsHeight(); } /** * Sets the compile message. */ setCompileMessage() { // Display time since last build and compile message const now = new Date(), msSinceCompile = now.getTime() - window.compileTime, timeSinceCompile = moment.duration(msSinceCompile, "milliseconds").humanize(); // Calculate previous version to compare to const prev = PKG_VERSION.split(".").map(n => { return parseInt(n, 10); }); if (prev[2] > 0) prev[2]--; else if (prev[1] > 0) prev[1]--; else prev[0]--; // const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`; let compileInfo = `Last build: ${timeSinceCompile.substring(0, 1).toUpperCase() + timeSinceCompile.substring(1)} ago`; if (window.compileMessage !== "") { compileInfo += " - " + window.compileMessage; } const notice = document.getElementById("notice"); notice.innerHTML = compileInfo; notice.setAttribute("title", Utils.stripHtmlTags(window.compileMessage)); notice.setAttribute("data-help-title", "Last build"); 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."); } /** * Determines whether the browser supports Local Storage and if it is accessible. * * @returns {boolean} */ isLocalStorageAvailable() { try { if (!localStorage) return false; return true; } catch (err) { // Access to LocalStorage is denied return false; } } /** * Pops up a message to the user and writes it to the console log. * * @param {string} str - The message to display (HTML supported) * @param {number} [timeout=0] - The number of milliseconds before the alert closes automatically * 0 for never (until the user closes it) * @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the * console * * @example * // Pops up a box with the message "Error: Something has gone wrong!" that will need to be * // dismissed by the user. * this.alert("Error: Something has gone wrong!", 0); * * // Pops up a box with the message "Happy Christmas!" that will disappear after 5 seconds. * this.alert("Happy Christmas!", 5000); */ alert(str, timeout=0, silent=false) { const time = new Date(); log.info("[" + time.toLocaleString() + "] " + str); if (silent) return; this.snackbars.push($.snackbar({ content: str, timeout: timeout, htmlAllowed: true, onClose: () => { this.snackbars.shift().remove(); } })); } /** * Pops up a box asking the user a question and sending the answer to a specified callback function. * * @param {string} title - The title of the box * @param {string} body - The question (HTML supported) * @param {string} accept - The text of the accept button * @param {string} reject - The text of the reject button * @param {function} callback - A function accepting one boolean argument which handles the * response e.g. function(answer) {...} * @param {Object} [scope=this] - The object to bind to the callback function * * @example * // Pops up a box asking if the user would like a cookie. Prints the answer to the console. * this.confirm("Question", "Would you like a cookie?", "Yes", "No", function(answer) {console.log(answer);}); */ confirm(title, body, accept, reject, callback, scope) { scope = scope || this; document.getElementById("confirm-title").innerHTML = title; document.getElementById("confirm-body").innerHTML = body; document.getElementById("confirm-yes").innerText = accept; document.getElementById("confirm-no").innerText = reject; document.getElementById("confirm-modal").style.display = "block"; this.confirmClosed = false; $("#confirm-modal").modal() .one("show.bs.modal", function(e) { this.confirmClosed = false; }.bind(this)) .one("click", "#confirm-yes", function() { this.confirmClosed = true; callback.bind(scope)(true); $("#confirm-modal").modal("hide"); }.bind(this)) .one("click", "#confirm-no", function() { this.confirmClosed = true; callback.bind(scope)(false); }.bind(this)) .one("hide.bs.modal", function(e) { if (!this.confirmClosed) { callback.bind(scope)(undefined); } this.confirmClosed = true; }.bind(this)); } /** * Handler for CyerChef statechange events. * Fires whenever the input or recipe changes in any way. * * @listens Manager#statechange * @param {event} e */ stateChange(e) { debounce(function() { this.progress = 0; this.autoBake(); this.updateURL(true, null, true); }, 20, "stateChange", this, [])(); } /** * Update the page title and URL to contain the new recipe * * @param {boolean} includeInput * @param {string} [input=null] * @param {boolean} [changeUrl=true] */ updateURL(includeInput, input=null, changeUrl=true) { // Set title const recipeConfig = this.getRecipeConfig(); let title = "CyberChef"; if (recipeConfig.length === 1) { title = `${recipeConfig[0].op} - ${title}`; } else if (recipeConfig.length > 1) { // See how long the full recipe is const ops = recipeConfig.map(op => op.op).join(", "); if (ops.length < 45) { title = `${ops} - ${title}`; } else { // If it's too long, just use the first one and say how many more there are title = `${recipeConfig[0].op}, ${recipeConfig.length - 1} more - ${title}`; } } document.title = title; // Update the current history state (not creating a new one) if (this.options.updateUrl && changeUrl) { this.lastStateUrl = this.manager.controls.generateStateUrl(true, includeInput, input, recipeConfig); window.history.replaceState({}, title, this.lastStateUrl); } } /** * Handler for the history popstate event. * Reloads parameters from the URL. * * @param {event} e */ popState(e) { this.loadURIParams(); } } export default App; ================================================ FILE: src/web/HTMLCategory.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ /** * Object to handle the creation of operation categories. */ class HTMLCategory { /** * HTMLCategory constructor. * * @param {string} name - The name of the category. * @param {boolean} selected - Whether this category is pre-selected or not. */ constructor(name, selected) { this.name = name; this.selected = selected; this.opList = []; } /** * Adds an operation to this category. * * @param {HTMLOperation} operation - The operation to add. */ addOperation(operation) { this.opList.push(operation); } /** * Renders the category and all operations within it in HTML. * * @returns {string} */ toHtml() { const catName = "cat" + this.name.replace(/[\s/\-:_]/g, ""); let html = `
${this.name}
    `; for (let i = 0; i < this.opList.length; i++) { html += this.opList[i].toStubHtml(); } html += "
"; return html; } } export default HTMLCategory; ================================================ FILE: src/web/HTMLIngredient.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Utils from "../core/Utils.mjs"; /** * Object to handle the creation of operation ingredients. */ class HTMLIngredient { /** * HTMLIngredient constructor. * * @param {Object} config - The configuration object for this ingredient. * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(config, app, manager) { this.app = app; this.manager = manager; this.name = config.name; this.type = config.type; this.value = config.value; this.disabled = config.disabled || false; this.hint = config.hint || false; this.rows = config.rows || false; this.target = config.target; this.defaultIndex = config.defaultIndex || 0; this.maxLength = config.maxLength || null; this.toggleValues = config.toggleValues; this.ingId = this.app.nextIngId(); this.id = "ing-" + this.ingId; this.tabIndex = this.ingId + 2; // Input = 1, Search = 2 this.min = (typeof config.min === "number") ? config.min : ""; this.max = (typeof config.max === "number") ? config.max : ""; this.step = config.step || 1; } /** * Renders the ingredient in HTML. * * @returns {string} */ toHtml() { let html = "", i, m, eventFn; const hintHtml = this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""; switch (this.type) { case "string": case "binaryString": case "byteArray": html += `
`; break; case "shortString": case "binaryShortString": html += `
`; break; case "toggleString": html += `
`; break; case "number": html += `
`; break; case "boolean": html += `
`; break; case "option": html += `
`; break; case "populateOption": case "populateMultiOption": html += `
`; eventFn = this.type === "populateMultiOption" ? this.populateMultiOptionChange : this.populateOptionChange; this.manager.addDynamicListener("#" + this.id, "change", eventFn, this); break; case "editableOption": html += `
`; this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); break; case "editableOptionShort": html += `
`; this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); break; case "text": html += `
`; break; case "argSelector": html += `
`; this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this); break; case "label": html += `
`; break; default: break; } return html; } /** * Handler for populate option changes. * Populates the relevant argument with the specified value. * * @param {event} e */ populateOptionChange(e) { e.preventDefault(); e.stopPropagation(); const el = e.target; const op = el.parentNode.parentNode; const target = op.querySelectorAll(".arg")[this.target]; const popVal = el.childNodes[el.selectedIndex].getAttribute("populate-value"); if (popVal !== "") target.value = popVal; const evt = new Event("change"); target.dispatchEvent(evt); this.manager.recipe.ingChange(); } /** * Handler for populate multi option changes. * Populates the relevant arguments with the specified values. * * @param {event} e */ populateMultiOptionChange(e) { e.preventDefault(); e.stopPropagation(); const el = e.target; const op = el.parentNode.parentNode; const args = op.querySelectorAll(".arg"); const targets = this.target.map(i => args[i]); const vals = JSON.parse(el.childNodes[el.selectedIndex].getAttribute("populate-value")); const evt = new Event("change"); for (let i = 0; i < targets.length; i++) { targets[i].value = vals[i]; } // Fire change event after all targets have been assigned this.manager.recipe.ingChange(); // Send change event for each target once all have been assigned, to update the label placement. for (const target of targets) { target.dispatchEvent(evt); } } /** * Handler for editable option clicks. * Populates the input box with the selected value. * * @param {event} e */ editableOptionClick(e) { e.preventDefault(); e.stopPropagation(); const link = e.target, input = link.parentNode.parentNode.parentNode.querySelector("input"); input.value = link.getAttribute("value"); const evt = new Event("change"); input.dispatchEvent(evt); this.manager.recipe.ingChange(); } /** * Handler for argument selector changes. * Shows or hides the relevant arguments for this operation. * * @param {event} e */ argSelectorChange(e) { e.preventDefault(); e.stopPropagation(); const option = e.target.options[e.target.selectedIndex]; const op = e.target.closest(".operation"); const args = op.querySelectorAll(".ingredients .form-group"); const turnon = JSON.parse(option.getAttribute("turnon")); const turnoff = JSON.parse(option.getAttribute("turnoff")); args.forEach((arg, i) => { if (turnon.includes(i)) { arg.classList.remove("d-none"); } if (turnoff.includes(i)) { arg.classList.add("d-none"); } }); } } export default HTMLIngredient; ================================================ FILE: src/web/HTMLOperation.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import HTMLIngredient from "./HTMLIngredient.mjs"; import Utils from "../core/Utils.mjs"; import url from "url"; /** * Object to handle the creation of operations. */ class HTMLOperation { /** * HTMLOperation constructor. * * @param {string} name - The name of the operation. * @param {Object} config - The configuration object for this operation. * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(name, config, app, manager) { this.app = app; this.manager = manager; this.name = name; this.description = config.description; this.infoURL = config.infoURL; this.manualBake = config.manualBake || false; this.config = config; this.ingList = []; for (let i = 0; i < config.args.length; i++) { const ing = new HTMLIngredient(config.args[i], this.app, this.manager); this.ingList.push(ing); } } /** * Renders the operation in HTML as a stub operation with no ingredients. * * @returns {string} */ toStubHtml(removeIcon) { let html = "
  • ${titleFromWikiLink(this.infoURL)}` : ""; html += ` data-container='body' data-toggle='popover' data-placement='right' data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover' data-boundary='viewport'`; } html += ">" + this.name; if (removeIcon) { html += "delete"; } html += "
  • "; return html; } /** * Renders the operation in HTML as a full operation with ingredients. * * @returns {string} */ toFullHtml() { let html = `
    ${Utils.escapeHtml(this.name)}
    `; for (let i = 0; i < this.ingList.length; i++) { html += this.ingList[i].toHtml(); } html += `
    pause not_interested keyboard_arrow_up
     
    `; return html; } /** * Highlights searched strings in the name and description of the operation. * * @param {[[number]]} nameIdxs - Indexes of the search strings in the operation name [[start, length]] * @param {[[number]]} descIdxs - Indexes of the search strings in the operation description [[start, length]] */ highlightSearchStrings(nameIdxs, descIdxs) { if (nameIdxs.length && typeof nameIdxs[0][0] === "number") { let opName = "", pos = 0; nameIdxs.forEach(idxs => { const [start, length] = idxs; if (typeof start !== "number") return; opName += this.name.slice(pos, start) + "" + this.name.slice(start, start + length) + ""; pos = start + length; }); opName += this.name.slice(pos, this.name.length); this.name = opName; } if (this.description && descIdxs.length && descIdxs[0][0] >= 0) { // Find HTML tag offsets const re = /<[^>]+>/g; let match; while ((match = re.exec(this.description))) { // If the search string occurs within an HTML tag, return without highlighting it. const inHTMLTag = descIdxs.reduce((acc, idxs) => { const start = idxs[0]; return start >= match.index && start <= (match.index + match[0].length); }, false); if (inHTMLTag) return; } let desc = "", pos = 0; descIdxs.forEach(idxs => { const [start, length] = idxs; desc += this.description.slice(pos, start) + "" + this.description.slice(start, start + length) + ""; pos = start + length; }); desc += this.description.slice(pos, this.description.length); this.description = desc; } } } /** * Given a URL for a Wikipedia (or other wiki) page, this function returns a link to that page. * * @param {string} urlStr * @returns {string} */ function titleFromWikiLink(urlStr) { const urlObj = url.parse(urlStr); let wikiName = "", pageTitle = ""; switch (urlObj.host) { case "forensics.wiki": wikiName = "Forensics Wiki"; pageTitle = Utils.toTitleCase(urlObj.path.replace(/\//g, "").replace(/_/g, " ")); break; case "wikipedia.org": wikiName = "Wikipedia"; pageTitle = urlObj.pathname.substr(6).replace(/_/g, " "); // Chop off '/wiki/' break; default: // Not a wiki link, return full URL return `More Informationopen_in_new`; } return `${pageTitle}open_in_new on ${wikiName}`; } export default HTMLOperation; ================================================ FILE: src/web/Manager.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import WorkerWaiter from "./waiters/WorkerWaiter.mjs"; import WindowWaiter from "./waiters/WindowWaiter.mjs"; import ControlsWaiter from "./waiters/ControlsWaiter.mjs"; import RecipeWaiter from "./waiters/RecipeWaiter.mjs"; import OperationsWaiter from "./waiters/OperationsWaiter.mjs"; import InputWaiter from "./waiters/InputWaiter.mjs"; import OutputWaiter from "./waiters/OutputWaiter.mjs"; import OptionsWaiter from "./waiters/OptionsWaiter.mjs"; import HighlighterWaiter from "./waiters/HighlighterWaiter.mjs"; import SeasonalWaiter from "./waiters/SeasonalWaiter.mjs"; import BindingsWaiter from "./waiters/BindingsWaiter.mjs"; import BackgroundWorkerWaiter from "./waiters/BackgroundWorkerWaiter.mjs"; import TabWaiter from "./waiters/TabWaiter.mjs"; import TimingWaiter from "./waiters/TimingWaiter.mjs"; /** * This object controls the Waiters responsible for handling events from all areas of the app. */ class Manager { /** * Manager constructor. * * @param {App} app - The main view object for CyberChef. */ constructor(app) { this.app = app; // Define custom events /** * @event Manager#appstart */ this.appstart = new CustomEvent("appstart", {bubbles: true}); /** * @event Manager#apploaded */ this.apploaded = new CustomEvent("apploaded", {bubbles: true}); /** * @event Manager#operationadd */ this.operationadd = new CustomEvent("operationadd", {bubbles: true}); /** * @event Manager#operationremove */ this.operationremove = new CustomEvent("operationremove", {bubbles: true}); /** * @event Manager#oplistcreate */ this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true}); /** * @event Manager#statechange */ this.statechange = new CustomEvent("statechange", {bubbles: true}); // Define Waiter objects to handle various areas this.timing = new TimingWaiter(this.app, this); this.worker = new WorkerWaiter(this.app, this); this.window = new WindowWaiter(this.app); this.controls = new ControlsWaiter(this.app, this); this.recipe = new RecipeWaiter(this.app, this); this.ops = new OperationsWaiter(this.app, this); this.tabs = new TabWaiter(this.app, this); this.input = new InputWaiter(this.app, this); this.output = new OutputWaiter(this.app, this); this.options = new OptionsWaiter(this.app, this); this.highlighter = new HighlighterWaiter(this.app, this); this.seasonal = new SeasonalWaiter(this.app, this); this.bindings = new BindingsWaiter(this.app, this); this.background = new BackgroundWorkerWaiter(this.app, this); // Object to store dynamic handlers to fire on elements that may not exist yet this.dynamicHandlers = {}; this.initialiseEventListeners(); } /** * Sets up the various components and listeners. */ setup() { this.input.setupInputWorker(); this.input.addInput(true); this.worker.setupChefWorker(); this.recipe.initialiseOperationDragNDrop(); this.controls.initComponents(); this.controls.autoBakeChange(); this.bindings.updateKeybList(); this.background.registerChefWorker(); this.seasonal.load(); this.confirmWaitersLoaded(); } /** * Confirms that all Waiters have loaded correctly. */ confirmWaitersLoaded() { if (this.tabs.getActiveTab("input") >= 0 && this.tabs.getActiveTab("output") >= 0) { log.debug("Waiters loaded"); this.app.waitersLoaded = true; this.app.loaded(); } else { // Not loaded yet, try again soon setTimeout(this.confirmWaitersLoaded.bind(this), 10); } } /** * Main function to handle the creation of the event listeners. */ initialiseEventListeners() { // Global window.addEventListener("resize", this.window.windowResize.bind(this.window)); window.addEventListener("blur", this.window.windowBlur.bind(this.window)); window.addEventListener("focus", this.window.windowFocus.bind(this.window)); window.addEventListener("statechange", this.app.stateChange.bind(this.app)); window.addEventListener("popstate", this.app.popState.bind(this.app)); window.addEventListener("message", this.input.handlePostMessage.bind(this.input)); // Controls document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls)); document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls)); document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls)); document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls)); document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls)); document.getElementById("save-link-input-checkbox").addEventListener("change", this.controls.sliCheckChange.bind(this.controls)); document.getElementById("load").addEventListener("click", this.controls.loadClick.bind(this.controls)); document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls)); document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls)); document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls)); document.getElementById("hide-icon").addEventListener("click", this.controls.hideRecipeArgsClick.bind(this.recipe)); document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls)); this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls); // Operations this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops); this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops); document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops)); document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops)); document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops)); this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops); this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe); // Recipe this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe); this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe); this.addDynamicListener(".hide-args-icon", "click", this.recipe.hideArgsClick, this.recipe); this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe); this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe); this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe)); this.addDynamicListener("textarea.arg", "dragover", this.recipe.textArgDragover, this.recipe); this.addDynamicListener("textarea.arg", "dragleave", this.recipe.textArgDragLeave, this.recipe); this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe); // Input document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input)); document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input)); this.addListeners("#input-wrapper", "dragover", this.input.inputDragover, this.input); this.addListeners("#input-wrapper", "dragleave", this.input.inputDragleave, this.input); this.addListeners("#input-wrapper", "drop", this.input.inputDrop, this.input); document.getElementById("btn-new-tab").addEventListener("click", this.input.addInputClick.bind(this.input)); document.getElementById("btn-previous-input-tab").addEventListener("mousedown", this.input.previousTabClick.bind(this.input)); document.getElementById("btn-next-input-tab").addEventListener("mousedown", this.input.nextTabClick.bind(this.input)); this.addListeners("#btn-next-input-tab,#btn-previous-input-tab", "mouseup", this.input.tabMouseUp, this.input); this.addListeners("#btn-next-input-tab,#btn-previous-input-tab", "mouseout", this.input.tabMouseUp, this.input); document.getElementById("btn-go-to-input-tab").addEventListener("click", this.input.goToTab.bind(this.input)); document.getElementById("btn-find-input-tab").addEventListener("click", this.input.findTab.bind(this.input)); this.addDynamicListener("#input-tabs li .input-tab-content", "click", this.input.changeTabClick, this.input); document.getElementById("input-show-pending").addEventListener("change", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-show-loading").addEventListener("change", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-show-loaded").addEventListener("change", this.input.filterTabSearch.bind(this.input)); this.addListeners("#input-filter-content,#input-filter-filename", "click", this.input.filterOptionClick, this.input); document.getElementById("input-filter").addEventListener("change", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-filter").addEventListener("keyup", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-num-results").addEventListener("change", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-num-results").addEventListener("keyup", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-filter-refresh").addEventListener("click", this.input.filterTabSearch.bind(this.input)); this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input); // Output document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); document.getElementById("save-all-to-file").addEventListener("click", this.output.saveAllClick.bind(this.output)); document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output)); document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output)); document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); this.addDynamicListener(".extract-file,.extract-file i", "click", this.output.extractFileClick, this.output); this.addDynamicListener("#output-tabs-wrapper #output-tabs li .output-tab-content", "click", this.output.changeTabClick, this.output); document.getElementById("btn-previous-output-tab").addEventListener("mousedown", this.output.previousTabClick.bind(this.output)); document.getElementById("btn-next-output-tab").addEventListener("mousedown", this.output.nextTabClick.bind(this.output)); this.addListeners("#btn-next-output-tab,#btn-previous-output-tab", "mouseup", this.output.tabMouseUp, this.output); this.addListeners("#btn-next-output-tab,#btn-previous-output-tab", "mouseout", this.output.tabMouseUp, this.output); document.getElementById("btn-go-to-output-tab").addEventListener("click", this.output.goToTab.bind(this.output)); document.getElementById("btn-find-output-tab").addEventListener("click", this.output.findTab.bind(this.output)); document.getElementById("output-show-pending").addEventListener("change", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-show-baking").addEventListener("change", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-show-baked").addEventListener("change", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-show-stale").addEventListener("change", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-show-errored").addEventListener("change", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-content-filter").addEventListener("change", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-content-filter").addEventListener("keyup", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-num-results").addEventListener("change", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-num-results").addEventListener("keyup", this.output.filterTabSearch.bind(this.output)); document.getElementById("output-filter-refresh").addEventListener("click", this.output.filterTabSearch.bind(this.output)); this.addDynamicListener(".output-filter-result", "click", this.output.filterItemClick, this.output); // Options document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options)); this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.switchChange, this.options); this.addDynamicListener(".option-item input[type=checkbox]#wordWrap", "change", this.options.setWordWrap, this.options); this.addDynamicListener(".option-item input[type=checkbox]#useMetaKey", "change", this.bindings.updateKeybList, this.bindings); this.addDynamicListener(".option-item input[type=checkbox]#showCatCount", "change", this.ops.setCatCount, this.ops); this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options); this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options); this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options)); // Misc window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); } /** * Adds an event listener to each element in the specified group. * * @param {string} selector - A selector string for the element group to add the event to, see * this.getAll() * @param {string} eventType - The event to listen for * @param {function} callback - The function to execute when the event is triggered * @param {Object} [scope=this] - The object to bind to the callback function * * @example * // Calls the clickable function whenever any element with the .clickable class is clicked * this.addListeners(".clickable", "click", this.clickable, this); */ addListeners(selector, eventType, callback, scope) { scope = scope || this; [].forEach.call(document.querySelectorAll(selector), function(el) { el.addEventListener(eventType, callback.bind(scope)); }); } /** * Adds multiple event listeners to the specified element. * * @param {string} selector - A selector string for the element to add the events to * @param {string} eventTypes - A space-separated string of all the event types to listen for * @param {function} callback - The function to execute when the events are triggered * @param {Object} [scope=this] - The object to bind to the callback function * * @example * // Calls the search function whenever the keyup, paste or search events are triggered on the * // search element * this.addMultiEventListener("search", "keyup paste search", this.search, this); */ addMultiEventListener(selector, eventTypes, callback, scope) { const evs = eventTypes.split(" "); for (let i = 0; i < evs.length; i++) { document.querySelector(selector).addEventListener(evs[i], callback.bind(scope)); } } /** * Adds multiple event listeners to each element in the specified group. * * @param {string} selector - A selector string for the element group to add the events to * @param {string} eventTypes - A space-separated string of all the event types to listen for * @param {function} callback - The function to execute when the events are triggered * @param {Object} [scope=this] - The object to bind to the callback function * * @example * // Calls the save function whenever the keyup or paste events are triggered on any element * // with the .saveable class * this.addMultiEventListener(".saveable", "keyup paste", this.save, this); */ addMultiEventListeners(selector, eventTypes, callback, scope) { const evs = eventTypes.split(" "); for (let i = 0; i < evs.length; i++) { this.addListeners(selector, evs[i], callback, scope); } } /** * Adds an event listener to the global document object which will listen on dynamic elements which * may not exist in the DOM yet. * * @param {string} selector - A selector string for the element(s) to add the event to * @param {string} eventType - The event(s) to listen for * @param {function} callback - The function to execute when the event(s) is/are triggered * @param {Object} [scope=this] - The object to bind to the callback function * * @example * // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this * // listener is created * this.addDynamicListener("button", "click", alert, this); */ addDynamicListener(selector, eventType, callback, scope) { const eventConfig = { selector: selector, callback: callback.bind(scope || this) }; if (Object.prototype.hasOwnProperty.call(this.dynamicHandlers, eventType)) { // Listener already exists, add new handler to the appropriate list this.dynamicHandlers[eventType].push(eventConfig); } else { this.dynamicHandlers[eventType] = [eventConfig]; // Set up listener for this new type document.addEventListener(eventType, this.dynamicListenerHandler.bind(this)); } } /** * Handler for dynamic events. This function is called for any dynamic event and decides which * callback(s) to execute based on the type and selector. * * @param {Event} e - The event to be handled */ dynamicListenerHandler(e) { const { type, target } = e; const handlers = this.dynamicHandlers[type]; const matches = target.matches || target.webkitMatchesSelector || target.mozMatchesSelector || target.msMatchesSelector || target.oMatchesSelector; for (let i = 0; i < handlers.length; i++) { if (matches && matches.call(target, handlers[i].selector)) { handlers[i].callback(e); } } } } export default Manager; ================================================ FILE: src/web/html/index.html ================================================ CyberChef
    Operations
      Recipe
        ================================================ FILE: src/web/index.js ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ // Styles import "./stylesheets/index.js"; // Libs import "arrive"; import "snackbarjs"; import "bootstrap-material-design/js/index"; import "bootstrap-colorpicker"; import moment from "moment-timezone"; import * as CanvasComponents from "../core/lib/CanvasComponents.mjs"; // CyberChef import App from "./App.mjs"; import Categories from "../core/config/Categories.json" assert {type: "json"}; import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"}; /** * Main function used to build the CyberChef web app. */ function main() { const defaultFavourites = [ "To Base64", "From Base64", "To Hex", "From Hex", "To Hexdump", "From Hexdump", "URL Decode", "Regular expression", "Entropy", "Fork", "Magic" ]; const defaultOptions = { updateUrl: true, showHighlighter: true, wordWrap: true, showErrors: true, errorTimeout: 4000, attemptHighlight: true, theme: "classic", useMetaKey: false, logLevel: "info", autoMagic: true, imagePreview: true, syncTabs: true, showCatCount: false, }; document.removeEventListener("DOMContentLoaded", main, false); window.app = new App(Categories, OperationConfig, defaultFavourites, defaultOptions); window.app.setup(); } window.compileTime = moment.tz(COMPILE_TIME, "DD/MM/YYYY HH:mm:ss z", "UTC").valueOf(); window.compileMessage = COMPILE_MSG; // Make libs available to operation outputs window.CanvasComponents = CanvasComponents; document.addEventListener("DOMContentLoaded", main, false); ================================================ FILE: src/web/static/fonts/bmfonts/Roboto72White.fnt ================================================ ================================================ FILE: src/web/static/fonts/bmfonts/RobotoBlack72White.fnt ================================================ ================================================ FILE: src/web/static/fonts/bmfonts/RobotoMono72White.fnt ================================================ ================================================ FILE: src/web/static/fonts/bmfonts/RobotoSlab72White.fnt ================================================ ================================================ FILE: src/web/static/ga.html ================================================ ================================================ FILE: src/web/static/images/IMAGE_LICENCES.md ================================================ ## Image attribution The following image files in this directory are taken from open sources: | File | Licence | Attribution | | --------------------- | ----------------------------------------- | ----------------------------------------------------------------------------- | | cook_female-32x32.png | Creative Commons Attribution 3.0 Unported | https://commons.wikimedia.org/wiki/File:Farm-Fresh_user_cook_female_white.png | | cook_male-32x32.png | Creative Commons Attribution 3.0 Unported | https://commons.wikimedia.org/wiki/File:Farm-Fresh_user_cook_male_white.png | | file-32x32.png | Free for commercial use | https://www.iconfinder.com/icons/66768/file_info_icon | | file-128x128.png | Free for commercial use | https://www.iconfinder.com/icons/66768/file_info_icon | ================================================ FILE: src/web/static/sitemap.mjs ================================================ import sm from "sitemap"; import OperationConfig from "../../core/config/OperationConfig.json" assert { type: "json" }; /** * Generates an XML sitemap for all CyberChef operations and a number of recipes. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ const baseUrl = "https://gchq.github.io/CyberChef/"; const smStream = new sm.SitemapStream({}); smStream.write({ url: baseUrl, changefreq: "weekly", priority: 1.0, }); for (const op in OperationConfig) { smStream.write({ url: `${baseUrl}?op=${encodeURIComponent(op)}`, changeFreq: "yearly", priority: 0.5, }); } smStream.end(); sm.streamToPromise(smStream).then( (buffer) => console.log(buffer.toString()), // eslint-disable-line no-console ); ================================================ FILE: src/web/static/structuredData.json ================================================ [ { "@context": "http://schema.org", "@graph": [ { "@type": "Organization", "url": "https://gchq.github.io/CyberChef/", "logo": "https://gchq.github.io/CyberChef/images/cyberchef-128x128.png", "sameAs": [ "https://github.com/gchq/CyberChef", "https://www.npmjs.com/package/cyberchef" ] }, { "@type": "WebSite", "url": "https://gchq.github.io/CyberChef/", "name": "CyberChef", "potentialAction": { "@type": "SearchAction", "target": "https://gchq.github.io/CyberChef/?op={operation_search_term}", "query-input": "required name=operation_search_term" } } ] } ] ================================================ FILE: src/web/stylesheets/components/_button.css ================================================ /** * Button styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ button img, span.btn img { margin-right: 3px; margin-bottom: 1px; } ================================================ FILE: src/web/stylesheets/components/_list.css ================================================ /** * Operation list styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ .op-list { list-style-type: none; margin: 0; padding: 0; } .category-title { display: block; padding: 10px; background-color: var(--secondary-background-colour); border-bottom: 1px solid var(--secondary-border-colour); font-weight: var(--title-weight); } .category-title[href='#catFavourites'] { border-bottom-color: var(--primary-border-colour); } .category-title[aria-expanded=true] { border-bottom-color: var(--primary-border-colour); } .category-title.collapsed { border-bottom-color: var(--secondary-border-colour); } .category-title:hover { color: var(--op-list-operation-font-colour); } .category { margin: 0 !important; border-radius: 0 !important; border: none; } .op-count { float: right; color: var(--subtext-font-colour); font-weight: normal; font-size: xx-small; opacity: 0.5; padding-left: .5em; } ================================================ FILE: src/web/stylesheets/components/_operation.css ================================================ /** * Operation styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ .operation { cursor: grab; padding: 10px; list-style-type: none; position: relative; border-width: 1px; border-style: solid; border-top: none; border-left: none; border-right: none; } #rec-list .operation { padding: 14px; } .op-title { font-weight: var(--op-title-font-weight); } .ingredients { display: flex; flex-flow: row wrap; justify-content: flex-start; column-gap: 14px; row-gap: 0; } .ing-very-wide { flex: 4 400px; } .ing-wide { flex: 3 200px; } .ing-medium { flex: 2 120px; } .ing-short { flex: 1 80px; } .ing-flexible { flex-grow: 1; } .ingredients .form-group { margin-top: 1rem; padding-top: 0; } .arg { font-family: var(--fixed-width-font-family); text-overflow: ellipsis; } select.arg { font-family: var(--primary-font-family); min-width: 100px; } select.arg.form-control:not([size]):not([multiple]), select.custom-file-control:not([size]):not([multiple]) { height: 100% !important; } textarea.arg { min-height: 74px; resize: vertical; } div.toggle-string { flex: 1; } input.toggle-string { border-top-right-radius: 0 !important; height: 100%; } .operation [class^='bmd-label'], .operation [class*=' bmd-label'] { top: 13px !important; left: 12px; z-index: 10; } .operation label, .operation .checkbox label { color: var(--arg-label-colour); } .operation .is-focused [class^='bmd-label'], .operation .is-focused [class*=' bmd-label'], .operation .is-focused [class^='bmd-label'], .operation .is-focused [class*=' bmd-label'], .operation .is-focused label, .operation .checkbox label:hover { color: var(--input-highlight-colour); } .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check, .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before { border-color: var(--input-border-colour); color: var(--input-highlight-colour); } .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { border-color: var(--input-highlight-colour); color: var(--input-highlight-colour); } .disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check, .disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before, .disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, .disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { border-color: var(--disabled-font-colour); color: var(--disabled-font-colour); } .break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check, .break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before, .break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, .break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { border-color: var(--breakpoint-font-colour); color: var(--breakpoint-font-colour); } .flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check, .flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before, .flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, .flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { border-color: var(--fc-breakpoint-operation-font-colour); color: var(--fc-breakpoint-operation-font-colour); } .operation .form-control { padding: 20px 12px 6px 12px !important; border-top-left-radius: 4px; border-top-right-radius: 4px; background-image: none; background-color: var(--arg-background); background-position-y: 100%, 100%; color: var(--arg-font-colour); } .operation .form-control:hover { background-image: linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px), linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px); filter: brightness(97%); } .operation .form-control:focus { background-color: var(--arg-background); background-image: linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px), linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px); filter: brightness(100%); } .operation .bmd-form-group.is-filled label.bmd-label-floating, .operation .bmd-form-group.is-focused label.bmd-label-floating { top: 4px !important; left: 12px; } .operation label.bmd-label-floating { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; width: calc(100% - 13px); } .input-group .form-control { border-top-left-radius: 4px; } .input-group-append button { border-top-right-radius: 4px; background-color: var(--arg-background) !important; margin: unset; } .input-group-append button:hover { filter: brightness(97%); } .editable-option-menu { height: auto; max-height: 350px; overflow-x: hidden; } .editable-option-menu .dropdown-item { padding: 0.3rem 1rem 0.3rem 1rem; min-height: 1.6rem; max-width: 20rem; } .ingredients .dropdown-toggle-split { height: 40px !important; } .boolean-arg { height: 46px; } .boolean-arg .checkbox { height: 100%; } .boolean-arg .checkbox label { height: 100%; display: flex; align-items: center; } .boolean-arg .checkbox-decorator { top: 13px; } .register-list { background-color: var(--fc-operation-border-colour); font-family: var(--fixed-width-font-family); padding: 10px; word-break: break-all; } .op-icon { float: right; color: #f44336; font-size: 18px; cursor: pointer; } .recip-icons { position: absolute; top: 13px; right: 10px; height: 16px; } .recip-icons i { margin-right: 10px; vertical-align: baseline; float: right; font-size: 18px; cursor: pointer; } .disable-icon { color: var(--disable-icon-colour); } .disable-icon-selected { color: var(--disable-icon-selected-colour); } .breakpoint { color: var(--breakpoint-icon-colour); } .breakpoint-selected { color: var(--breakpoint-icon-selected-colour); } .break { color: var(--breakpoint-font-colour) !important; background-color: var(--breakpoint-bg-colour) !important; border-color: var(--breakpoint-border-colour) !important; } .break .form-group * { color: var(--breakpoint-font-colour) !important; } .selected-op { color: var(--selected-operation-font-color) !important; background-color: var(--selected-operation-bg-colour) !important; border-color: var(--selected-operation-border-colour) !important; } .selected-op .form-group * { color: var(--selected-operation-font-color) !important; } .flow-control-op { color: var(--fc-operation-font-colour) !important; background-color: var(--fc-operation-bg-colour) !important; border-color: var(--fc-operation-border-colour) !important; } .flow-control-op .form-group *:not(.arg) { color: var(--fc-operation-font-colour) } .flow-control-op.break { color: var(--fc-breakpoint-operation-font-colour) !important; background-color: var(--fc-breakpoint-operation-bg-colour) !important; border-color: var(--fc-breakpoint-operation-border-colour) !important; } .flow-control-op.break .form-group * { color: var(--fc-breakpoint-operation-font-colour) !important; } .disabled { color: var(--disabled-font-colour) !important; background-color: var(--disabled-bg-colour) !important; border-color: var(--disabled-border-colour) !important; } .disabled .form-group * { color: var(--disabled-font-colour) !important; } .break .register-list { color: var(--fc-breakpoint-operation-font-colour) !important; background-color: var(--fc-breakpoint-operation-border-colour) !important; } .disabled .register-list { color: var(--disabled-font-colour) !important; background-color: var(--disabled-border-colour) !important; } ================================================ FILE: src/web/stylesheets/components/_pane.css ================================================ /** * Workspace pane styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ :root { --title-height: 48px; --tab-height: 40px; } .title { padding: 8px; padding-left: 12px; padding-right: 12px; height: var(--title-height); border-bottom: 1px solid var(--primary-border-colour); font-weight: var(--title-weight); font-size: var(--title-size); color: var(--title-colour); background-color: var(--title-background-colour); line-height: calc(var(--title-height) - 14px); } .pane-controls { position: absolute; right: 8px; top: 8px; display: flex; flex-direction: row; } .pane-controls .btn { margin-left: 2px; } .list-area { position: absolute; top: var(--title-height); bottom: 0; width: 100%; list-style-type: none; margin: 0; padding: 0; } #files .card-header .float-right a:hover { text-decoration: none; } ================================================ FILE: src/web/stylesheets/index.css ================================================ /** * CyberChef styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ /* Themes */ @import "./themes/_classic.css"; @import "./themes/_dark.css"; @import "./themes/_geocities.css"; @import "./themes/_solarizedDark.css"; @import "./themes/_solarizedLight.css"; /* Utilities */ @import "./utils/_overrides.css"; @import "./utils/_general.css"; /* Preloader styles */ @import "./preloader.css"; /* Components */ @import "./components/_button.css"; @import "./components/_list.css"; @import "./components/_operation.css"; @import "./components/_pane.css"; /* Layout */ @import "./layout/_banner.css"; @import "./layout/_controls.css"; @import "./layout/_io.css"; @import "./layout/_modals.css"; @import "./layout/_operations.css"; @import "./layout/_recipe.css"; @import "./layout/_structure.css"; /* Operations */ @import "./operations/diff.css"; @import "./operations/json.css"; ================================================ FILE: src/web/stylesheets/index.js ================================================ /** * Styles index * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ /* Libraries */ import "highlight.js/styles/vs.css"; /* Frameworks */ import "bootstrap-material-design/dist/css/bootstrap-material-design.css"; import "bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css"; /* CyberChef styles */ import "./index.css"; ================================================ FILE: src/web/stylesheets/layout/_banner.css ================================================ /** * Banner area styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ #banner { position: absolute; height: 30px; width: 100%; line-height: 30px; border-bottom: 1px solid var(--primary-border-colour); color: var(--banner-font-colour); background-color: var(--banner-bg-colour); margin: 0; } #banner i { vertical-align: middle; padding-right: 10px; } #banner a { color: var(--banner-url-colour); } #notice-wrapper { text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } ================================================ FILE: src/web/stylesheets/layout/_controls.css ================================================ /** * Controls area styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ #controls { position: absolute; width: 100%; bottom: 0; padding: 10px 0; border-top: 1px solid var(--primary-border-colour); background-color: var(--secondary-background-colour); } #controls-content { position: relative; display: flex; flex-flow: row nowrap; align-items: center; } #auto-bake-label { display: inline-block; width: 100px; padding: 0; margin: 0; text-align: center; color: var(--primary-font-colour); font-size: 14px; cursor: pointer; } #auto-bake-label .check, #auto-bake-label .check::before { border-color: var(--input-highlight-colour); color: var(--input-highlight-colour); } #auto-bake-label .checkbox-decorator { position: relative; } #bake { box-shadow: none; } #controls .btn { border-radius: 30px; margin: 0; } .output-maximised .hide-on-maximised-output { display: none !important; } .spin { animation-name: spin; animation-duration: 3s; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes spin { 0% {transform: rotate(0deg);} 100% {transform: rotate(360deg);} } ================================================ FILE: src/web/stylesheets/layout/_io.css ================================================ /** * Input/Output area styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ #input-text, #output-text { position: relative; width: 100%; height: 100%; margin: 0; background-color: transparent; overflow: hidden; user-select: auto; } #output-text.html-output .cm-content, #output-text.html-output .cm-line, #output-html { display: block; height: 100%; user-select: auto; } #output-text.html-output .cm-line .cm-widgetBuffer, #output-text.html-output .cm-line>br { display: none; } .cm-editor { height: 100%; } .cm-editor .cm-content { font-family: var(--fixed-width-font-family); font-size: var(--fixed-width-font-size); color: var(--fixed-width-font-colour); } #input-tabs-wrapper #input-tabs, #output-tabs-wrapper #output-tabs { list-style: none; background-color: var(--title-background-colour); padding: 0; margin: 0; overflow-x: auto; overflow-y: hidden; display: flex; flex-direction: row; border-bottom: 1px solid var(--primary-border-colour); border-left: 1px solid var(--primary-border-colour); height: var(--tab-height); clear: none; } #input-tabs li, #output-tabs li { display: flex; flex-direction: row; width: 100%; min-width: 120px; float: left; padding: 0px; text-align: center; border-right: 1px solid var(--primary-border-colour); height: var(--tab-height); vertical-align: middle; } #input-tabs li:hover, #output-tabs li:hover { cursor: pointer; background-color: var(--primary-background-colour); } .active-input-tab, .active-output-tab { font-weight: bold; background-color: var(--primary-background-colour); } .input-tab-content+.btn-close-tab { display: block; margin-top: auto; margin-bottom: auto; margin-right: 2px; } .input-tab-content+.btn-close-tab i { font-size: 0.8em; } .input-tab-buttons, .output-tab-buttons { width: 25px; text-align: center; margin: 0; height: var(--tab-height); line-height: var(--tab-height); font-weight: bold; background-color: var(--title-background-colour); border-bottom: 1px solid var(--primary-border-colour); } .input-tab-buttons:hover, .output-tab-buttons:hover { cursor: pointer; background-color: var(--primary-background-colour); } #btn-next-input-tab, #btn-input-tab-dropdown, #btn-next-output-tab, #btn-output-tab-dropdown { float: right; } #btn-previous-input-tab, #btn-previous-output-tab { float: left; } #btn-close-all-tabs { color: var(--breakpoint-font-colour) !important; } .input-tab-content, .output-tab-content { width: 100%; max-width: 100%; padding-left: 5px; padding-right: 5px; padding-top: 10px; padding-bottom: 10px; height: var(--tab-height); vertical-align: middle; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .btn-close-tab { height: var(--tab-height); vertical-align: middle; width: fit-content; } .tabs-left > li:first-child { box-shadow: 15px 0px 15px -15px var(--primary-border-colour) inset; } .tabs-right > li:last-child { box-shadow: -15px 0px 15px -15px var(--primary-border-colour) inset; } #input-wrapper, #output-wrapper { height: calc(100% - var(--title-height)); } #input-wrapper.show-tabs, #output-wrapper.show-tabs { height: calc(100% - var(--tab-height) - var(--title-height)); } #output-loader { position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0; background-color: var(--secondary-background-colour); opacity: 0; visibility: hidden; display: flex; justify-content: center; align-items: center; transition: all 0.5s ease; } #output-loader-animation { display: block; position: absolute; width: 60%; height: 60%; top: 10%; transition: all 0.5s ease; } #output-loader .loading-msg { opacity: 1; font-family: var(--primary-font-family); line-height: var(--primary-line-height); color: var(--primary-font-colour); left: unset; top: 30%; position: relative; transition: all 0.5s ease; } .io-info { margin-right: 18px; margin-top: 1px; height: 30px; text-align: right; line-height: 12px; font-family: var(--fixed-width-font-family); font-weight: normal; font-size: 8pt; display: flex; align-items: center; } .dropping-file { border: 5px dashed var(--drop-file-border-colour) !important; } #stale-indicator { opacity: 1; visibility: visible; transition: margin 0s, opacity 0.3s; margin-left: 5px; cursor: help; } #stale-indicator i { vertical-align: middle; margin-bottom: 5px; } #magic { opacity: 1; visibility: visible; transition: margin 0s 0.3s, opacity 0.3s 0.3s, visibility 0.3s 0.3s; margin-left: 5px; margin-bottom: 5px; } #magic.hidden, #stale-indicator.hidden { visibility: hidden; transition: opacity 0.3s, margin 0.3s 0.3s, visibility 0.3s; opacity: 0; } #magic.hidden { margin-left: -32px; } #magic svg path { fill: var(--primary-font-colour); } .pulse { box-shadow: 0 0 0 0 rgba(90, 153, 212, .3); animation: pulse 1.5s 1; } .pulse:hover { animation-play-state: paused; } @keyframes pulse { 0% { transform: scale(1); } 70% { transform: scale(1.1); box-shadow: 0 0 0 20px rgba(90, 153, 212, 0); } 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(90, 153, 212, 0); } } #input-find-options, #output-find-options { display: flex; flex-direction: row; flex-wrap: wrap; width: 100%; } #input-tab-body .form-group.input-group, #output-tab-body .form-group.input-group { width: 70%; float: left; margin-bottom: 2rem; } .input-find-option .toggle-string { width: 70%; display: inline-block; } .input-find-option-append button { border-top-right-radius: 4px; background-color: var(--arg-background) !important; margin: unset; } .input-find-option-append button:hover { filter: brightness(97%); } .form-group.output-find-option { width: 70%; float: left; } #input-num-results-container, #output-num-results-container { width: 20%; float: right; margin: 0; margin-left: 10%; } #input-find-options-checkboxes, #output-find-options-checkboxes { list-style: none; padding: 0; margin: auto; overflow-x: auto; overflow-y: hidden; text-align: center; width: fit-content; } #input-find-options-checkboxes li, #output-find-options-checkboxes li { display: flex; flex-direction: row; float: left; padding: 10px; text-align: center; } #input-search-results, #output-search-results { list-style: none; width: 75%; min-width: 200px; margin-left: auto; margin-right: auto; } #input-search-results li, #output-search-results li { padding-left: 5px; padding-right: 5px; padding-top: 10px; padding-bottom: 10px; text-align: center; width: 100%; color: var(--op-list-operation-font-colour); background-color: var(--op-list-operation-bg-colour); border-bottom: 2px solid var(--op-list-operation-border-colour); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } #input-search-results li:first-of-type, #output-search-results li:first-of-type { border-top: 2px solid var(--op-list-operation-border-colour); } #input-search-results li:hover, #output-search-results li:hover { cursor: pointer; filter: brightness(98%); } /* Highlighting */ .ͼ2.cm-focused .cm-selectionBackground { background-color: var(--hl5); } .ͼ2 .cm-selectionBackground { background-color: var(--hl1); } .ͼ1 .cm-selectionMatch { background-color: var(--hl2); } .ͼ1.cm-focused .cm-cursor.cm-cursor-primary { border-color: var(--primary-font-colour); } .ͼ1 .cm-cursor.cm-cursor-primary { display: block; border-color: var(--subtext-font-colour); } /* Status bar */ .cm-panel input::placeholder { font-size: 12px !important; } .ͼ2 .cm-panels, .ͼ2 .cm-side-panels { background-color: var(--secondary-background-colour); border-color: var(--primary-border-colour); color: var(--primary-font-colour); } .cm-status-bar { font-family: var(--fixed-width-font-family); font-weight: normal; font-size: 8pt; margin: 0 5px; display: flex; flex-flow: row nowrap; justify-content: space-between; align-items: center; } .cm-status-bar i { font-size: 12pt; vertical-align: middle; margin-left: 8px; } .cm-status-bar>div>span:first-child i { margin-left: 0; } .cm-status-bar .disabled { background-color: unset !important; cursor: not-allowed; } /* Dropup Button */ .cm-status-bar-select-btn { border: none; cursor: pointer; } /* The container
        - needed to position the dropup content */ .cm-status-bar-select { position: relative; display: inline-block; } /* Dropup content (Hidden by Default) */ .cm-status-bar-select-content { display: none; position: absolute; bottom: 20px; right: 0; background-color: #f1f1f1; min-width: 200px; box-shadow: 0px 4px 4px 0px rgba(0,0,0,0.2); z-index: 1; } /* Links inside the dropup */ .cm-status-bar-select-content a { color: black; padding: 2px 5px; text-decoration: none; display: block; } /* Change color of dropup links on hover */ .cm-status-bar-select-content a:hover { background-color: #ddd } /* Change the background color of the dropup button when the dropup content is shown */ .cm-status-bar-select:hover .cm-status-bar-select-btn { background-color: #f1f1f1; } /* The search field */ .cm-status-bar-filter-input { box-sizing: border-box; font-size: 12px; padding-left: 10px !important; border: none; } .cm-status-bar-filter-search { border-top: 1px solid #ddd; } /* Show the dropup menu */ .cm-status-bar-select .show { display: block; } .cm-status-bar-select-scroll { overflow-y: auto; max-height: 300px; } .chr-enc-value { max-width: 150px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; } /* File details panel */ .cm-file-details { text-align: center; display: flex; flex-direction: column; align-items: center; overflow-y: auto; padding-bottom: 21px; height: 100%; } .file-details-toggle-shown, .file-details-toggle-hidden { width: 8px; height: 40px; border: 1px solid var(--secondary-border-colour); position: absolute; top: calc(50% - 20px); cursor: pointer; display: flex; align-items: center; justify-content: center; background-color: var(--secondary-border-colour); color: var(--subtext-font-colour); z-index: 1; } .file-details-toggle-shown { left: 0; border-left: none; border-top-right-radius: 5px; border-bottom-right-radius: 5px; } .file-details-toggle-hidden { left: -8px; border-right: none; border-top-left-radius: 5px; border-bottom-left-radius: 5px; } .file-details-toggle-shown:hover, .file-details-toggle-hidden:hover { background-color: var(--primary-border-colour); border-color: var(--primary-border-colour); color: var(--primary-font-colour); } .file-details-heading { font-weight: bold; margin: 10px 0 10px 0; } .file-details-data { text-align: left; margin: 10px 2px; } .file-details-data td { padding: 0 3px; max-width: 130px; min-width: 60px; overflow: hidden; vertical-align: top; word-break: break-all; } .file-details-error { color: #f00; } .file-details-thumbnail { max-width: 180px; } ================================================ FILE: src/web/stylesheets/layout/_modals.css ================================================ /** * Modal layout styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ .modal-content { background-color: var(--primary-background-colour); } .option-item { margin-bottom: 20px; } #edit-favourites-list { margin: 10px; border: 1px solid var(--op-list-operation-border-colour); } #edit-favourites-list .operation { border-left: none; border-right: none; } #edit-favourites-list .operation:last-child { border-bottom: none; } .about-img-left { float: left; margin: 10px 20px 20px 0; } .about-img-right { float: right; margin: 10px 0 20px 20px; } #save-link-group { padding-top: 0; } .save-link-options { float: right; } .save-link-options label { margin-left: 10px; } #save-footer { border-top: none; margin-top: 0; border-bottom: 1px solid var(--primary-border-colour); } #support-modal textarea { font-family: var(--primary-font-family); } #save-texts textarea, #load-text { font-family: var(--fixed-width-font-family); } #save-texts textarea { height: 200px; } #faqs a.btn { text-transform: unset; } #faqs > div { padding: 20px; border-left: 2px solid var(--primary-border-colour); } .checkbox label input[type=checkbox]+.checkbox-decorator .check, .checkbox label input[type=checkbox]+.checkbox-decorator .check::before { border-color: var(--input-border-colour); color: var(--input-highlight-colour); } .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { border-color: var(--input-highlight-colour); color: var(--input-highlight-colour); } .bmd-form-group.is-focused .option-item label { color: var(--input-highlight-colour); } .bmd-form-group.is-focused [class^='bmd-label'], .bmd-form-group.is-focused [class*=' bmd-label'], .bmd-form-group.is-focused [class^='bmd-label'], .bmd-form-group.is-focused [class*=' bmd-label'], .bmd-form-group.is-focused label, .checkbox label:hover, .bmd-form-group.is-filled:focus-within .checkbox.option-item label { color: var(--input-highlight-colour); } .bmd-form-group.option-item label+.form-control{ background-image: linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px), linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); } ================================================ FILE: src/web/stylesheets/layout/_operations.css ================================================ /** * Operation area styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ .op-list .operation { color: var(--op-list-operation-font-colour); background-color: var(--op-list-operation-bg-colour); border-color: var(--op-list-operation-border-colour); } #search { padding-left: 10px; padding-right: 10px; background-image: linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px), linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); } #edit-favourites { float: right; margin-top: -7px; } .favourites-hover { color: var(--rec-list-operation-font-colour); background-color: var(--rec-list-operation-bg-colour); border: 2px dashed var(--rec-list-operation-font-colour) !important; padding: 8px 8px 9px 8px; } #categories a { color: var(--category-list-font-colour); cursor: pointer; } #categories a:hover, .op-list .operation:hover { filter: brightness(98%); } ================================================ FILE: src/web/stylesheets/layout/_recipe.css ================================================ /** * Recipe area styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ #rec-list { overflow: auto; } #rec-list .operation { color: var(--rec-list-operation-font-colour); background-color: var(--rec-list-operation-bg-colour); border-color: var(--rec-list-operation-border-colour); } ================================================ FILE: src/web/stylesheets/layout/_structure.css ================================================ /** * Overall page structure styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ body { overflow: hidden; } #content-wrapper { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } #workspace-wrapper { position: absolute; top: 30px; bottom: 0; width: 100%; } div#operations, div#recipe { width: 50%; height: 100%; } div#input, div#output { width: 100%; height: 50%; } .split { box-sizing: border-box; /* overflow: auto; */ /* Removed to enable Background Magic button pulse to overflow. Replace this rule if it seems to be causing problems. */ position: relative; } #operations.split { overflow: auto; } .split.split-horizontal, .gutter.gutter-horizontal { height: 100%; float: left; } .gutter { background-color: var(--secondary-border-colour); background-repeat: no-repeat; background-position: 50%; } .gutter.gutter-horizontal { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+EFBhEwBDmIiYYAAAAjSURBVBjTYzxz5sx/BgYGBiYGKGB89+4dA4oIy71790aGGgCn+DBbOcAB0wAAAABJRU5ErkJggg=='); cursor: ew-resize; } .gutter.gutter-vertical { 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'); cursor: ns-resize; } ================================================ FILE: src/web/stylesheets/operations/diff.css ================================================ del { background-color: var(--hl3); } ins { text-decoration: underline; /* shouldn't be needed, but Chromium doesn't copy to clipboard without it */ background-color: var(--hl5); } ================================================ FILE: src/web/stylesheets/operations/json.css ================================================ /** * JSON styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 * * Adapted for CyberChef by @n1474335 from jQuery json-viewer * @author Alexandre Bodelot * @link https://github.com/abodelot/jquery.json-viewer * @license MIT */ /* Root element */ .json-document { padding: .5em 1.5em; } /* Syntax highlighting for JSON objects */ ul.json-dict, ol.json-array { list-style-type: none; margin: 0 0 0 1px; border-left: 1px dotted #ccc; padding-left: 2em; } .json-string { color: green; } .json-literal { color: red; } .json-brace, .json-bracket, .json-colon, .json-comma { color: gray; } /* Collapse */ .json-details { display: inline; } .json-details[open] { display: contents; } .json-summary { display: inline; list-style: none; } /* Display object and array brackets when closed */ .json-summary.json-obj::after { color: gray; content: "{ ... }" } .json-summary.json-arr::after { color: gray; content: "[ ... ]" } .json-details[open] > .json-summary.json-obj::after, .json-details[open] > .json-summary.json-arr::after { content: ""; } /* Show arrows, even in inline mode */ .json-summary::before { content: "\25BC"; color: #c0c0c0; margin-left: -12px; margin-right: 5px; display: inline-block; transform: rotate(-90deg); } .json-summary:hover::before { color: #aaa; } .json-details[open] > .json-summary::before { transform: rotate(0deg); } ================================================ FILE: src/web/stylesheets/preloader.css ================================================ /** * Preloader styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ #loader-wrapper { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000; background-color: var(--loader-background-colour); } .loader { display: block; position: relative; left: 50%; top: 50%; width: 150px; height: 150px; margin: -75px 0 0 -75px; border: 3px solid transparent; border-top-color: var(--loader-outer-colour); border-radius: 50%; animation: spin 2s linear infinite; } .loader:before, .loader:after { content: ""; position: absolute; border: 3px solid transparent; border-radius: 50%; } .loader:before { top: 5px; left: 5px; right: 5px; bottom: 5px; border-top-color: var(--loader-middle-colour); animation: spin 3s linear infinite; } .loader:after { top: 13px; left: 13px; right: 13px; bottom: 13px; border-top-color: var(--loader-inner-colour); animation: spin 1.5s linear infinite; } .loading-msg { display: block; position: relative; width: 400px; left: calc(50% - 200px); top: calc(50% + 50px); text-align: center; opacity: 0; font-size: 18px; } .loading-msg.loading { opacity: 1; transition: all 0.1s ease-in; } .loading-error { display: block; position: relative; width: 600px; left: calc(50% - 300px); top: 10%; } /* Loaded */ .loaded .loading-msg { opacity: 0; transition: all 0.3s ease-out; } .loaded #loader-wrapper { opacity: 0; transition: all 0.5s 0.3s ease-out; } .loaded #rec-list li { animation: bump 0.7s cubic-bezier(0.7, 0, 0.3, 1) both; } .loaded #content-wrapper { animation-delay: 0.10s; } .loaded #rec-list li:first-child { animation-delay: 0.20s; } .loaded #rec-list li:nth-child(2) { animation-delay: 0.25s; } .loaded #rec-list li:nth-child(3) { animation-delay: 0.30s; } .loaded #rec-list li:nth-child(4) { animation-delay: 0.35s; } .loaded #rec-list li:nth-child(5) { animation-delay: 0.40s; } .loaded #rec-list li:nth-child(6) { animation-delay: 0.45s; } .loaded #rec-list li:nth-child(7) { animation-delay: 0.50s; } .loaded #rec-list li:nth-child(8) { animation-delay: 0.55s; } .loaded #rec-list li:nth-child(9) { animation-delay: 0.60s; } .loaded #rec-list li:nth-child(10) { animation-delay: 0.65s; } /* Animations */ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes bump { from { opacity: 0; transform: translate3d(0, 200px, 0); } } ================================================ FILE: src/web/stylesheets/themes/_classic.css ================================================ /** * Classic theme definitions * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ :root, :root.classic { --primary-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --primary-font-colour: #333; --primary-font-size: 14px; --primary-line-height: 20px; --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --fixed-width-font-colour: inherit; --fixed-width-font-size: inherit; --subtext-font-colour: #999; --subtext-font-size: 13px; --primary-background-colour: #fff; --secondary-background-colour: #fafafa; --primary-border-colour: #ddd; --secondary-border-colour: #eee; --title-colour: #424242; --title-weight: bold; --title-size: 16px; --title-background-colour: #fafafa; --banner-font-colour: #468847; --banner-bg-colour: #dff0d8; --banner-url-colour: #1976d2; --category-list-font-colour: #1976d2; --loader-background-colour: var(--secondary-border-colour); --loader-outer-colour: #3498db; --loader-middle-colour: #e74c3c; --loader-inner-colour: #f9c922; /* Operation colours */ --op-list-operation-font-colour: #3a87ad; --op-list-operation-bg-colour: #d9edf7; --op-list-operation-border-colour: #bce8f1; --rec-list-operation-font-colour: #468847; --rec-list-operation-bg-colour: #dff0d8; --rec-list-operation-border-colour: #d3e8c0; --selected-operation-font-color: #c09853; --selected-operation-bg-colour: #fcf8e3; --selected-operation-border-colour: #fbeed5; --breakpoint-font-colour: #b94a48; --breakpoint-bg-colour: #f2dede; --breakpoint-border-colour: #eed3d7; --disabled-font-colour: #999; --disabled-bg-colour: #dfdfdf; --disabled-border-colour: #cdcdcd; --fc-operation-font-colour: #396f3a; --fc-operation-bg-colour: #c7e4ba; --fc-operation-border-colour: #b3dba2; --fc-breakpoint-operation-font-colour: #94312f; --fc-breakpoint-operation-bg-colour: #eabfbf; --fc-breakpoint-operation-border-colour: #e2aeb5; /* Operation arguments */ --op-title-font-weight: bold; --arg-font-colour: #424242; --arg-background: #fff; --arg-border-colour: #ddd; --arg-disabled-background: #eee; --arg-label-colour: #388e3c; /* Operation buttons */ --disable-icon-colour: #9e9e9e; --disable-icon-selected-colour: #f44336; --breakpoint-icon-colour: #9e9e9e; --breakpoint-icon-selected-colour: #f44336; /* Buttons */ --btn-default-font-colour: #333; --btn-default-bg-colour: #fff; --btn-default-border-colour: #ddd; --btn-default-hover-font-colour: #333; --btn-default-hover-bg-colour: #ebebeb; --btn-default-hover-border-colour: #adadad; --btn-success-font-colour: #fff; --btn-success-bg-colour: #5cb85c; --btn-success-border-colour: #4cae4c; --btn-success-hover-font-colour: #fff; --btn-success-hover-bg-colour: #449d44; --btn-success-hover-border-colour: #398439; /* Highlighter colours */ --hl1: #ffee00aa; --hl2: #95dfffaa; --hl3: #ffb6b6aa; --hl4: #fcf8e3aa; --hl5: #8de768aa; /* Scrollbar */ --scrollbar-track: var(--secondary-background-colour); --scrollbar-thumb: #ccc; --scrollbar-hover: #bbb; /* Misc. */ --drop-file-border-colour: #3a87ad; --table-border-colour: #ccc; --popover-background: #fff; --popover-border-colour: #ccc; --code-background: #f9f2f4; --code-font-colour: #c7254e; --input-highlight-colour: #1976d2; --input-border-colour: #424242; } ================================================ FILE: src/web/stylesheets/themes/_dark.css ================================================ /** * Dark theme definitions * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ :root.dark { --primary-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; --primary-font-colour: #c5c5c5; --primary-font-size: 14px; --primary-line-height: 20px; --fixed-width-font-family: "Monaco", "Droid Sans Mono", "Consolas", monospace; --fixed-width-font-colour: inherit; --fixed-width-font-size: inherit; --subtext-font-colour: #999; --subtext-font-size: 13px; --primary-background-colour: #1e1e1e; --secondary-background-colour: #252525; --primary-border-colour: #444; --secondary-border-colour: #3c3c3c; --title-colour: #fff; --title-weight: bold; --title-background-colour: #333; --banner-font-colour: #c5c5c5; --banner-bg-colour: #252525; --banner-url-colour: #1976d2; --category-list-font-colour: #1976d2; --loader-background-colour: var(--secondary-border-colour); --loader-outer-colour: #3498db; --loader-middle-colour: #e74c3c; --loader-inner-colour: #f9c922; /* Operation colours */ --op-list-operation-font-colour: #c5c5c5; --op-list-operation-bg-colour: #333; --op-list-operation-border-colour: #444; --rec-list-operation-font-colour: #c5c5c5; --rec-list-operation-bg-colour: #252525; --rec-list-operation-border-colour: #444; --selected-operation-font-color: #c5c5c5; --selected-operation-bg-colour: #3f3f3f; --selected-operation-border-colour: #444; --breakpoint-font-colour: #ddd; --breakpoint-bg-colour: #073655; --breakpoint-border-colour: #444; --disabled-font-colour: #666; --disabled-bg-colour: #444; --disabled-border-colour: #444; --fc-operation-font-colour: #c5c5c5; --fc-operation-bg-colour: #2d2d2d; --fc-operation-border-colour: #444; --fc-breakpoint-operation-font-colour: #ddd; --fc-breakpoint-operation-bg-colour: #072b49; --fc-breakpoint-operation-border-colour: #444; /* Operation arguments */ --op-title-font-weight: bold; --arg-font-colour: #bbb; --arg-background: #3c3c3c; --arg-border-colour: #3c3c3c; --arg-disabled-background: #4f4f4f; --arg-label-colour: rgb(25, 118, 210); /* Operation buttons */ --disable-icon-colour: #9e9e9e; --disable-icon-selected-colour: #f44336; --breakpoint-icon-colour: #9e9e9e; --breakpoint-icon-selected-colour: #f44336; /* Buttons */ --btn-default-font-colour: #c5c5c5; --btn-default-bg-colour: #2d2d2d; --btn-default-border-colour: #3c3c3c; --btn-default-hover-font-colour: #c5c5c5; --btn-default-hover-bg-colour: #2d2d2d; --btn-default-hover-border-colour: #205375; --btn-success-font-colour: #fff; --btn-success-bg-colour: #073655; --btn-success-border-colour: #0e639c; --btn-success-hover-font-colour: #fff; --btn-success-hover-bg-colour: #0e639c; --btn-success-hover-border-colour: #0e639c; /* Highlighter colours */ --hl1: #264f78; --hl2: #675351; --hl3: #c40000; --hl4: #fcf8e3; --hl5: #38811b; /* Scrollbar */ --scrollbar-track: #1e1e1e; --scrollbar-thumb: #424242; --scrollbar-hover: #4e4e4e; /* Misc. */ --drop-file-border-colour: #0e639c; --table-border-colour: #555; --popover-background: #444; --popover-border-colour: #555; --code-background: #0e639c; --code-font-colour: #fff; --input-highlight-colour: #1976d2; --input-border-colour: #424242; } ================================================ FILE: src/web/stylesheets/themes/_geocities.css ================================================ /** * GeoCities theme definitions * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ :root.geocities { --primary-font-family: "Comic Sans", "Comic Sans MS", "Chalkboard", "ChalkboardSE-Regular", "Marker Felt", "Purisa", "URW Chancery L", cursive, sans-serif; --primary-font-colour: black; --primary-font-size: 14px; --primary-line-height: 20px; --fixed-width-font-family: "Courier New", Courier, monospace; --fixed-width-font-colour: yellow; --fixed-width-font-size: inherit; --subtext-font-colour: darkgrey; --subtext-font-size: 13px; --primary-background-colour: #00f; --secondary-background-colour: #f00; --primary-border-colour: pink; --secondary-border-colour: springgreen; --title-colour: red; --title-weight: bold; --title-background-colour: yellow; --banner-font-colour: white; --banner-bg-colour: maroon; --banner-url-colour: yellow; --category-list-font-colour: yellow; --loader-background-colour: #00f; --loader-outer-colour: #0f0; --loader-middle-colour: red; --loader-inner-colour: yellow; /* Operation colours */ --op-list-operation-font-colour: blue; --op-list-operation-bg-colour: yellow; --op-list-operation-border-colour: green; --rec-list-operation-font-colour: white; --rec-list-operation-bg-colour: purple; --rec-list-operation-border-colour: green; --selected-operation-font-color: white; --selected-operation-bg-colour: pink; --selected-operation-border-colour: blue; --breakpoint-font-colour: white; --breakpoint-bg-colour: red; --breakpoint-border-colour: blue; --disabled-font-colour: grey; --disabled-bg-colour: black; --disabled-border-colour: grey; --fc-operation-font-colour: sienna; --fc-operation-bg-colour: pink; --fc-operation-border-colour: yellow; --fc-breakpoint-operation-font-colour: darkgrey; --fc-breakpoint-operation-bg-colour: deeppink; --fc-breakpoint-operation-border-colour: yellowgreen; /* Operation arguments */ --op-title-font-weight: bold; --arg-font-colour: white; --arg-background: black; --arg-border-colour: lime; --arg-disabled-background: grey; --arg-label-colour: red; /* Operation buttons */ --disable-icon-colour: #0f0; --disable-icon-selected-colour: yellow; --breakpoint-icon-colour: #0f0; --breakpoint-icon-selected-colour: yellow; /* Buttons */ --btn-default-font-colour: black; --btn-default-bg-colour: white; --btn-default-border-colour: grey; --btn-default-hover-font-colour: black; --btn-default-hover-bg-colour: white; --btn-default-hover-border-colour: grey; --btn-success-font-colour: white; --btn-success-bg-colour: lawngreen; --btn-success-border-colour: grey; --btn-success-hover-font-colour: white; --btn-success-hover-bg-colour: lime; --btn-success-hover-border-colour: grey; /* Highlighter colours */ --hl1: #fff000; --hl2: #95dfff; --hl3: #ffb6b6; --hl4: #fcf8e3; --hl5: #8de768; /* Scrollbar */ --scrollbar-track: lightsteelblue; --scrollbar-thumb: lightslategrey; --scrollbar-hover: grey; /* Misc. */ --drop-file-border-colour: purple; --table-border-colour: var(--hl3); --popover-background: turquoise; --popover-border-colour: violet; --code-background: black; --code-font-colour: limegreen; --input-highlight-colour: limegreen; --input-border-colour: limegreen; } ================================================ FILE: src/web/stylesheets/themes/_solarizedDark.css ================================================ /** * Solarized dark theme definitions * * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ :root.solarizedDark { --base03: #002b36; --base02: #073642; --base01: #586e75; --base00: #657b83; --base0: #839496; --base1: #93a1a1; --base2: #eee8d5; --base3: #fdf6e3; --sol-yellow: #b58900; --sol-orange: #cb4b16; --sol-red: #dc322f; --sol-magenta: #d33682; --sol-violet: #6c71c4; --sol-blue: #268bd2; --sol-cyan: #2aa198; --sol-green: #859900; --primary-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --primary-font-colour: var(--base0); --primary-font-size: 14px; --primary-line-height: 20px; --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --fixed-width-font-colour: inherit; --fixed-width-font-size: inherit; --subtext-font-colour: var(--base01); --subtext-font-size: 13px; --primary-background-colour: var(--base03); --secondary-background-colour: var(--base02); --primary-border-colour: var(--base00); --secondary-border-colour: var(--base01); --title-colour: var(--base1); --title-weight: bold; --title-background-colour: var(--base02); --banner-font-colour: var(--base0); --banner-bg-colour: var(--base03); --banner-url-colour: var(--base1); --category-list-font-colour: var(--base1); --loader-background-colour: var(--base03); --loader-outer-colour: var(--base1); --loader-middle-colour: var(--base0); --loader-inner-colour: var(--base00); /* Operation colours */ --op-list-operation-font-colour: var(--base0); --op-list-operation-bg-colour: var(--base03); --op-list-operation-border-colour: var(--base02); --rec-list-operation-font-colour: var(--base0); --rec-list-operation-bg-colour: var(--base02); --rec-list-operation-border-colour: var(--base01); --selected-operation-font-color: var(--base1); --selected-operation-bg-colour: var(--base02); --selected-operation-border-colour: var(--base01); --breakpoint-font-colour: var(--sol-red); --breakpoint-bg-colour: var(--base02); --breakpoint-border-colour: var(--base00); --disabled-font-colour: var(--base01); --disabled-bg-colour: var(--base03); --disabled-border-colour: var(--base02); --fc-operation-font-colour: var(--base1); --fc-operation-bg-colour: var(--base02); --fc-operation-border-colour: var(--base01); --fc-breakpoint-operation-font-colour: var(--sol-orange); --fc-breakpoint-operation-bg-colour: var(--base02); --fc-breakpoint-operation-border-colour: var(--base00); /* Operation arguments */ --op-title-font-weight: bold; --arg-font-colour: var(--base0); --arg-background: var(--base03); --arg-border-colour: var(--base00); --arg-disabled-background: var(--base03); --arg-label-colour: var(--base1); /* Operation buttons */ --disable-icon-colour: var(--base00); --disable-icon-selected-colour: var(--sol-red); --breakpoint-icon-colour: var(--base00); --breakpoint-icon-selected-colour: var(--sol-red); /* Buttons */ --btn-default-font-colour: var(--base0); --btn-default-bg-colour: var(--base02); --btn-default-border-colour: var(--base01); --btn-default-hover-font-colour: var(--base1); --btn-default-hover-bg-colour: var(--base01); --btn-default-hover-border-colour: var(--base00); --btn-success-font-colour: var(--base0); --btn-success-bg-colour: var(--base03); --btn-success-border-colour: var(--base00); --btn-success-hover-font-colour: var(--base1); --btn-success-hover-bg-colour: var(--base01); --btn-success-hover-border-colour: var(--base00); /* Highlighter colours */ --hl1: var(--base01); --hl2: var(--sol-blue); --hl3: var(--sol-green); --hl4: var(--sol-yellow); --hl5: var(--sol-magenta); /* Scrollbar */ --scrollbar-track: var(--base03); --scrollbar-thumb: var(--base00); --scrollbar-hover: var(--base01); /* Misc. */ --drop-file-border-colour: var(--base01); --table-border-colour: var(--base01); --popover-background: var(--base02); --popover-border-colour: var(--base01); --code-background: var(--base03); --code-font-colour: var(--base1); --input-highlight-colour: var(--base1); --input-border-colour: var(--base0); } ================================================ FILE: src/web/stylesheets/themes/_solarizedLight.css ================================================ /** * Solarized light theme definitions * * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ :root.solarizedLight { --base03: #002b36; --base02: #073642; --base01: #586e75; --base00: #657b83; --base0: #839496; --base1: #93a1a1; --base2: #eee8d5; --base3: #fdf6e3; --sol-yellow: #b58900; --sol-orange: #cb4b16; --sol-red: #dc322f; --sol-magenta: #d33682; --sol-violet: #6c71c4; --sol-blue: #268bd2; --sol-cyan: #2aa198; --sol-green: #859900; --primary-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --primary-font-colour: var(--base00); --primary-font-size: 14px; --primary-line-height: 20px; --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --fixed-width-font-colour: inherit; --fixed-width-font-size: inherit; --subtext-font-colour: var(--base1); --subtext-font-size: 13px; --primary-background-colour: var(--base3); --secondary-background-colour: var(--base2); --primary-border-colour: var(--base0); --secondary-border-colour: var(--base1); --title-colour: var(--base01); --title-weight: bold; --title-background-colour: var(--base2); --banner-font-colour: var(--base00); --banner-bg-colour: var(--base3); --banner-url-colour: var(--base01); --category-list-font-colour: var(--base01); --loader-background-colour: var(--base3); --loader-outer-colour: var(--base01); --loader-middle-colour: var(--base00); --loader-inner-colour: var(--base0); /* Operation colours */ --op-list-operation-font-colour: var(--base00); --op-list-operation-bg-colour: var(--base3); --op-list-operation-border-colour: var(--base2); --rec-list-operation-font-colour: var(--base00); --rec-list-operation-bg-colour: var(--base2); --rec-list-operation-border-colour: var(--base1); --selected-operation-font-color: var(--base01); --selected-operation-bg-colour: var(--base2); --selected-operation-border-colour: var(--base1); --breakpoint-font-colour: var(--sol-red); --breakpoint-bg-colour: var(--base2); --breakpoint-border-colour: var(--base0); --disabled-font-colour: var(--base1); --disabled-bg-colour: var(--base3); --disabled-border-colour: var(--base2); --fc-operation-font-colour: var(--base01); --fc-operation-bg-colour: var(--base2); --fc-operation-border-colour: var(--base1); --fc-breakpoint-operation-font-colour: var(--base02); --fc-breakpoint-operation-bg-colour: var(--base1); --fc-breakpoint-operation-border-colour: var(--base0); /* Operation arguments */ --op-title-font-weight: bold; --arg-font-colour: var(--base00); --arg-background: var(--base3); --arg-border-colour: var(--base0); --arg-disabled-background: var(--base3); --arg-label-colour: var(--base01); /* Operation buttons */ --disable-icon-colour: #9e9e9e; --disable-icon-selected-colour: #f44336; --breakpoint-icon-colour: #9e9e9e; --breakpoint-icon-selected-colour: #f44336; /* Buttons */ --btn-default-font-colour: var(--base00); --btn-default-bg-colour: var(--base2); --btn-default-border-colour: var(--base1); --btn-default-hover-font-colour: var(--base01); --btn-default-hover-bg-colour: var(--base1); --btn-default-hover-border-colour: var(--base0); --btn-success-font-colour: var(--base00); --btn-success-bg-colour: var(--base3); --btn-success-border-colour: var(--base0); --btn-success-hover-font-colour: var(--base01); --btn-success-hover-bg-colour: var(--base1); --btn-success-hover-border-colour: var(--base0); /* Highlighter colours */ --hl1: var(--base1); --hl2: var(--sol-blue); --hl3: var(--sol-green); --hl4: var(--sol-yellow); --hl5: var(--sol-magenta); /* Scrollbar */ --scrollbar-track: var(--base3); --scrollbar-thumb: var(--base1); --scrollbar-hover: var(--base0); /* Misc. */ --drop-file-border-colour: var(--base1); --table-border-colour: var(--base1); --popover-background: var(--base2); --popover-border-colour: var(--base1); --code-background: var(--base3); --code-font-colour: var(--base01); --input-highlight-colour: var(--base01); --input-border-colour: var(--base00); } ================================================ FILE: src/web/stylesheets/utils/_general.css ================================================ /** * General styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ body { font-family: var(--primary-font-family); font-size: var(--primary-font-size); line-height: var(--primary-line-height); color: var(--primary-font-colour); background-color: var(--primary-background-colour); } .subtext { font-style: italic; font-size: var(--subtext-font-size); color: var(--subtext-font-colour); } .data-text { font-family: var(--fixed-width-font-family); } .word-wrap { white-space: pre !important; word-wrap: normal !important; overflow-x: scroll !important; } .clearfix { clear: both; height: 0; line-height: 0; } .hidden { display: none; } .blur { color: transparent !important; text-shadow: rgba(0, 0, 0, 0.95) 0 0 10px !important; } .no-select { user-select: none; } .inline-icon { font-size: 12px; padding-left: 2px; } .modal-icon { position: absolute; right: 25px; } .konami { transform: rotate(180deg); } ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background-color: var(--scrollbar-track); } ::-webkit-scrollbar-thumb { background-color: var(--scrollbar-thumb); } ::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); } ::-webkit-scrollbar-corner { background-color: var(--scrollbar-track); } /* Highlighters */ .hl1 { background-color: var(--hl1); } .hl2 { background-color: var(--hl2); } .hl3 { background-color: var(--hl3); } /* Half-Life 3 confirmed :O */ .hl4 { background-color: var(--hl4); } .hl5 { background-color: var(--hl5); } ================================================ FILE: src/web/stylesheets/utils/_overrides.css ================================================ /** * Overrides for vendor styles * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ /* Bootstrap */ /* fallback */ @font-face { font-family: 'Material Icons'; font-style: normal; font-weight: 400; src: url("../static/fonts/MaterialIcons-Regular.ttf") format('truetype'); } .material-icons { font-family: 'Material Icons'; font-weight: normal; font-style: normal; font-size: 24px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; font-feature-settings: 'liga'; -webkit-font-smoothing: antialiased; } .form-group { margin-bottom: 0; } button, a:focus { outline: none; } .btn.btn-raised.btn-secondary { color: var(--btn-default-font-colour); background-color: var(--btn-default-bg-colour); border-color: var(--btn-default-border-colour); } .btn.btn-raised.btn-secondary:hover, .btn.btn-raised.btn-secondary:active, .btn.btn-raised.btn-secondary:hover:active { color: var(--btn-default-hover-font-colour); background-color: var(--btn-default-hover-bg-colour); border-color: var(--btn-default-hover-border-colour); } .btn.btn-raised.btn-secondary:focus { color: var(--btn-default-font-colour); background-color: var(--btn-default-bg-colour); border-color: var(--btn-default-hover-border-colour); } .btn.btn-raised.btn-secondary[disabled]:hover { background-color: var(--primary-background-colour); border-color: var(--primary-border-colour); } .btn.btn-raised.btn-success { color: var(--btn-success-font-colour); background-color: var(--btn-success-bg-colour); border-color: var(--btn-success-border-colour); } .btn.btn-raised.btn-success:hover, .btn.btn-raised.btn-success:active, .btn.btn-raised.btn-success:focus, .btn.btn-raised.btn-success:hover:active { color: var(--btn-success-hover-font-colour); background-color: var(--btn-success-hover-bg-colour); border-color: var(--btn-success-hover-border-colour); } select.form-control, select.form-control:focus { background-color: var(--primary-background-colour) !important; } select.form-control:focus { transition: none !important; } select.form-control:not([size]):not([multiple]), select.custom-file-control:not([size]):not([multiple]) { height: unset !important; } .checkbox label, .checkbox-inline, .is-focused .checkbox-inline, .is-focused .checkbox-inline:hover, [class^="bmd-label"], .form-control, .is-focused .form-control { color: var(--primary-font-colour); } .form-control, .is-focused .form-control { background-image: linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px), linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); } code { border: 0; white-space: pre-wrap; font-family: var(--fixed-width-font-family); background-color: var(--code-background); color: var(--code-font-colour); } pre { border-radius: 0 !important; background-color: var(--secondary-background-colour); border-color: var(--secondary-border-colour); color: var(--fixed-width-font-colour); } blockquote { font-size: inherit; border-left-color: var(--secondary-border-colour); } blockquote a { cursor: pointer; } optgroup { font-weight: bold; } .panel-body:before, .panel-body:after { content: ""; } .table-nonfluid { width: auto !important; } .table, .table-hover tbody tr:hover { color: var(--primary-font-colour); } .table-bordered th, .table-bordered td { border: 1px solid var(--table-border-colour); } .popover { background-color: var(--popover-background); border-color: var(--popover-border-colour); } .popover-body { max-height: 95vh; overflow-y: auto; color: var(--primary-font-colour); } .bs-popover-right>.arrow { border-right-color: var(--popover-border-colour); } .bs-popover-right>.arrow:after { border-right-color: var(--popover-background); } .nav-tabs .nav-link { color: var(--subtext-font-colour); } .nav-tabs>li>a.nav-link.active, .nav-tabs>li>a.nav-link.active:focus, .nav-tabs>li>a.nav-link.active:hover { background-color: var(--secondary-background-colour); border-color: var(--secondary-border-colour); border-bottom-color: transparent; color: var(--primary-font-colour); } .nav-tabs { border-color: var(--primary-border-colour); } .nav a.nav-link:focus, .nav a.nav-link:hover { background-color: var(--secondary-border-colour); } .nav-tabs a.nav-link:hover { border-color: var(--secondary-border-colour) var(--secondary-border-colour) var(--primary-border-colour); } .dropdown-menu { background-color: var(--primary-background-colour); } .dropdown-menu a { color: var(--primary-font-colour); } .dropdown-menu a:focus, .dropdown-menu a:hover { background-color: var(--secondary-background-colour); color: var(--primary-font-colour); } .input-group-addon:not(:first-child):not(:last-child) { border-left: 0; border-right: 0; } .input-group-btn:first-child>.btn { border-right: 0; } /* Sortable */ .sortable-ghost { opacity: 0.6; } /* Bootstrap Colorpicker */ .colorpicker-element { float: left; margin-right: 15px; } .colorpicker-color, .colorpicker-color div { height: 100px; } /* Bootstrap form inside CodeMirror editor */ .cm-panel > .bmd-form-group { padding-top: 0; } /* CodeMirror */ .ͼ2 .cm-specialChar, .cm-specialChar { color: red; } ================================================ FILE: src/web/utils/copyOverride.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 * * In order to render whitespace characters as control character pictures in the output, even * when they are the designated line separator, CyberChef sometimes chooses to represent them * internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas). * See `Utils.escapeWhitespace()` for an example of this. * * The `renderSpecialChar()` function understands that it should display these characters as * control pictures. When copying data from the Output, we need to replace these PUA characters * with their original values, so we override the DOM "copy" event and modify the copied data * if required. This handler is based closely on the built-in CodeMirror handler and defers to the * built-in handler if PUA characters are not present in the copied data, in order to minimise the * impact of breaking changes. */ import {EditorView} from "@codemirror/view"; /** * Copies the currently selected text from the state doc. * Based on the built-in implementation with a few unrequired bits taken out: * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L604 * * @param {EditorState} state * @returns {Object} */ function copiedRange(state) { const content = []; let linewise = false; for (const range of state.selection.ranges) if (!range.empty) { content.push(state.sliceDoc(range.from, range.to)); } if (!content.length) { // Nothing selected, do a line-wise copy let upto = -1; for (const {from} of state.selection.ranges) { const line = state.doc.lineAt(from); if (line.number > upto) { content.push(line.text); } upto = line.number; } linewise = true; } return {text: content.join(state.lineBreak), linewise}; } /** * Regex to match characters in the Private Use Area of the Unicode table. */ const PUARegex = new RegExp("[\ue000-\uf8ff]"); const PUARegexG = new RegExp("[\ue000-\uf8ff]", "g"); /** * Regex tto match Unicode Control Pictures. */ const CPRegex = new RegExp("[\u2400-\u243f]"); const CPRegexG = new RegExp("[\u2400-\u243f]", "g"); /** * Overrides the DOM "copy" handler in the CodeMirror editor in order to return the original * values of control characters that have been represented in the Unicode Private Use Area for * visual purposes. * Based on the built-in copy handler with some modifications: * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L629 * * This handler will defer to the built-in version if no PUA characters are present. * * @returns {Extension} */ export function copyOverride() { return EditorView.domEventHandlers({ copy(event, view) { const {text, linewise} = copiedRange(view.state); if (!text && !linewise) return; // If there are no PUA chars in the copied text, return false and allow the built-in // copy handler to fire if (!PUARegex.test(text)) return false; // If PUA chars are detected, modify them back to their original values and copy that instead const rawText = text.replace(PUARegexG, function(c) { return String.fromCharCode(c.charCodeAt(0) - 0xe000); }); event.preventDefault(); event.clipboardData.clearData(); event.clipboardData.setData("text/plain", rawText); // Returning true prevents CodeMirror default handlers from firing return true; } }); } /** * Handler for copy events in output-html decorations. If there are control pictures present, * this handler will convert them back to their raw form before copying. If there are no * control pictures present, it will do nothing and defer to the default browser handler. * * @param {ClipboardEvent} event * @returns {boolean} */ export function htmlCopyOverride(event) { const text = window.getSelection().toString(); if (!text) return; // If there are no control picture chars in the copied text, return false and allow the built-in // copy handler to fire if (!CPRegex.test(text)) return false; // If control picture chars are detected, modify them back to their original values and copy that instead const rawText = text.replace(CPRegexG, function(c) { return String.fromCharCode(c.charCodeAt(0) - 0x2400); }); event.preventDefault(); event.clipboardData.clearData(); event.clipboardData.setData("text/plain", rawText); return true; } ================================================ FILE: src/web/utils/editorUtils.mjs ================================================ /** * CodeMirror utilities that are relevant to both the input and output * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Utils from "../../core/Utils.mjs"; // Descriptions for named control characters const Names = { 0: "null", 7: "bell", 8: "backspace", 10: "line feed", 11: "vertical tab", 13: "carriage return", 27: "escape", 8203: "zero width space", 8204: "zero width non-joiner", 8205: "zero width joiner", 8206: "left-to-right mark", 8207: "right-to-left mark", 8232: "line separator", 8237: "left-to-right override", 8238: "right-to-left override", 8294: "left-to-right isolate", 8295: "right-to-left isolate", 8297: "pop directional isolate", 8233: "paragraph separator", 65279: "zero width no-break space", 65532: "object replacement" }; // Regex for Special Characters to be replaced const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g"; const 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); /** * Override for rendering special characters. * Should mirror the toDOM function in * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L153 * But reverts the replacement of line feeds with newline control pictures. * * @param {number} code * @param {string} desc * @param {string} placeholder * @returns {element} */ export function renderSpecialChar(code, desc, placeholder) { const s = document.createElement("span"); // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back along with its description. if (code === 0x0a) { placeholder = "\u240a"; desc = desc.replace("newline", "line feed"); } // Render CyberChef escaped characters correctly - see Utils.escapeWhitespace if (code >= 0xe000 && code <= 0xf8ff) { code = code - 0xe000; placeholder = String.fromCharCode(0x2400 + code); desc = "Control character " + (Names[code] || "0x" + code.toString(16)); } s.textContent = placeholder; s.title = desc; s.setAttribute("aria-label", desc); s.className = "cm-specialChar"; return s; } /** * Given a string, returns that string with any control characters replaced with HTML * renderings of control pictures. * * @param {string} str * @param {boolean} [preserveWs=false] * @param {string} [lineBreak="\n"] * @returns {html} */ export function escapeControlChars(str, preserveWs=false, lineBreak="\n") { if (!preserveWs) str = Utils.escapeWhitespace(str); return str.replace(Specials, function(c) { if (lineBreak.includes(c)) return c; const code = c.charCodeAt(0); const desc = "Control character " + (Names[code] || "0x" + code.toString(16)); const placeholder = code > 32 ? "\u2022" : String.fromCharCode(9216 + code); const n = renderSpecialChar(code, desc, placeholder); return n.outerHTML; }); } /** * Convert and EOL sequence to its name */ export const eolSeqToCode = { "\u000a": "LF", "\u000b": "VT", "\u000c": "FF", "\u000d": "CR", "\u000d\u000a": "CRLF", "\u0085": "NEL", "\u2028": "LS", "\u2029": "PS" }; /** * Convert an EOL name to its sequence */ export const eolCodeToSeq = { "LF": "\u000a", "VT": "\u000b", "FF": "\u000c", "CR": "\u000d", "CRLF": "\u000d\u000a", "NEL": "\u0085", "LS": "\u2028", "PS": "\u2029" }; export const eolCodeToName = { "LF": "Line Feed", "VT": "Vertical Tab", "FF": "Form Feed", "CR": "Carriage Return", "CRLF": "Carriage Return + Line Feed", "NEL": "Next Line", "LS": "Line Separator", "PS": "Paragraph Separator" }; ================================================ FILE: src/web/utils/fileDetails.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import {showSidePanel} from "./sidePanel.mjs"; import Utils from "../../core/Utils.mjs"; import {isImage, detectFileType} from "../../core/lib/FileType.mjs"; /** * A File Details extension for CodeMirror */ class FileDetailsPanel { /** * FileDetailsPanel constructor * @param {Object} opts */ constructor(opts) { this.fileDetails = opts?.fileDetails; this.progress = opts?.progress ?? 0; this.status = opts?.status; this.buffer = opts?.buffer; this.renderPreview = opts?.renderPreview; this.toggleHandler = opts?.toggleHandler; this.hidden = opts?.hidden; this.dom = this.buildDOM(); this.renderFileThumb(); } /** * Builds the file details DOM tree * @returns {DOMNode} */ buildDOM() { const dom = document.createElement("div"); dom.className = "cm-file-details"; const fileThumb = require("../static/images/file-128x128.png"); dom.innerHTML = `
        ${this.hidden ? "❰" : "❱"}

        File details

        Name: ${Utils.escapeHtml(this.fileDetails?.name)}
        Size: ${Utils.escapeHtml(this.fileDetails?.size)} bytes
        Type: ${Utils.escapeHtml(this.fileDetails?.type)}
        Loaded: ${this.status === "error" ? "Error" : this.progress + "%"}
        `; dom.querySelector(".file-details-toggle-shown,.file-details-toggle-hidden") .addEventListener("click", this.toggleHandler, false); return dom; } /** * Render the file thumbnail */ renderFileThumb() { if (!this.renderPreview) { this.resetFileThumb(); return; } const fileThumb = this.dom.querySelector(".file-details-thumbnail"); const fileType = this.dom.querySelector(".file-details-type"); const fileBuffer = new Uint8Array(this.buffer); const type = isImage(fileBuffer); if (type && type !== "image/tiff" && fileBuffer.byteLength <= 512000) { // Most browsers don't support displaying TIFFs, so ignore them // Don't render images over 512,000 bytes const blob = new Blob([fileBuffer], {type: type}), url = URL.createObjectURL(blob); fileThumb.src = url; } else { this.resetFileThumb(); } fileType.textContent = type ? type : detectFileType(fileBuffer)[0]?.mime ?? "unknown"; } /** * Reset the file thumbnail to the default icon */ resetFileThumb() { const fileThumb = this.dom.querySelector(".file-details-thumbnail"); fileThumb.src = require("../static/images/file-128x128.png"); } } /** * A panel constructor factory building a panel that displays file details * @param {Object} opts * @returns {Function} */ function makePanel(opts) { const fdPanel = new FileDetailsPanel(opts); return (view) => { return { dom: fdPanel.dom, width: opts?.hidden ? 1 : 200, update(update) { }, mount() { $("[data-toggle='tooltip']").tooltip(); } }; }; } /** * A function that build the extension that enables the panel in an editor. * @param {Object} opts * @returns {Extension} */ export function fileDetailsPanel(opts) { const panelMaker = makePanel(opts); return showSidePanel.of(panelMaker); } ================================================ FILE: src/web/utils/htmlWidget.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import {WidgetType, Decoration, ViewPlugin} from "@codemirror/view"; import {escapeControlChars} from "./editorUtils.mjs"; import {htmlCopyOverride} from "./copyOverride.mjs"; import Utils from "../../core/Utils.mjs"; /** * Adds an HTML widget to the Code Mirror editor */ class HTMLWidget extends WidgetType { /** * HTMLWidget consructor */ constructor(html, view) { super(); this.html = html; this.view = view; } /** * Builds the DOM node * @returns {DOMNode} */ toDOM() { const wrap = document.createElement("span"); wrap.setAttribute("id", "output-html"); wrap.innerHTML = this.html; // Find text nodes and replace unprintable chars with control codes this.walkTextNodes(wrap); // Add a handler for copy events to ensure the control codes are copied correctly wrap.addEventListener("copy", htmlCopyOverride); return wrap; } /** * Walks all text nodes in a given element * @param {DOMNode} el */ walkTextNodes(el) { for (const node of el.childNodes) { switch (node.nodeType) { case Node.TEXT_NODE: this.replaceControlChars(node); break; default: if (node.nodeName !== "SCRIPT" && node.nodeName !== "STYLE") this.walkTextNodes(node); break; } } } /** * Renders control characters in text nodes * @param {DOMNode} textNode */ replaceControlChars(textNode) { // .nodeValue unencodes HTML encoding such as < to "<" // We must remember to escape any potential HTML in TextNodes as we do not // want to render it. const textValue = Utils.escapeHtml(textNode.nodeValue); const val = escapeControlChars(textValue, true, this.view.state.lineBreak); if (val.length !== textNode.nodeValue.length) { const node = document.createElement("span"); node.innerHTML = val; textNode.parentNode.replaceChild(node, textNode); } } } /** * Decorator function to provide a set of widgets for the editor DOM * @param {EditorView} view * @param {string} html * @returns {DecorationSet} */ function decorateHTML(view, html) { const widgets = []; if (html.length) { const deco = Decoration.widget({ widget: new HTMLWidget(html, view), side: 1 }); widgets.push(deco.range(0)); } return Decoration.set(widgets); } /** * An HTML Plugin builder * @param {Object} htmlOutput * @returns {ViewPlugin} */ export function htmlPlugin(htmlOutput) { const plugin = ViewPlugin.fromClass( class { /** * Plugin constructor * @param {EditorView} view */ constructor(view) { this.htmlOutput = htmlOutput; this.decorations = decorateHTML(view, this.htmlOutput.html); } /** * Editor update listener * @param {ViewUpdate} update */ update(update) { if (this.htmlOutput.changed) { this.decorations = decorateHTML(update.view, this.htmlOutput.html); this.htmlOutput.changed = false; } } }, { decorations: v => v.decorations } ); return plugin; } ================================================ FILE: src/web/utils/sidePanel.mjs ================================================ /** * A modification of the CodeMirror Panel extension to enable panels to the * left and right of the editor. * Based on code here: https://github.com/codemirror/view/blob/main/src/panel.ts * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import {EditorView, ViewPlugin} from "@codemirror/view"; import {Facet} from "@codemirror/state"; const panelConfig = Facet.define({ combine(configs) { let leftContainer, rightContainer; for (const c of configs) { leftContainer = leftContainer || c.leftContainer; rightContainer = rightContainer || c.rightContainer; } return {leftContainer, rightContainer}; } }); /** * Configures the panel-managing extension. * @param {PanelConfig} config * @returns Extension */ export function panels(config) { return config ? [panelConfig.of(config)] : []; } /** * Get the active panel created by the given constructor, if any. * This can be useful when you need access to your panels' DOM * structure. * @param {EditorView} view * @param {PanelConstructor} panel * @returns {Panel} */ export function getPanel(view, panel) { const plugin = view.plugin(panelPlugin); const index = plugin ? plugin.specs.indexOf(panel) : -1; return index > -1 ? plugin.panels[index] : null; } const panelPlugin = ViewPlugin.fromClass(class { /** * @param {EditorView} view */ constructor(view) { this.input = view.state.facet(showSidePanel); this.specs = this.input.filter(s => s); this.panels = this.specs.map(spec => spec(view)); const conf = view.state.facet(panelConfig); this.left = new PanelGroup(view, true, conf.leftContainer); this.right = new PanelGroup(view, false, conf.rightContainer); this.left.sync(this.panels.filter(p => p.left)); this.right.sync(this.panels.filter(p => !p.left)); for (const p of this.panels) { p.dom.classList.add("cm-panel"); if (p.mount) p.mount(); } } /** * @param {ViewUpdate} update */ update(update) { const conf = update.state.facet(panelConfig); if (this.left.container !== conf.leftContainer) { this.left.sync([]); this.left = new PanelGroup(update.view, true, conf.leftContainer); } if (this.right.container !== conf.rightContainer) { this.right.sync([]); this.right = new PanelGroup(update.view, false, conf.rightContainer); } this.left.syncClasses(); this.right.syncClasses(); const input = update.state.facet(showSidePanel); if (input !== this.input) { const specs = input.filter(x => x); const panels = [], left = [], right = [], mount = []; for (const spec of specs) { const known = this.specs.indexOf(spec); let panel; if (known < 0) { panel = spec(update.view); mount.push(panel); } else { panel = this.panels[known]; if (panel.update) panel.update(update); } panels.push(panel) ;(panel.left ? left : right).push(panel); } this.specs = specs; this.panels = panels; this.left.sync(left); this.right.sync(right); for (const p of mount) { p.dom.classList.add("cm-panel"); if (p.mount) p.mount(); } } else { for (const p of this.panels) if (p.update) p.update(update); } } /** * Destroy panel */ destroy() { this.left.sync([]); this.right.sync([]); } }, { // provide: PluginField.scrollMargins.from(value => ({left: value.left.scrollMargin(), right: value.right.scrollMargin()})) }); /** * PanelGroup */ class PanelGroup { /** * @param {EditorView} view * @param {boolean} left * @param {HTMLElement} container */ constructor(view, left, container) { this.view = view; this.left = left; this.container = container; this.dom = undefined; this.classes = ""; this.panels = []; this.syncClasses(); } /** * @param {Panel[]} panels */ sync(panels) { for (const p of this.panels) if (p.destroy && panels.indexOf(p) < 0) p.destroy(); this.panels = panels; this.syncDOM(); } /** * Synchronise the DOM */ syncDOM() { if (this.panels.length === 0) { if (this.dom) { this.dom.remove(); this.dom = undefined; this.setScrollerMargin(0); } return; } const parent = this.container || this.view.dom; if (!this.dom) { this.dom = document.createElement("div"); this.dom.className = this.left ? "cm-side-panels cm-panels-left" : "cm-side-panels cm-panels-right"; parent.insertBefore(this.dom, parent.firstChild); } let curDOM = this.dom.firstChild; let bufferWidth = 0; for (const panel of this.panels) { bufferWidth += panel.width; if (panel.dom.parentNode === this.dom) { while (curDOM !== panel.dom) curDOM = rm(curDOM); curDOM = curDOM.nextSibling; } else { this.dom.insertBefore(panel.dom, curDOM); panel.dom.style.width = panel.width + "px"; this.dom.style.width = bufferWidth + "px"; } } while (curDOM) curDOM = rm(curDOM); this.setScrollerMargin(bufferWidth); } /** * Sets the margin of the cm-scroller element to make room for the panel */ setScrollerMargin(width) { const parent = this.container || this.view.dom; const margin = this.left ? "marginLeft" : "marginRight"; parent.querySelector(".cm-scroller").style[margin] = width + "px"; } /** * */ scrollMargin() { return !this.dom || this.container ? 0 : Math.max(0, this.left ? this.dom.getBoundingClientRect().right - Math.max(0, this.view.scrollDOM.getBoundingClientRect().left) : Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().right) - this.dom.getBoundingClientRect().left); } /** * */ syncClasses() { if (!this.container || this.classes === this.view.themeClasses) return; for (const cls of this.classes.split(" ")) if (cls) this.container.classList.remove(cls); for (const cls of (this.classes = this.view.themeClasses).split(" ")) if (cls) this.container.classList.add(cls); } } /** * @param {ChildNode} node * @returns HTMLElement */ function rm(node) { const next = node.nextSibling; node.remove(); return next; } const baseTheme = EditorView.baseTheme({ ".cm-side-panels": { boxSizing: "border-box", position: "absolute", height: "100%", top: 0, bottom: 0 }, "&light .cm-side-panels": { backgroundColor: "#f5f5f5", color: "black" }, "&light .cm-panels-left": { borderRight: "1px solid #ddd", left: 0 }, "&light .cm-panels-right": { borderLeft: "1px solid #ddd", right: 0 }, "&dark .cm-side-panels": { backgroundColor: "#333338", color: "white" } }); /** * Opening a panel is done by providing a constructor function for * the panel through this facet. (The panel is closed again when its * constructor is no longer provided.) Values of `null` are ignored. */ export const showSidePanel = Facet.define({ enables: [panelPlugin, baseTheme] }); ================================================ FILE: src/web/utils/statusBar.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import {showPanel} from "@codemirror/view"; import {CHR_ENC_SIMPLE_LOOKUP, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from "../../core/lib/ChrEnc.mjs"; import { eolCodeToName, eolSeqToCode } from "./editorUtils.mjs"; /** * A Status bar extension for CodeMirror */ class StatusBarPanel { /** * StatusBarPanel constructor * @param {Object} opts */ constructor(opts) { this.label = opts.label; this.timing = opts.timing; this.tabNumGetter = opts.tabNumGetter; this.eolHandler = opts.eolHandler; this.chrEncHandler = opts.chrEncHandler; this.chrEncGetter = opts.chrEncGetter; this.getEncodingState = opts.getEncodingState; this.getEOLState = opts.getEOLState; this.htmlOutput = opts.htmlOutput; this.eolVal = null; this.chrEncVal = null; this.dom = this.buildDOM(); } /** * Builds the status bar DOM tree * @returns {DOMNode} */ buildDOM() { const dom = document.createElement("div"); const lhs = document.createElement("div"); const rhs = document.createElement("div"); dom.className = "cm-status-bar"; dom.setAttribute("data-help-title", `${this.label} status bar`); 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.`); lhs.innerHTML = this.constructLHS(); rhs.innerHTML = this.constructRHS(); dom.appendChild(lhs); dom.appendChild(rhs); // Event listeners dom.querySelectorAll(".cm-status-bar-select-btn").forEach( el => el.addEventListener("click", this.showDropUp.bind(this), false) ); dom.querySelector(".eol-select").addEventListener("click", this.eolSelectClick.bind(this), false); dom.querySelector(".chr-enc-select").addEventListener("click", this.chrEncSelectClick.bind(this), false); dom.querySelector(".cm-status-bar-filter-input").addEventListener("keyup", this.chrEncFilter.bind(this), false); return dom; } /** * Handler for dropup clicks * Shows/Hides the dropup * @param {Event} e */ showDropUp(e) { const el = e.target .closest(".cm-status-bar-select") .querySelector(".cm-status-bar-select-content"); const btn = e.target.closest(".cm-status-bar-select-btn"); if (btn.classList.contains("disabled")) return; el.classList.add("show"); // Focus the filter input if present const filter = el.querySelector(".cm-status-bar-filter-input"); if (filter) filter.focus(); // Set up a listener to close the menu if the user clicks outside of it hideOnClickOutside(el, e); } /** * Handler for EOL Select clicks * Sets the line separator * @param {Event} e */ eolSelectClick(e) { // preventDefault is required to stop the URL being modified and popState being triggered e.preventDefault(); const eolCode = e.target.getAttribute("data-val"); if (!eolCode) return; // Call relevant EOL change handler this.eolHandler(e.target.getAttribute("data-val"), true); hideElement(e.target.closest(".cm-status-bar-select-content")); } /** * Handler for Chr Enc Select clicks * Sets the character encoding * @param {Event} e */ chrEncSelectClick(e) { // preventDefault is required to stop the URL being modified and popState being triggered e.preventDefault(); const chrEncVal = parseInt(e.target.getAttribute("data-val"), 10); if (isNaN(chrEncVal)) return; this.chrEncHandler(chrEncVal, true); this.updateCharEnc(chrEncVal); hideElement(e.target.closest(".cm-status-bar-select-content")); } /** * Handler for Chr Enc keyup events * Filters the list of selectable character encodings * @param {Event} e */ chrEncFilter(e) { const input = e.target; const filter = input.value.toLowerCase(); const div = input.closest(".cm-status-bar-select-content"); const a = div.getElementsByTagName("a"); for (let i = 0; i < a.length; i++) { const txtValue = a[i].textContent || a[i].innerText; if (txtValue.toLowerCase().includes(filter)) { a[i].style.display = "block"; } else { a[i].style.display = "none"; } } } /** * Counts the stats of a document * @param {EditorState} state */ updateStats(state) { const length = this.dom.querySelector(".stats-length-value"), lines = this.dom.querySelector(".stats-lines-value"); let docLength = state.doc.length; // CodeMirror always counts line breaks as one character. // We want to show an accurate reading of how many bytes there are. if (state.lineBreak.length !== 1) { docLength += (state.lineBreak.length * state.doc.lines) - state.doc.lines - 1; } length.textContent = docLength; lines.textContent = state.doc.lines; } /** * Gets the current selection info * @param {EditorState} state * @param {boolean} selectionSet */ updateSelection(state, selectionSet) { const selLen = state?.selection?.main ? state.selection.main.to - state.selection.main.from : 0; const selInfo = this.dom.querySelector(".sel-info"), curOffsetInfo = this.dom.querySelector(".cur-offset-info"); if (!selectionSet) { selInfo.style.display = "none"; curOffsetInfo.style.display = "none"; return; } // CodeMirror always counts line breaks as one character. // We want to show an accurate reading of how many bytes there are. let from = state.selection.main.from, to = state.selection.main.to; if (state.lineBreak.length !== 1) { const fromLine = state.doc.lineAt(from).number; const toLine = state.doc.lineAt(to).number; from += (state.lineBreak.length * fromLine) - fromLine - 1; to += (state.lineBreak.length * toLine) - toLine - 1; } if (selLen > 0) { // Range const start = this.dom.querySelector(".sel-start-value"), end = this.dom.querySelector(".sel-end-value"), length = this.dom.querySelector(".sel-length-value"); selInfo.style.display = "inline-block"; curOffsetInfo.style.display = "none"; start.textContent = from; end.textContent = to; length.textContent = to - from; } else { // Position const offset = this.dom.querySelector(".cur-offset-value"); selInfo.style.display = "none"; curOffsetInfo.style.display = "inline-block"; offset.textContent = from; } } /** * Sets the current EOL separator in the status bar * @param {EditorState} state */ updateEOL(state) { if (this.getEOLState() < 2 && state.lineBreak === this.eolVal) return; const val = this.dom.querySelector(".eol-value"); const button = val.closest(".cm-status-bar-select-btn"); let eolCode = eolSeqToCode[state.lineBreak]; let eolName = eolCodeToName[eolCode]; switch (this.getEOLState()) { case 1: // Detected val.classList.add("font-italic"); eolCode += " (detected)"; eolName += " (detected)"; // Pulse val.classList.add("pulse"); setTimeout(() => { val.classList.remove("pulse"); }, 2000); break; case 0: // Unset case 2: // Manually set default: val.classList.remove("font-italic"); break; } val.textContent = eolCode; button.setAttribute("title", `End of line sequence:
        ${eolName}`); button.setAttribute("data-original-title", `End of line sequence:
        ${eolName}`); this.eolVal = state.lineBreak; } /** * Sets the current character encoding of the document */ updateCharEnc() { const chrEncVal = this.chrEncGetter(); if (this.getEncodingState() < 2 && chrEncVal === this.chrEncVal) return; let name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] ? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] : "Raw Bytes"; const val = this.dom.querySelector(".chr-enc-value"); const button = val.closest(".cm-status-bar-select-btn"); switch (this.getEncodingState()) { case 1: // Detected val.classList.add("font-italic"); name += " (detected)"; // Pulse val.classList.add("pulse"); setTimeout(() => { val.classList.remove("pulse"); }, 2000); break; case 0: // Unset case 2: // Manually set default: val.classList.remove("font-italic"); break; } val.textContent = name; button.setAttribute("title", `${this.label} character encoding:
        ${name}`); button.setAttribute("data-original-title", `${this.label} character encoding:
        ${name}`); this.chrEncVal = chrEncVal; } /** * Sets the latest timing info */ updateTiming() { if (!this.timing) return; const bakingTime = this.dom.querySelector(".baking-time-value"); const bakingTimeInfo = this.dom.querySelector(".baking-time-info"); if (this.label === "Output" && this.timing) { bakingTimeInfo.style.display = "inline-block"; bakingTime.textContent = this.timing.duration(this.tabNumGetter()); const info = this.timing.printStages(this.tabNumGetter()).replace(/\n/g, "
        "); bakingTimeInfo.setAttribute("data-original-title", info); } else { bakingTimeInfo.style.display = "none"; } } /** * Updates the sizing of elements that need to fit correctly * @param {EditorView} view */ updateSizing(view) { const viewHeight = view.contentDOM.parentNode.clientHeight; this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach( el => { el.style.maxHeight = (viewHeight - 50) + "px"; } ); } /** * Checks whether there is HTML output requiring some widgets to be disabled */ monitorHTMLOutput() { if (!this.htmlOutput?.changed) return; if (this.htmlOutput?.html === "") { // Enable all controls this.dom.querySelectorAll(".disabled").forEach(el => { el.classList.remove("disabled"); }); } else { // Disable chrenc, length, selection etc. this.dom.querySelectorAll(".cm-status-bar-select-btn").forEach(el => { el.classList.add("disabled"); }); this.dom.querySelector(".stats-length-value").parentNode.classList.add("disabled"); this.dom.querySelector(".stats-lines-value").parentNode.classList.add("disabled"); this.dom.querySelector(".sel-info").classList.add("disabled"); this.dom.querySelector(".cur-offset-info").classList.add("disabled"); } } /** * Builds the Left-hand-side widgets * @returns {string} */ constructLHS() { return ` abc sort highlight_alt \u279E ( selected) location_on `; } /** * Builds the Right-hand-side widgets * Event listener set up in Manager * * @returns {string} */ constructRHS() { const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP).map(name => `${name}` ).join(""); let chrEncHelpText = "", eolHelpText = ""; if (this.label === "Input") { chrEncHelpText = "The input character encoding defines how the input text is encoded into bytes which are then processed by the Recipe.

        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."; 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.

        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."; } else { chrEncHelpText = "The output character encoding defines how the output bytes are decoded into text which can be displayed to you.

        The 'Raw bytes' option treats the output data as individual bytes in the range 0-255."; eolHelpText = "The End of Line Sequence defines which bytes are considered EOL terminators.

        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."; } return `
        text_fields Raw Bytes
        Raw Bytes ${chrEncOptions}
        `; } } const elementsWithListeners = {}; /** * Hides the provided element when a click is made outside of it * @param {Element} element * @param {Event} instantiatingEvent */ function hideOnClickOutside(element, instantiatingEvent) { /** * Handler for document click events * Closes element if click is outside it. * @param {Event} event */ const outsideClickListener = event => { // Don't trigger if we're clicking inside the element, or if the element // is not visible, or if this is the same click event that opened it. if (!element.contains(event.target) && event.timeStamp !== instantiatingEvent.timeStamp) { hideElement(element); } }; if (!Object.prototype.hasOwnProperty.call(elementsWithListeners, element)) { elementsWithListeners[element] = outsideClickListener; document.addEventListener("click", elementsWithListeners[element], false); } } /** * Hides the specified element and removes the click listener for it * @param {Element} element */ function hideElement(element) { element.classList.remove("show"); document.removeEventListener("click", elementsWithListeners[element], false); delete elementsWithListeners[element]; } /** * A panel constructor factory building a panel that re-counts the stats every time the document changes. * @param {Object} opts * @returns {Function} */ function makePanel(opts) { const sbPanel = new StatusBarPanel(opts); return (view) => { sbPanel.updateEOL(view.state); sbPanel.updateCharEnc(); sbPanel.updateTiming(); sbPanel.updateStats(view.state); sbPanel.updateSelection(view.state, false); sbPanel.monitorHTMLOutput(); return { "dom": sbPanel.dom, update(update) { sbPanel.updateEOL(update.state); sbPanel.updateCharEnc(); sbPanel.updateSelection(update.state, update.selectionSet); sbPanel.updateTiming(); sbPanel.monitorHTMLOutput(); if (update.geometryChanged) { sbPanel.updateSizing(update.view); } if (update.docChanged) { sbPanel.updateStats(update.state); } } }; }; } /** * A function that build the extension that enables the panel in an editor. * @param {Object} opts * @returns {Extension} */ export function statusBar(opts) { const panelMaker = makePanel(opts); return showPanel.of(panelMaker); } ================================================ FILE: src/web/waiters/BackgroundWorkerWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import ChefWorker from "worker-loader?inline=no-fallback!../../core/ChefWorker.js"; /** * Waiter to handle conversations with a ChefWorker in the background. */ class BackgroundWorkerWaiter { /** * BackgroundWorkerWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; this.callbacks = {}; this.callbackID = 0; this.completedCallback = -1; this.timeout = null; } /** * Sets up the ChefWorker and associated listeners. */ registerChefWorker() { log.debug("Registering new background ChefWorker"); this.chefWorker = new ChefWorker(); this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this)); this.chefWorker.postMessage({ action: "setLogPrefix", data: "BGChefWorker" }); this.chefWorker.postMessage({ action: "setLogLevel", data: log.getLevel() }); let docURL = document.location.href.split(/[#?]/)[0]; const index = docURL.lastIndexOf("/"); if (index > 0) { docURL = docURL.substring(0, index); } this.chefWorker.postMessage({"action": "docURL", "data": docURL}); } /** * Handler for messages sent back by the ChefWorker. * * @param {MessageEvent} e */ handleChefMessage(e) { const r = e.data; log.debug(`Receiving '${r.action}' from BGChefWorker`); switch (r.action) { case "bakeComplete": case "bakeError": if (typeof r.data.id !== "undefined") { clearTimeout(this.timeout); this.callbacks[r.data.id].bind(this)(r.data); this.completedCallback = r.data.id; } break; case "workerLoaded": log.debug("Background ChefWorker loaded"); break; case "optionUpdate": case "statusMessage": case "progressMessage": // Ignore these messages break; default: log.error("Unrecognised message from background ChefWorker", e); break; } } /** * Cancels the current bake by terminating the ChefWorker and creating a new one. */ cancelBake() { if (this.chefWorker) this.chefWorker.terminate(); this.registerChefWorker(); } /** * Asks the ChefWorker to bake the input using the specified recipe. * * @param {string} input * @param {Object[]} recipeConfig * @param {Object} options * @param {number} progress * @param {boolean} step * @param {Function} callback */ bake(input, recipeConfig, options, progress, step, callback) { const id = this.callbackID++; this.callbacks[id] = callback; this.chefWorker.postMessage({ action: "bake", data: { input: input, recipeConfig: recipeConfig, options: options, progress: progress, step: step, id: id } }); } /** * Asks the Magic operation what it can do with the input data. * * @param {string|ArrayBuffer} input */ magic(input) { // If we're still working on the previous bake, cancel it before starting a new one. if (this.completedCallback + 1 < this.callbackID) { clearTimeout(this.timeout); this.cancelBake(); } this.bake(input, [ { "op": "Magic", "args": [3, false, false] } ], {}, 0, false, this.magicComplete); // Cancel this bake if it takes too long. this.timeout = setTimeout(this.cancelBake.bind(this), 3000); } /** * Handler for completed Magic bakes. * * @param {Object} response */ magicComplete(response) { log.debug("--- Background Magic Bake complete ---"); if (!response || response.error) return; this.manager.output.backgroundMagicResult(response.dish.value); } /** * Sets the console log level in the workers. */ setLogLevel() { if (!this.chefWorker) return; this.chefWorker.postMessage({ action: "setLogLevel", data: log.getLevel() }); } } export default BackgroundWorkerWaiter; ================================================ FILE: src/web/waiters/BindingsWaiter.mjs ================================================ /** * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ /** * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.) */ class BindingsWaiter { /** * BindingsWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; } /** * Handler for all keydown events * Checks whether valid keyboard shortcut has been instated * * @fires Manager#statechange * @param {event} e */ parseInput(e) { const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey; if (e.ctrlKey && modKey) { let elem; switch (e.code) { case "KeyF": // Focus search e.preventDefault(); document.getElementById("search").focus(); break; case "KeyI": // Focus input e.preventDefault(); this.manager.input.inputEditorView.focus(); break; case "KeyO": // Focus output e.preventDefault(); this.manager.output.outputEditorView.focus(); break; case "Period": // Focus next operation e.preventDefault(); try { elem = document.activeElement.closest(".operation") || document.querySelector("#rec-list .operation"); if (elem.parentNode.lastChild === elem) { // If operation is last in recipe, loop around to the top operation's first argument elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); } else { // Focus first argument of next operation elem.nextSibling.querySelectorAll(".arg")[0].focus(); } } catch (e) { // do nothing, just don't throw an error } break; case "KeyB": // Set breakpoint e.preventDefault(); try { elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0]; if (elem.getAttribute("break") === "false") { elem.setAttribute("break", "true"); // add break point if not already enabled elem.classList.add("breakpoint-selected"); } else { elem.setAttribute("break", "false"); // remove break point if already enabled elem.classList.remove("breakpoint-selected"); } window.dispatchEvent(this.manager.statechange); } catch (e) { // do nothing, just don't throw an error } break; case "KeyD": // Disable operation e.preventDefault(); try { elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0]; if (elem.getAttribute("disabled") === "false") { elem.setAttribute("disabled", "true"); // disable operation if enabled elem.classList.add("disable-elem-selected"); elem.parentNode.parentNode.classList.add("disabled"); } else { elem.setAttribute("disabled", "false"); // enable operation if disabled elem.classList.remove("disable-elem-selected"); elem.parentNode.parentNode.classList.remove("disabled"); } this.app.progress = 0; window.dispatchEvent(this.manager.statechange); } catch (e) { // do nothing, just don't throw an error } break; case "Space": // Bake e.preventDefault(); this.manager.controls.bakeClick(); break; case "Quote": // Step through e.preventDefault(); this.manager.controls.stepClick(); break; case "KeyC": // Clear recipe e.preventDefault(); this.manager.recipe.clearRecipe(); break; case "KeyS": // Save output to file e.preventDefault(); this.manager.output.saveClick(); break; case "KeyL": // Load recipe e.preventDefault(); this.manager.controls.loadClick(); break; case "KeyM": // Switch input and output e.preventDefault(); this.manager.output.switchClick(); break; case "KeyT": // New tab e.preventDefault(); this.manager.input.addInputClick(); break; case "KeyW": // Close tab e.preventDefault(); this.manager.input.removeInput(this.manager.tabs.getActiveTab("input")); break; case "ArrowLeft": // Go to previous tab e.preventDefault(); this.manager.input.changeTabLeft(); break; case "ArrowRight": // Go to next tab e.preventDefault(); this.manager.input.changeTabRight(); break; default: if (e.code.match(/Digit[0-9]/g)) { // Select nth operation e.preventDefault(); try { // Select the first argument of the operation corresponding to the number pressed document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); } catch (e) { // do nothing, just don't throw an error } } break; } } else { switch (e.code) { case "F1": e.preventDefault(); this.contextualHelp(); break; } } } /** * Updates keybinding list when metaKey option is toggled */ updateKeybList() { let modWinLin = "Alt"; let modMac = "Opt"; if (this.app.options.useMetaKey) { modWinLin = "Win"; modMac = "Cmd"; } document.getElementById("keybList").innerHTML = ` Command Shortcut (Win/Linux) Shortcut (Mac) Activate contextual help F1 F1 Place cursor in search field Ctrl+${modWinLin}+f Ctrl+${modMac}+f Place cursor in input box Ctrl+${modWinLin}+i Ctrl+${modMac}+i Place cursor in output box Ctrl+${modWinLin}+o Ctrl+${modMac}+o Place cursor in first argument field of the next operation in the recipe Ctrl+${modWinLin}+. Ctrl+${modMac}+. Place cursor in first argument field of the nth operation in the recipe Ctrl+${modWinLin}+[1-9] Ctrl+${modMac}+[1-9] Disable current operation Ctrl+${modWinLin}+d Ctrl+${modMac}+d Set/clear breakpoint Ctrl+${modWinLin}+b Ctrl+${modMac}+b Bake Ctrl+${modWinLin}+Space Ctrl+${modMac}+Space Step Ctrl+${modWinLin}+' Ctrl+${modMac}+' Clear recipe Ctrl+${modWinLin}+c Ctrl+${modMac}+c Save to file Ctrl+${modWinLin}+s Ctrl+${modMac}+s Load recipe Ctrl+${modWinLin}+l Ctrl+${modMac}+l Move output to input Ctrl+${modWinLin}+m Ctrl+${modMac}+m Create a new tab Ctrl+${modWinLin}+t Ctrl+${modMac}+t Close the current tab Ctrl+${modWinLin}+w Ctrl+${modMac}+w Go to next tab Ctrl+${modWinLin}+RightArrow Ctrl+${modMac}+RightArrow Go to previous tab Ctrl+${modWinLin}+LeftArrow Ctrl+${modMac}+LeftArrow `; } /** * Shows contextual help message based on where the mouse pointer is */ contextualHelp() { const hoveredHelpEls = document.querySelectorAll(":hover[data-help],:hover[data-help-proxy]"); if (!hoveredHelpEls.length) return; let helpEl = hoveredHelpEls[hoveredHelpEls.length - 1]; const helpElSelector = helpEl.getAttribute("data-help-proxy"); if (helpElSelector) { // A hovered element is directing us to another element for its help text helpEl = document.querySelector(helpElSelector); } this.displayHelp(helpEl); } /** * Displays the help pane populated with help text associated with the given element * * @param {Element} el */ displayHelp(el) { const helpText = el.getAttribute("data-help"); let helpTitle = el.getAttribute("data-help-title"); if (helpTitle) helpTitle = "Help topic: " + helpTitle; else helpTitle = "Help topic"; document.querySelector("#help-modal .modal-body").innerHTML = helpText; document.querySelector("#help-modal #help-title").innerHTML = helpTitle; $("#help-modal").modal(); } } export default BindingsWaiter; ================================================ FILE: src/web/waiters/ControlsWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Utils from "../../core/Utils.mjs"; import { eolSeqToCode } from "../utils/editorUtils.mjs"; /** * Waiter to handle events related to the CyberChef controls (i.e. Bake, Step, Save, Load etc.) */ class ControlsWaiter { /** * ControlsWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; } /** * Initialise Bootstrap components */ initComponents() { $("body").bootstrapMaterialDesign(); $("[data-toggle=tooltip]").tooltip({ animation: false, container: "body", boundary: "viewport", trigger: "hover" }); // Set number of operations in various places in the DOM document.querySelectorAll(".num-ops").forEach(el => { el.innerHTML = Object.keys(this.app.operations).length; }); } /** * Checks or unchecks the Auto Bake checkbox based on the given value. * * @param {boolean} value - The new value for Auto Bake. */ setAutoBake(value) { const autoBakeCheckbox = document.getElementById("auto-bake"); if (autoBakeCheckbox.checked !== value) { autoBakeCheckbox.click(); } } /** * Handler to trigger baking. */ bakeClick() { const btnBake = document.getElementById("bake"); if (btnBake.textContent.indexOf("Bake") > 0) { this.app.manager.input.bakeAll(); } else if (btnBake.textContent.indexOf("Cancel") > 0) { this.manager.worker.cancelBake(false, true); } } /** * Handler for the 'Step through' command. Executes the next step of the recipe. */ stepClick() { this.app.step(); } /** * Handler for changes made to the Auto Bake checkbox. */ autoBakeChange() { this.app.autoBake_ = document.getElementById("auto-bake").checked; } /** * Handler for the 'Clear recipe' command. Removes all operations from the recipe. */ clearRecipeClick() { this.manager.recipe.clearRecipe(); } /** * Populates the save dialog box with a URL incorporating the recipe and input. * * @param {Object[]} [recipeConfig] - The recipe configuration object array. */ initialiseSaveLink(recipeConfig) { recipeConfig = recipeConfig || this.app.getRecipeConfig(); const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked; const includeInput = document.getElementById("save-link-input-checkbox").checked; const saveLinkEl = document.getElementById("save-link"); const saveLink = this.generateStateUrl(includeRecipe, includeInput, null, recipeConfig); saveLinkEl.innerHTML = Utils.escapeHtml(Utils.truncate(saveLink, 120)); saveLinkEl.setAttribute("href", saveLink); } /** * Generates a URL containing the current recipe and input state. * * @param {boolean} includeRecipe - Whether to include the recipe in the URL. * @param {boolean} includeInput - Whether to include the input in the URL. * @param {string} input * @param {Object[]} [recipeConfig] - The recipe configuration object array. * @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included * @returns {string} */ generateStateUrl(includeRecipe, includeInput, input, recipeConfig, baseURL) { recipeConfig = recipeConfig || this.app.getRecipeConfig(); const link = baseURL || window.location.protocol + "//" + window.location.host + window.location.pathname; const recipeStr = Utils.generatePrettyRecipe(recipeConfig); includeRecipe = includeRecipe && (recipeConfig.length > 0); // If we don't get passed an input, get it from the current URI if (input === null && includeInput) { const params = this.app.getURIParams(); if (params.input) { includeInput = true; input = params.input; } else { includeInput = false; } } const inputChrEnc = this.manager.input.getChrEnc(); const outputChrEnc = this.manager.output.getChrEnc(); const inputEOL = eolSeqToCode[this.manager.input.getEOLSeq()]; const outputEOL = eolSeqToCode[this.manager.output.getEOLSeq()]; const params = [ includeRecipe ? ["recipe", recipeStr] : undefined, includeInput && input.length ? ["input", Utils.escapeHtml(input)] : undefined, inputChrEnc !== 0 ? ["ienc", inputChrEnc] : undefined, outputChrEnc !== 0 ? ["oenc", outputChrEnc] : undefined, inputEOL !== "LF" ? ["ieol", inputEOL] : undefined, outputEOL !== "LF" ? ["oeol", outputEOL] : undefined ]; const hash = params .filter(v => v) .map(([key, value]) => `${key}=${Utils.encodeURIFragment(value)}`) .join("&"); if (hash) { return `${link}#${hash}`; } return link; } /** * Handler for changes made to the save dialog text area. Re-initialises the save link. */ saveTextChange(e) { try { const recipeConfig = Utils.parseRecipeConfig(e.target.value); this.initialiseSaveLink(recipeConfig); } catch (err) {} } /** * Handler for the 'Save' command. Pops up the save dialog box. */ saveClick() { const recipeConfig = this.app.getRecipeConfig(); const recipeStr = JSON.stringify(recipeConfig); document.getElementById("save-text-chef").value = Utils.generatePrettyRecipe(recipeConfig, true); document.getElementById("save-text-clean").value = JSON.stringify(recipeConfig, null, 2) .replace(/{\n\s+"/g, "{ \"") .replace(/\[\n\s{3,}/g, "[") .replace(/\n\s{3,}]/g, "]") .replace(/\s*\n\s*}/g, " }") .replace(/\n\s{6,}/g, " "); document.getElementById("save-text-compact").value = recipeStr; this.initialiseSaveLink(recipeConfig); $("#save-modal").modal(); } /** * Handler for the save link recipe checkbox change event. */ slrCheckChange() { this.initialiseSaveLink(); } /** * Handler for the save link input checkbox change event. */ sliCheckChange() { this.initialiseSaveLink(); } /** * Handler for the 'Load' command. Pops up the load dialog box. */ loadClick() { this.populateLoadRecipesList(); $("#load-modal").modal(); } /** * Saves the recipe specified in the save textarea to local storage. */ saveButtonClick() { if (!this.app.isLocalStorageAvailable()) { this.app.alert( "Your security settings do not allow access to local storage so your recipe cannot be saved.", 5000 ); return false; } const recipeName = Utils.escapeHtml(document.getElementById("save-name").value); const recipeStr = document.querySelector("#save-texts .tab-pane.active textarea").value; if (!recipeName) { this.app.alert("Please enter a recipe name", 3000); return; } const savedRecipes = localStorage.savedRecipes ? JSON.parse(localStorage.savedRecipes) : []; let recipeId = localStorage.recipeId || 0; savedRecipes.push({ id: ++recipeId, name: recipeName, recipe: recipeStr }); localStorage.savedRecipes = JSON.stringify(savedRecipes); localStorage.recipeId = recipeId; this.app.alert(`Recipe saved as "${recipeName}".`, 3000); } /** * Populates the list of saved recipes in the load dialog box from local storage. */ populateLoadRecipesList() { if (!this.app.isLocalStorageAvailable()) return false; const loadNameEl = document.getElementById("load-name"); // Remove current recipes from select let i = loadNameEl.options.length; while (i--) { loadNameEl.remove(i); } // Add recipes to select const savedRecipes = localStorage.savedRecipes ? JSON.parse(localStorage.savedRecipes) : []; for (i = 0; i < savedRecipes.length; i++) { const opt = document.createElement("option"); opt.value = savedRecipes[i].id; // Unescape then re-escape in case localStorage has been corrupted opt.innerHTML = Utils.escapeHtml(Utils.unescapeHtml(savedRecipes[i].name)); loadNameEl.appendChild(opt); } // Populate textarea with first recipe const loadText = document.getElementById("load-text"); const evt = new Event("change"); loadText.value = savedRecipes.length ? savedRecipes[0].recipe : ""; loadText.dispatchEvent(evt); } /** * Removes the currently selected recipe from local storage. */ loadDeleteClick() { if (!this.app.isLocalStorageAvailable()) return false; const id = parseInt(document.getElementById("load-name").value, 10); const rawSavedRecipes = localStorage.savedRecipes ? JSON.parse(localStorage.savedRecipes) : []; const savedRecipes = rawSavedRecipes.filter(r => r.id !== id); localStorage.savedRecipes = JSON.stringify(savedRecipes); this.populateLoadRecipesList(); } /** * Displays the selected recipe in the load text box. */ loadNameChange(e) { if (!this.app.isLocalStorageAvailable()) return false; const el = e.target; const savedRecipes = localStorage.savedRecipes ? JSON.parse(localStorage.savedRecipes) : []; const id = parseInt(el.value, 10); const recipe = savedRecipes.find(r => r.id === id); document.getElementById("load-text").value = recipe.recipe; } /** * Loads the selected recipe and populates the Recipe with its operations. */ loadButtonClick() { try { const recipeConfig = Utils.parseRecipeConfig(document.getElementById("load-text").value); this.app.setRecipeConfig(recipeConfig); this.app.autoBake(); $("#rec-list [data-toggle=popover]").popover(); } catch (e) { this.app.alert("Invalid recipe", 2000); } } /** * Hides the arguments for all the operations in the current recipe. */ hideRecipeArgsClick() { const icon = document.getElementById("hide-icon"); if (icon.getAttribute("hide-args") === "false") { icon.setAttribute("hide-args", "true"); icon.setAttribute("data-original-title", "Show arguments"); icon.children[0].innerText = "keyboard_arrow_down"; Array.from(document.getElementsByClassName("hide-args-icon")).forEach(function(item) { item.setAttribute("hide-args", "true"); item.innerText = "keyboard_arrow_down"; item.classList.add("hide-args-selected"); item.parentNode.previousElementSibling.style.display = "none"; }); } else { icon.setAttribute("hide-args", "false"); icon.setAttribute("data-original-title", "Hide arguments"); icon.children[0].innerText = "keyboard_arrow_up"; Array.from(document.getElementsByClassName("hide-args-icon")).forEach(function(item) { item.setAttribute("hide-args", "false"); item.innerText = "keyboard_arrow_up"; item.classList.remove("hide-args-selected"); item.parentNode.previousElementSibling.style.display = "grid"; }); } } /** * Populates the bug report information box with useful technical info. * * @param {event} e */ supportButtonClick(e) { e.preventDefault(); const reportBugInfo = document.getElementById("report-bug-info"); const saveLink = this.generateStateUrl(true, true, null, null, "https://gchq.github.io/CyberChef/"); if (reportBugInfo) { reportBugInfo.innerHTML = `* Version: ${PKG_VERSION} * Compile time: ${COMPILE_TIME} * User-Agent: ${navigator.userAgent} * [Link to reproduce](${saveLink}) `; } } /** * Shows the stale indicator to show that the input or recipe has changed * since the last bake. */ showStaleIndicator() { const staleIndicator = document.getElementById("stale-indicator"); staleIndicator.classList.remove("hidden"); } /** * Hides the stale indicator to show that the input or recipe has not changed * since the last bake. */ hideStaleIndicator() { const staleIndicator = document.getElementById("stale-indicator"); staleIndicator.classList.add("hidden"); } /** * Switches the Bake button between 'Bake', 'Cancel' and 'Loading' functions. * * @param {string} func - The function to change to. Either "cancel", "loading" or "bake" */ toggleBakeButtonFunction(func) { const bakeButton = document.getElementById("bake"), btnText = bakeButton.querySelector("span"); switch (func) { case "cancel": btnText.innerText = "Cancel"; bakeButton.classList.remove("btn-success"); bakeButton.classList.remove("btn-warning"); bakeButton.classList.add("btn-danger"); break; case "loading": bakeButton.style.background = ""; btnText.innerText = "Loading..."; bakeButton.classList.remove("btn-success"); bakeButton.classList.remove("btn-danger"); bakeButton.classList.add("btn-warning"); break; default: bakeButton.style.background = ""; btnText.innerText = "Bake!"; bakeButton.classList.remove("btn-danger"); bakeButton.classList.remove("btn-warning"); bakeButton.classList.add("btn-success"); } } /** * Calculates the height of the controls area and adjusts the recipe * height accordingly. */ calcControlsHeight() { const controls = document.getElementById("controls"), recList = document.getElementById("rec-list"); recList.style.bottom = controls.clientHeight + "px"; } } export default ControlsWaiter; ================================================ FILE: src/web/waiters/HighlighterWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import {EditorSelection} from "@codemirror/state"; import {chrEncWidth} from "../../core/lib/ChrEnc.mjs"; /** * Waiter to handle events related to highlighting in CyberChef. */ class HighlighterWaiter { /** * HighlighterWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; this.currentSelectionRanges = []; } /** * Handler for selection change events in the input and output * * Highlights the given offsets in the input or output. * We will only highlight if: * - input hasn't changed since last bake * - last bake was a full bake * - all operations in the recipe support highlighting * * @param {string} io * @param {ViewUpdate} e */ selectionChange(io, e) { // Confirm we are not currently baking if (!this.app.autoBake_ || this.app.baking) return false; // Confirm this was a user-generated event to prevent looping // from setting the selection in this class if (!e.transactions[0].isUserEvent("select")) return false; this.currentSelectionRanges = []; // Confirm some non-empty ranges are set const selectionRanges = e.state.selection.ranges; // Adjust offsets based on the width of the character set const inputCharacterWidth = chrEncWidth(this.manager.input.getChrEnc()); const outputCharacterWidth = chrEncWidth(this.manager.output.getChrEnc()); let ratio = 1; if (inputCharacterWidth !== outputCharacterWidth && inputCharacterWidth !== 0 && outputCharacterWidth !== 0) { ratio = io === "input" ? inputCharacterWidth / outputCharacterWidth : outputCharacterWidth / inputCharacterWidth; } // Loop through ranges and send request for output offsets for each one const direction = io === "input" ? "forward" : "reverse"; for (const range of selectionRanges) { const pos = [{ start: Math.floor(range.from * ratio), end: Math.floor(range.to * ratio) }]; this.manager.worker.highlight(this.app.getRecipeConfig(), direction, pos); } } /** * Displays highlight offsets sent back from the Chef. * * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. * @param {string} direction */ displayHighlights(pos, direction) { if (!pos) return; if (this.manager.tabs.getActiveTab("input") !== this.manager.tabs.getActiveTab("output")) return; const io = direction === "forward" ? "output" : "input"; this.highlight(io, pos); } /** * Sends selection updates to the relevant EditorView. * * @param {string} io - The input or output * @param {Object[]} ranges - An array of position objects to highlight * @param {number} ranges.start - The start offset * @param {number} ranges.end - The end offset */ async highlight(io, ranges) { if (!this.app.options.showHighlighter) return false; if (!this.app.options.attemptHighlight) return false; if (!ranges || !ranges.length) return false; const view = io === "input" ? this.manager.input.inputEditorView : this.manager.output.outputEditorView; // Add new SelectionRanges to existing ones for (const range of ranges) { if (typeof range.start !== "number" || typeof range.end !== "number") continue; const selection = range.end <= range.start ? EditorSelection.cursor(range.start) : EditorSelection.range(range.start, range.end); this.currentSelectionRanges.push(selection); } // Set selection if (this.currentSelectionRanges.length) { try { view.dispatch({ selection: EditorSelection.create(this.currentSelectionRanges), scrollIntoView: true }); } catch (err) { // Ignore Range Errors if (!err.toString().startsWith("RangeError")) { log.error(err); } } } } } export default HighlighterWaiter; ================================================ FILE: src/web/waiters/InputWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import LoaderWorker from "worker-loader?inline=no-fallback!../workers/LoaderWorker.js"; import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker.mjs"; import Utils, {debounce} from "../../core/Utils.mjs"; import {toBase64} from "../../core/lib/Base64.mjs"; import cptable from "codepage"; import { EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor, dropCursor } from "@codemirror/view"; import { EditorState, Compartment } from "@codemirror/state"; import { defaultKeymap, insertTab, insertNewline, history, historyKeymap } from "@codemirror/commands"; import { bracketMatching } from "@codemirror/language"; import { search, searchKeymap, highlightSelectionMatches } from "@codemirror/search"; import {statusBar} from "../utils/statusBar.mjs"; import {fileDetailsPanel} from "../utils/fileDetails.mjs"; import {eolCodeToSeq, eolCodeToName, renderSpecialChar} from "../utils/editorUtils.mjs"; /** * Waiter to handle events related to the input. */ class InputWaiter { /** * InputWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; this.inputTextEl = document.getElementById("input-text"); this.inputChrEnc = 0; this.eolState = 0; // 0 = unset, 1 = detected, 2 = manual this.encodingState = 0; // 0 = unset, 1 = detected, 2 = manual this.initEditor(); this.inputWorker = null; this.loaderWorkers = []; this.workerId = 0; this.maxTabs = this.manager.tabs.calcMaxTabs(); this.callbacks = {}; this.callbackID = 0; this.fileDetails = {}; this.maxWorkers = 1; if (navigator.hardwareConcurrency !== undefined && navigator.hardwareConcurrency > 1) { // Subtract 1 from hardwareConcurrency value to avoid using // the entire available resources this.maxWorkers = navigator.hardwareConcurrency - 1; } } /** * Sets up the CodeMirror Editor */ initEditor() { // Mutable extensions this.inputEditorConf = { eol: new Compartment, lineWrapping: new Compartment, fileDetailsPanel: new Compartment }; const self = this; const initialState = EditorState.create({ doc: null, extensions: [ // Editor extensions history(), highlightSpecialChars({ render: renderSpecialChar // Custom character renderer to handle special cases }), drawSelection(), rectangularSelection(), crosshairCursor(), dropCursor(), bracketMatching(), highlightSelectionMatches(), search({top: true}), EditorState.allowMultipleSelections.of(true), // Custom extensions statusBar({ label: "Input", eolHandler: this.eolChange.bind(this), chrEncHandler: this.chrEncChange.bind(this), chrEncGetter: this.getChrEnc.bind(this), getEncodingState: this.getEncodingState.bind(this), getEOLState: this.getEOLState.bind(this) }), // Mutable state this.inputEditorConf.fileDetailsPanel.of([]), this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), // Keymap keymap.of([ // Explicitly insert a tab rather than indenting the line { key: "Tab", run: insertTab }, // Explicitly insert a new line (using the current EOL char) rather // than messing around with indenting, which does not respect EOL chars { key: "Enter", run: insertNewline }, ...historyKeymap, ...defaultKeymap, ...searchKeymap ]), // Event listeners EditorView.updateListener.of(e => { if (e.selectionSet) this.manager.highlighter.selectionChange("input", e); if (e.docChanged && !this.silentInputChange) this.inputChange(e); this.silentInputChange = false; }), // Event handlers EditorView.domEventHandlers({ paste(event, view) { const clipboardData = event.clipboardData; const items = clipboardData.items; let files = []; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.kind === "string") { // If there are any string items they should be preferred over // files. files = []; break; } else if (item.kind === "file") { files.push(item.getAsFile()); } } if (files.length > 0) { // Prevent the default paste behavior, afterPaste will load the files instead event.preventDefault(); } setTimeout(() => { self.afterPaste(files); }); } }) ] }); if (this.inputEditorView) this.inputEditorView.destroy(); this.inputEditorView = new EditorView({ state: initialState, parent: this.inputTextEl }); } /** * Handler for EOL change events * Sets the line separator * @param {string} eol * @param {boolean} [manual=false] */ eolChange(eol, manual=false) { const eolVal = eolCodeToSeq[eol]; if (eolVal === undefined) return; this.eolState = manual ? 2 : this.eolState; if (this.eolState < 2 && eolVal === this.getEOLSeq()) return; if (this.eolState === 1) { // Alert this.app.alert(`Input end of line separator has been detected and changed to ${eolCodeToName[eol]}`, 5000); } // Update the EOL value const oldInputVal = this.getInput(); this.inputEditorView.dispatch({ effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal)) }); // Reset the input so that lines are recalculated, preserving the old EOL values this.setInput(oldInputVal); } /** * Getter for the input EOL sequence * @returns {string} */ getEOLSeq() { return this.inputEditorView.state.lineBreak; } /** * Returns whether the input EOL sequence was set manually or has been detected automatically * @returns {number} - 0 = unset, 1 = detected, 2 = manual */ getEOLState() { return this.eolState; } /** * Handler for Chr Enc change events * Sets the input character encoding * @param {number} chrEncVal * @param {boolean} [manual=false] - Flag to indicate the encoding was set by the user * @param {boolean} [internal=false] - Flag to indicate this was set internally, i.e. by loading from URI */ chrEncChange(chrEncVal, manual=false, internal=false) { if (typeof chrEncVal !== "number") return; this.inputChrEnc = chrEncVal; this.encodingState = manual ? 2 : this.encodingState; if (!internal) { this.inputChange(); } } /** * Getter for the input character encoding * @returns {number} */ getChrEnc() { return this.inputChrEnc; } /** * Returns whether the input character encoding was set manually or has been detected automatically * @returns {number} - 0 = unset, 1 = detected, 2 = manual */ getEncodingState() { return this.encodingState; } /** * Sets word wrap on the input editor * @param {boolean} wrap */ setWordWrap(wrap) { this.inputEditorView.dispatch({ effects: this.inputEditorConf.lineWrapping.reconfigure( wrap ? EditorView.lineWrapping : [] ) }); } /** * Gets the value of the current input * @returns {string} */ getInput() { const doc = this.inputEditorView.state.doc; const eol = this.getEOLSeq(); return doc.sliceString(0, doc.length, eol); } /** * Sets the value of the current input * @param {string} data * @param {boolean} [silent=false] */ setInput(data, silent=false) { const lineLengthThreshold = 131072; // 128KB let wrap = this.app.options.wordWrap; if (data.length > lineLengthThreshold) { const lines = data.split(this.getEOLSeq()); const longest = lines.reduce((a, b) => a > b.length ? a : b.length, 0 ); if (longest > lineLengthThreshold) { // If we are exceeding the max line length, turn off word wrap wrap = false; this.app.alert("Maximum line length exceeded. Word wrap will be temporarily disabled to improve performance.", 20000); } } // If turning word wrap off, do it before we populate the editor for performance reasons if (!wrap) this.setWordWrap(wrap); // We use setTimeout here to delay the editor dispatch until the next event cycle, // ensuring all async actions have completed before attempting to set the contents // of the editor. This is mainly with the above call to setWordWrap() in mind. setTimeout(() => { // Insert data into editor, overwriting any previous contents this.silentInputChange = silent; this.inputEditorView.dispatch({ changes: { from: 0, to: this.inputEditorView.state.doc.length, insert: data } }); // If turning word wrap on, do it after we populate the editor if (wrap) setTimeout(() => { this.setWordWrap(wrap); }); }); } /** * Calculates the maximum number of tabs to display */ calcMaxTabs() { const numTabs = this.manager.tabs.calcMaxTabs(); if (this.inputWorker && this.maxTabs !== numTabs) { this.maxTabs = numTabs; this.inputWorker.postMessage({ action: "updateMaxTabs", data: { maxTabs: numTabs, activeTab: this.manager.tabs.getActiveTab("input") } }); } } /** * Terminates any existing workers and sets up a new InputWorker and LoaderWorker */ setupInputWorker() { if (this.inputWorker !== null) { this.inputWorker.terminate(); this.inputWorker = null; } for (let i = this.loaderWorkers.length - 1; i >= 0; i--) { this.removeLoaderWorker(this.loaderWorkers[i]); } log.debug("Adding new InputWorker"); this.inputWorker = new InputWorker(); this.inputWorker.postMessage({ action: "setLogLevel", data: log.getLevel() }); this.inputWorker.postMessage({ action: "updateMaxWorkers", data: this.maxWorkers }); this.inputWorker.postMessage({ action: "updateMaxTabs", data: { maxTabs: this.maxTabs, activeTab: this.manager.tabs.getActiveTab("input") } }); this.inputWorker.addEventListener("message", this.handleInputWorkerMessage.bind(this)); } /** * Activates a loaderWorker and sends it to the InputWorker */ activateLoaderWorker() { const workerIdx = this.addLoaderWorker(); if (workerIdx === -1) return; const workerObj = this.loaderWorkers[workerIdx]; this.inputWorker.postMessage({ action: "loaderWorkerReady", data: { id: workerObj.id } }); } /** * Adds a new loaderWorker * * @returns {number} - The index of the created worker */ addLoaderWorker() { if (this.loaderWorkers.length === this.maxWorkers) { return -1; } log.debug(`Adding new LoaderWorker (${this.loaderWorkers.length + 1}/${this.maxWorkers}).`); const newWorker = new LoaderWorker(); const workerId = this.workerId++; newWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); newWorker.postMessage({ action: "setLogLevel", data: log.getLevel() }); newWorker.postMessage({ action: "setID", data: { id: workerId } }); const newWorkerObj = { worker: newWorker, id: workerId }; this.loaderWorkers.push(newWorkerObj); return this.loaderWorkers.indexOf(newWorkerObj); } /** * Removes a loaderworker * * @param {Object} workerObj - Object containing the loaderWorker and its id * @param {LoaderWorker} workerObj.worker - The actual loaderWorker * @param {number} workerObj.id - The ID of the loaderWorker */ removeLoaderWorker(workerObj) { const idx = this.loaderWorkers.indexOf(workerObj); if (idx === -1) { return; } log.debug(`Terminating worker ${this.loaderWorkers[idx].id}`); this.loaderWorkers[idx].worker.terminate(); this.loaderWorkers.splice(idx, 1); } /** * Finds and returns the object for the loaderWorker of a given id * * @param {number} id - The ID of the loaderWorker to find * @returns {object} */ getLoaderWorker(id) { const idx = this.getLoaderWorkerIndex(id); if (idx === -1) return; return this.loaderWorkers[idx]; } /** * Gets the index for the loaderWorker of a given id * * @param {number} id - The ID of hte loaderWorker to find * @returns {number} The current index of the loaderWorker in the array */ getLoaderWorkerIndex(id) { for (let i = 0; i < this.loaderWorkers.length; i++) { if (this.loaderWorkers[i].id === id) { return i; } } return -1; } /** * Sends an input to be loaded to the loaderWorker * * @param {object} inputData - Object containing the input to be loaded * @param {File} inputData.file - The actual file object to load * @param {number} inputData.inputNum - The inputNum for the file object * @param {number} inputData.workerId - The ID of the loaderWorker that will load it */ loadInput(inputData) { const idx = this.getLoaderWorkerIndex(inputData.workerId); if (idx === -1) return; this.loaderWorkers[idx].worker.postMessage({ action: "loadFile", data: { file: inputData.file, inputNum: inputData.inputNum } }); } /** * Handler for messages sent back by the loaderWorker * Sends the message straight to the inputWorker to be handled there. * * @param {MessageEvent} e */ handleLoaderMessage(e) { const r = e.data; if (Object.prototype.hasOwnProperty.call(r, "progress") && Object.prototype.hasOwnProperty.call(r, "inputNum")) { this.manager.tabs.updateTabProgress(r.inputNum, r.progress, 100, "input"); } const transferable = Object.prototype.hasOwnProperty.call(r, "fileBuffer") ? [r.fileBuffer] : undefined; this.inputWorker.postMessage({ action: "loaderWorkerMessage", data: r }, transferable); } /** * Handler for messages sent back by the InputWorker * * @param {MessageEvent} e */ handleInputWorkerMessage(e) { const r = e.data; if (!("action" in r)) { log.error("A message was received from the InputWorker with no action property. Ignoring message."); return; } log.debug(`Receiving '${r.action}' from InputWorker.`); switch (r.action) { case "activateLoaderWorker": this.activateLoaderWorker(); break; case "loadInput": this.loadInput(r.data); break; case "terminateLoaderWorker": this.removeLoaderWorker(this.getLoaderWorker(r.data)); break; case "refreshTabs": this.refreshTabs(r.data.nums, r.data.activeTab, r.data.tabsLeft, r.data.tabsRight); break; case "changeTab": this.changeTab(r.data, this.app.options.syncTabs); break; case "updateTabHeader": this.manager.tabs.updateTabHeader(r.data.inputNum, r.data.input, "input"); break; case "loadingInfo": this.showLoadingInfo(r.data, true); break; case "setInput": this.set(r.data.inputNum, r.data.inputObj, r.data.silent); break; case "inputAdded": this.inputAdded(r.data.changeTab, r.data.inputNum); break; case "queueInput": this.manager.worker.queueInput(r.data); break; case "queueInputError": this.manager.worker.queueInputError(r.data); break; case "bakeInputs": this.manager.worker.bakeInputs(r.data); break; case "displayTabSearchResults": this.displayTabSearchResults(r.data); break; case "filterTabError": this.app.handleError(r.data); break; case "setUrl": this.app.updateURL(r.data.includeInput, r.data.input); break; case "getInput": case "getInputNums": this.callbacks[r.data.id](r.data); break; case "removeChefWorker": this.removeChefWorker(); break; case "fileLoaded": this.fileLoaded(r.data.inputNum); break; default: log.error(`Unknown action ${r.action}.`); } } /** * Sends a message to the inputWorker to bake all inputs */ bakeAll() { this.app.progress = 0; debounce(this.manager.controls.toggleBakeButtonFunction, 20, "toggleBakeButton", this, ["loading"]); this.inputWorker.postMessage({ action: "bakeAll" }); } /** * Sets the input in the input area * * @param {number} inputNum * @param {Object} inputData - Object containing the input and its metadata * @param {string} type * @param {ArrayBuffer} buffer * @param {string} stringSample * @param {Object} file * @param {string} file.name * @param {number} file.size * @param {string} file.type * @param {string} status * @param {number} progress * @param {number} encoding * @param {string} eolSequence * @param {boolean} [silent=false] - If false, fires the manager statechange event */ async set(inputNum, inputData, silent=false) { return new Promise(function(resolve, reject) { const activeTab = this.manager.tabs.getActiveTab("input"); if (inputNum !== activeTab) { this.changeTab(inputNum, this.app.options.syncTabs); return; } // Update current character encoding this.inputChrEnc = inputData.encoding; // Update current eol sequence this.inputEditorView.dispatch({ effects: this.inputEditorConf.eol.reconfigure( EditorState.lineSeparator.of(inputData.eolSequence) ) }); // Handle file previews if (inputData.file) { this.setFile(inputNum, inputData); } else { this.clearFile(inputNum); } // Decode the data to a string this.manager.timing.recordTime("inputEncodingStart", inputNum); let inputVal; if (this.getChrEnc() > 0) { inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer)); } else { inputVal = Utils.arrayBufferToStr(inputData.buffer); } this.manager.timing.recordTime("inputEncodingEnd", inputNum); // Populate the input editor this.setInput(inputVal, silent); // Set URL to current input if (inputVal.length >= 0 && inputVal.length <= 51200) { const inputStr = toBase64(inputVal, "A-Za-z0-9+/"); this.app.updateURL(true, inputStr); } }.bind(this)); } /** * Displays file details * * @param {number} inputNum * @param {Object} inputData - Object containing the input and its metadata * @param {string} type * @param {ArrayBuffer} buffer * @param {string} stringSample * @param {Object} file * @param {string} file.name * @param {number} file.size * @param {string} file.type * @param {string} status * @param {number} progress */ setFile(inputNum, inputData) { const activeTab = this.manager.tabs.getActiveTab("input"); if (inputNum !== activeTab) return; // Create file details panel this.fileDetails = { fileDetails: inputData.file, progress: inputData.progress, status: inputData.status, buffer: inputData.buffer, renderPreview: this.app.options.imagePreview, toggleHandler: this.toggleFileDetails.bind(this), hidden: false }; this.inputEditorView.dispatch({ effects: this.inputEditorConf.fileDetailsPanel.reconfigure( fileDetailsPanel(this.fileDetails) ) }); } /** * Clears the file details panel * * @param {number} inputNum */ clearFile(inputNum) { const activeTab = this.manager.tabs.getActiveTab("input"); if (inputNum !== activeTab) return; // Clear file details panel this.inputEditorView.dispatch({ effects: this.inputEditorConf.fileDetailsPanel.reconfigure([]) }); } /** * Handler for file details toggle clicks * @param {event} e */ toggleFileDetails(e) { $("[data-toggle='tooltip']").tooltip("hide"); this.fileDetails.hidden = !this.fileDetails.hidden; this.inputEditorView.dispatch({ effects: this.inputEditorConf.fileDetailsPanel.reconfigure( fileDetailsPanel(this.fileDetails) ) }); } /** * Update file details when a file completes loading * * @param {number} inputNum - The inputNum of the input which has finished loading */ fileLoaded(inputNum) { this.manager.tabs.updateTabProgress(inputNum, 100, 100, "input"); const activeTab = this.manager.tabs.getActiveTab("input"); if (activeTab !== inputNum) return; this.inputWorker.postMessage({ action: "setInput", data: { inputNum: inputNum, silent: false } }); this.updateFileProgress(inputNum, 100); } /** * Updates the displayed load progress for a file * * @param {number} inputNum * @param {number | string} progress - Either a number or "error" */ updateFileProgress(inputNum, progress) { const activeTab = this.manager.tabs.getActiveTab("input"); if (inputNum !== activeTab) return; this.fileDetails.progress = progress; if (progress === "error") this.fileDetails.status = "error"; this.inputEditorView.dispatch({ effects: this.inputEditorConf.fileDetailsPanel.reconfigure( fileDetailsPanel(this.fileDetails) ) }); } /** * Updates the stored value for the specified inputNum * * @param {number} inputNum * @param {string | ArrayBuffer} value */ updateInputValue(inputNum, value, force=false) { // Prepare the value as a buffer (full value) and a string sample (up to 4096 bytes) let buffer; let stringSample = ""; // If value is a string, interpret it using the specified character encoding const tabNum = this.manager.tabs.getActiveTab("input"); this.manager.timing.recordTime("inputEncodingStart", tabNum); if (typeof value === "string") { stringSample = value.slice(0, 4096); if (this.getChrEnc() > 0) { buffer = cptable.utils.encode(this.getChrEnc(), value); buffer = new Uint8Array(buffer).buffer; } else { buffer = Utils.strToArrayBuffer(value); } } else { buffer = value; stringSample = Utils.arrayBufferToStr(value.slice(0, 4096)); } this.manager.timing.recordTime("inputEncodingEnd", tabNum); // Update the deep link const recipeStr = buffer.byteLength < 51200 ? toBase64(buffer, "A-Za-z0-9+/") : ""; // B64 alphabet with no padding const includeInput = recipeStr.length > 0 && buffer.byteLength < 51200; this.app.updateURL(includeInput, recipeStr); // Post new value to the InputWorker const transferable = [buffer]; this.inputWorker.postMessage({ action: "updateInputValue", data: { inputNum: inputNum, buffer: buffer, stringSample: stringSample, encoding: this.getChrEnc(), eolSequence: this.getEOLSeq() } }, transferable); } /** * Get the input value for the specified input * * @param {number} inputNum - The inputNum of the input to retrieve from the inputWorker * @returns {ArrayBuffer | string} */ async getInputValue(inputNum) { return await new Promise(resolve => { this.getInputFromWorker(inputNum, false, r => { resolve(r.data); }); }); } /** * Get the input object for the specified input * * @param {number} inputNum - The inputNum of the input to retrieve from the inputWorker * @returns {object} */ async getInputObj(inputNum) { return await new Promise(resolve => { this.getInputFromWorker(inputNum, true, r => { resolve(r.data); }); }); } /** * Gets the specified input from the inputWorker * * @param {number} inputNum - The inputNum of the data to get * @param {boolean} getObj - If true, get the actual data object of the input instead of just the value * @param {Function} callback - The callback to execute when the input is returned * @returns {ArrayBuffer | string | object} */ getInputFromWorker(inputNum, getObj, callback) { const id = this.callbackID++; this.callbacks[id] = callback; this.inputWorker.postMessage({ action: "getInput", data: { inputNum: inputNum, getObj: getObj, id: id } }); } /** * Gets the number of inputs from the inputWorker * * @returns {object} */ async getInputNums() { return await new Promise(resolve => { this.getNums(r => { resolve(r); }); }); } /** * Gets a list of inputNums from the inputWorker, and sends * them back to the specified callback */ getNums(callback) { const id = this.callbackID++; this.callbacks[id] = callback; this.inputWorker.postMessage({ action: "getInputNums", data: id }); } /** * Handler for input change events. * Updates the value stored in the inputWorker * Debounces the input so we don't call autobake too often. * * @param {event} e * * @fires Manager#statechange */ inputChange(e) { // Change debounce delay based on input length const inputLength = this.inputEditorView.state.doc.length; let delay; if (inputLength < 10000) delay = 20; else if (inputLength < 100000) delay = 50; else if (inputLength < 1000000) delay = 200; else delay = 500; debounce(function(e) { const value = this.getInput(); const activeTab = this.manager.tabs.getActiveTab("input"); this.updateInputValue(activeTab, value); this.inputWorker.postMessage({ action: "updateTabHeader", data: activeTab }); // Fire the statechange event as the input has been modified window.dispatchEvent(this.manager.statechange); }, delay, "inputChange", this, [e])(); } /** * Handler that fires just after input paste events. * Checks whether the EOL separator or character encoding should be updated. * * @param {File[]} files - An array of any files that were included in the paste event */ afterPaste(files) { if (files.length > 0) { this.loadUIFiles(files); } // If EOL has been fixed, skip this. if (this.eolState > 1) return; const inputText = this.getInput(); // Detect most likely EOL sequence const eolCharCounts = { "LF": inputText.count("\u000a"), "VT": inputText.count("\u000b"), "FF": inputText.count("\u000c"), "CR": inputText.count("\u000d"), "CRLF": inputText.count("\u000d\u000a"), "NEL": inputText.count("\u0085"), "LS": inputText.count("\u2028"), "PS": inputText.count("\u2029") }; // If all zero, leave alone const total = Object.values(eolCharCounts).reduce((acc, curr) => { return acc + curr; }, 0); if (total === 0) return; // Find most prevalent line ending sequence const highest = Object.entries(eolCharCounts).reduce((acc, curr) => { return curr[1] > acc[1] ? curr : acc; }, ["LF", 0]); let choice = highest[0]; // If CRLF not zero and more than half the highest alternative, choose CRLF if ((eolCharCounts.CRLF * 2) > highest[1]) { choice = "CRLF"; } const eolVal = eolCodeToSeq[choice]; if (eolVal === this.getEOLSeq()) return; // Setting automatically this.eolState = 1; this.eolChange(choice); } /** * Handler for input dragover events. * Gives the user a visual cue to show that items can be dropped here. * * @param {event} e */ inputDragover(e) { // This will be set if we're dragging an operation if (e.dataTransfer.effectAllowed === "move") return false; e.stopPropagation(); e.preventDefault(); e.target.closest("#input-text").classList.add("dropping-file"); } /** * Handler for input dragleave events. * Removes the visual cue. * * @param {event} e */ inputDragleave(e) { e.stopPropagation(); e.preventDefault(); // Dragleave often fires when moving between lines in the editor. // If the from element is within the input-text element, we are still on target. if (!this.inputTextEl.contains(e.fromElement)) { e.target.closest("#input-text").classList.remove("dropping-file"); } } /** * Handler for input drop events. * Loads the dragged data. * * @param {event} e */ async inputDrop(e) { // This will be set if we're dragging an operation if (e.dataTransfer.effectAllowed === "move") return false; e.stopPropagation(); e.preventDefault(); e.target.closest("#input-text").classList.remove("dropping-file"); // Dropped text is handled by the editor itself if (e.dataTransfer.getData("Text")) return; // Dropped files if (e?.dataTransfer?.files?.length > 0) { let files = []; // Handling the files as FileSystemEntry objects allows us to open directories, // but relies on a function that may be deprecated in future. if (Object.prototype.hasOwnProperty.call(DataTransferItem.prototype, "webkitGetAsEntry")) { const fileEntries = await this.getAllFileEntries(e.dataTransfer.items); // Read all FileEntry objects into File objects files = await Promise.all(fileEntries.map(async fe => await this.getFile(fe))); } else { files = e.dataTransfer.files; } this.loadUIFiles(files); } } /** * * @param {DataTransferItemList} dataTransferItemList * @returns {FileSystemEntry[]} */ async getAllFileEntries(dataTransferItemList) { const fileEntries = []; // Use BFS to traverse entire directory/file structure const queue = []; // Unfortunately dataTransferItemList is not iterable i.e. no forEach for (let i = 0; i < dataTransferItemList.length; i++) { // Note webkitGetAsEntry a non-standard feature and may change // Usage is necessary for handling directories queue.push(dataTransferItemList[i].webkitGetAsEntry()); } while (queue.length > 0) { const entry = queue.shift(); if (entry.isFile) { fileEntries.push(entry); } else if (entry.isDirectory) { queue.push(...await this.readAllDirectoryEntries(entry.createReader())); } } return fileEntries; } /** * Get all the entries (files or sub-directories) in a directory by calling * readEntries until it returns empty array * * @param {FileSystemDirectoryReader} directoryReader * @returns {FileSystemEntry[]} */ async readAllDirectoryEntries(directoryReader) { const entries = []; let readEntries = await this.readEntriesPromise(directoryReader); while (readEntries.length > 0) { entries.push(...readEntries); readEntries = await this.readEntriesPromise(directoryReader); } return entries; } /** * Wrap readEntries in a promise to make working with readEntries easier. * readEntries will return only some of the entries in a directory * e.g. Chrome returns at most 100 entries at a time * * @param {FileSystemDirectoryReader} directoryReader * @returns {Promise} */ async readEntriesPromise(directoryReader) { try { return await new Promise((resolve, reject) => { directoryReader.readEntries(resolve, reject); }); } catch (err) { log.error(err); } } /** * Reads a FileEntry and returns it as a File object * @param {FileEntry} fileEntry * @returns {File} */ async getFile(fileEntry) { try { return new Promise((resolve, reject) => fileEntry.file(resolve, reject)); } catch (err) { log.error(err); } } /** * Handler for open input button events * Loads the opened data into the input textarea * * @param {event} e */ inputOpen(e) { e.preventDefault(); if (e.target.files.length > 0) { this.loadUIFiles(e.target.files); e.target.value = ""; } } /** * Handler for open input button click. * Opens the open file dialog. */ inputOpenClick() { document.getElementById("open-file").click(); } /** * Handler for open folder button click * Opens the open folder dialog. */ folderOpenClick() { document.getElementById("open-folder").click(); } /** * Load files from the UI into the inputWorker * * @param {FileList} files - The list of files to be loaded */ loadUIFiles(files) { const numFiles = files.length; const activeTab = this.manager.tabs.getActiveTab("input"); log.debug(`Loading ${numFiles} files.`); // Display the number of files as pending so the user // knows that we've received the files. this.showLoadingInfo({ pending: numFiles, loading: 0, loaded: 0, total: numFiles, activeProgress: { inputNum: activeTab, progress: 0 } }, false); this.inputWorker.postMessage({ action: "loadUIFiles", data: { files: files, activeTab: activeTab } }); } /** * Display the loaded files information in the input header. * Also, sets the background of the Input header to be a progress bar * @param {object} loadedData - Object containing the loading information * @param {number} loadedData.pending - How many files are pending (not loading / loaded) * @param {number} loadedData.loading - How many files are being loaded * @param {number} loadedData.loaded - How many files have been loaded * @param {number} loadedData.total - The total number of files * @param {object} loadedData.activeProgress - Object containing data about the active tab * @param {number} loadedData.activeProgress.inputNum - The inputNum of the input the progress is for * @param {number} loadedData.activeProgress.progress - The loading progress of the active input * @param {boolean} autoRefresh - If true, automatically refreshes the loading info by sending a message to the inputWorker after 100ms */ showLoadingInfo(loadedData, autoRefresh) { const pending = loadedData.pending; const loading = loadedData.loading; const loaded = loadedData.loaded; const total = loadedData.total; let width = total.toLocaleString().length; width = width < 2 ? 2 : width; const totalStr = total.toLocaleString().padStart(width, " ").replace(/ /g, " "); let msg = "total: " + totalStr; const loadedStr = loaded.toLocaleString().padStart(width, " ").replace(/ /g, " "); msg += "
        loaded: " + loadedStr; if (pending > 0) { const pendingStr = pending.toLocaleString().padStart(width, " ").replace(/ /g, " "); msg += "
        pending: " + pendingStr; } else if (loading > 0) { const loadingStr = loading.toLocaleString().padStart(width, " ").replace(/ /g, " "); msg += "
        loading: " + loadingStr; } const inFiles = document.getElementById("input-files-info"); if (total > 1) { inFiles.innerHTML = msg; inFiles.style.display = ""; } else { inFiles.style.display = "none"; } this.updateFileProgress(loadedData.activeProgress.inputNum, loadedData.activeProgress.progress); const inputTitle = document.getElementById("input").firstElementChild; if (loaded < total) { const percentComplete = loaded / total * 100; inputTitle.style.background = `linear-gradient(to right, var(--title-background-colour) ${percentComplete}%, var(--primary-background-colour) ${percentComplete}%)`; } else { inputTitle.style.background = ""; } if (loaded < total && autoRefresh) { setTimeout(function() { this.inputWorker.postMessage({ action: "getLoadProgress", data: this.manager.tabs.getActiveTab("input") }); }.bind(this), 100); } } /** * Change to a different tab. * * @param {number} inputNum - The inputNum of the tab to change to * @param {boolean} [changeOutput=false] - If true, also changes the output */ changeTab(inputNum, changeOutput=false) { if (this.manager.tabs.getTabItem(inputNum, "input") !== null) { this.manager.tabs.changeTab(inputNum, "input"); this.inputWorker.postMessage({ action: "setInput", data: { inputNum: inputNum, silent: true } }); } else { const minNum = Math.min(...this.manager.tabs.getTabList("input")); let direction = "right"; if (inputNum < minNum) { direction = "left"; } this.inputWorker.postMessage({ action: "refreshTabs", data: { inputNum: inputNum, direction: direction } }); } if (changeOutput) { this.manager.output.changeTab(inputNum, false); } // Set cursor focus to current tab this.inputEditorView.focus(); } /** * Handler for clicking on a tab * * @param {event} mouseEvent */ changeTabClick(mouseEvent) { if (!mouseEvent.target) return; const tabNum = mouseEvent.target.parentElement.getAttribute("inputNum"); if (tabNum >= 0) { this.changeTab(parseInt(tabNum, 10), this.app.options.syncTabs); } } /** * Handler for clear all IO events. * Resets the input, output and info areas, and creates a new inputWorker */ clearAllIoClick() { this.manager.worker.cancelBake(true, true); this.manager.worker.loaded = false; this.manager.output.removeAllOutputs(); this.manager.output.terminateZipWorker(); this.eolState = 0; this.encodingState = 0; this.manager.output.eolState = 0; this.manager.output.encodingState = 0; this.initEditor(); this.manager.output.initEditor(); const tabsList = document.getElementById("input-tabs"); const tabsListChildren = tabsList.children; tabsList.classList.remove("tabs-left"); tabsList.classList.remove("tabs-right"); for (let i = tabsListChildren.length - 1; i >= 0; i--) { tabsListChildren.item(i).remove(); } this.showLoadingInfo({ pending: 0, loading: 0, loaded: 1, total: 1, activeProgress: { inputNum: 1, progress: 100 } }); this.setupInputWorker(); this.manager.worker.setupChefWorker(); this.addInput(true); } /** * Sets the console log level in the workers. */ setLogLevel() { this.loaderWorkers.forEach(w => { w.postMessage({ action: "setLogLevel", data: log.getLevel() }); }); if (!this.inputWorker) return; this.inputWorker.postMessage({ action: "setLogLevel", data: log.getLevel() }); } /** * Sends a message to the inputWorker to add a new input. * @param {boolean} [changeTab=false] - If true, changes the tab to the new input */ addInput(changeTab=false) { if (!this.inputWorker) return; this.inputWorker.postMessage({ action: "addInput", data: changeTab }); } /** * Handler for add input button clicked. */ addInputClick() { this.addInput(true); } /** * Handler for when the inputWorker adds a new input * * @param {boolean} changeTab - Whether or not to change to the new input tab * @param {number} inputNum - The new inputNum */ inputAdded(changeTab, inputNum) { this.addTab(inputNum, changeTab); this.manager.output.addOutput(inputNum, changeTab); this.manager.worker.addChefWorker(); } /** * Remove a chefWorker from the workerWaiter if we remove an input */ removeChefWorker() { const workerIdx = this.manager.worker.getInactiveChefWorker(true); const worker = this.manager.worker.chefWorkers[workerIdx]; this.manager.worker.removeChefWorker(worker); } /** * Adds a new input tab. * * @param {number} inputNum - The inputNum of the new tab * @param {boolean} [changeTab=true] - If true, changes to the new tab once it's been added */ addTab(inputNum, changeTab=true) { const tabsWrapper = document.getElementById("input-tabs"), numTabs = tabsWrapper.children.length; if (!this.manager.tabs.getTabItem(inputNum, "input") && numTabs < this.maxTabs) { const newTab = this.manager.tabs.createTabElement(inputNum, changeTab, "input"); tabsWrapper.appendChild(newTab); if (numTabs > 0) { this.manager.tabs.showTabBar(); } else { this.manager.tabs.hideTabBar(); } this.inputWorker.postMessage({ action: "updateTabHeader", data: inputNum }); } else if (numTabs === this.maxTabs) { // Can't create a new tab document.getElementById("input-tabs").lastElementChild.classList.add("tabs-right"); } if (changeTab) this.changeTab(inputNum, false); } /** * Refreshes the input tabs, and changes to activeTab * * @param {number[]} nums - The inputNums to be displayed as tabs * @param {number} activeTab - The tab to change to * @param {boolean} tabsLeft - True if there are input tabs to the left of the displayed tabs * @param {boolean} tabsRight - True if there are input tabs to the right of the displayed tabs */ refreshTabs(nums, activeTab, tabsLeft, tabsRight) { this.manager.tabs.refreshTabs(nums, activeTab, tabsLeft, tabsRight, "input"); this.inputWorker.postMessage({ action: "setInput", data: { inputNum: activeTab, silent: true } }); } /** * Sends a message to the inputWorker to remove an input. * If the input tab is on the screen, refreshes the tabs * * @param {number} inputNum - The inputNum of the tab to be removed */ removeInput(inputNum) { let refresh = false; if (this.manager.tabs.getTabItem(inputNum, "input") !== null) { refresh = true; } this.inputWorker.postMessage({ action: "removeInput", data: { inputNum: inputNum, refreshTabs: refresh, removeChefWorker: true } }); this.manager.output.removeTab(inputNum); } /** * Handler for clicking on a remove tab button * * @param {event} mouseEvent */ removeTabClick(mouseEvent) { if (!mouseEvent.target) { return; } const tabNum = mouseEvent.target.closest("button").parentElement.getAttribute("inputNum"); if (tabNum) { this.removeInput(parseInt(tabNum, 10)); } } /** * Handler for scrolling on the input tabs area * * @param {event} wheelEvent */ scrollTab(wheelEvent) { wheelEvent.preventDefault(); if (wheelEvent.deltaY > 0) { this.changeTabLeft(); } else if (wheelEvent.deltaY < 0) { this.changeTabRight(); } } /** * Handler for mouse down on the next tab button */ nextTabClick() { this.mousedown = true; this.changeTabRight(); const time = 200; const func = function(time) { if (this.mousedown) { this.changeTabRight(); const newTime = (time > 50) ? time - 10 : 50; setTimeout(func.bind(this, [newTime]), newTime); } }; this.tabTimeout = setTimeout(func.bind(this, [time]), time); } /** * Handler for mouse down on the previous tab button */ previousTabClick() { this.mousedown = true; this.changeTabLeft(); const time = 200; const func = function(time) { if (this.mousedown) { this.changeTabLeft(); const newTime = (time > 50) ? time - 10 : 50; setTimeout(func.bind(this, [newTime]), newTime); } }; this.tabTimeout = setTimeout(func.bind(this, [time]), time); } /** * Handler for mouse up event on the tab buttons */ tabMouseUp() { this.mousedown = false; clearTimeout(this.tabTimeout); this.tabTimeout = null; } /** * Changes to the next (right) tab */ changeTabRight() { const activeTab = this.manager.tabs.getActiveTab("input"); if (activeTab === -1) return; this.inputWorker.postMessage({ action: "changeTabRight", data: { activeTab: activeTab } }); } /** * Changes to the previous (left) tab */ changeTabLeft() { const activeTab = this.manager.tabs.getActiveTab("input"); if (activeTab === -1) return; this.inputWorker.postMessage({ action: "changeTabLeft", data: { activeTab: activeTab } }); } /** * Handler for go to tab button clicked */ async goToTab() { const inputNums = await this.getInputNums(); let tabNum = window.prompt(`Enter tab number (${inputNums.min} - ${inputNums.max}):`, this.manager.tabs.getActiveTab("input").toString()); if (tabNum === null) return; tabNum = parseInt(tabNum, 10); this.changeTab(tabNum, this.app.options.syncTabs); } /** * Handler for find tab button clicked */ findTab() { this.filterTabSearch(); $("#input-tab-modal").modal(); } /** * Sends a message to the inputWorker to search the inputs */ filterTabSearch() { const showPending = document.getElementById("input-show-pending").checked; const showLoading = document.getElementById("input-show-loading").checked; const showLoaded = document.getElementById("input-show-loaded").checked; const filter = document.getElementById("input-filter").value; const filterType = document.getElementById("input-filter-button").innerText; const numResults = parseInt(document.getElementById("input-num-results").value, 10); this.inputWorker.postMessage({ action: "filterTabs", data: { showPending: showPending, showLoading: showLoading, showLoaded: showLoaded, filter: filter, filterType: filterType, numResults: numResults } }); } /** * Handle when an option in the filter drop down box is clicked * * @param {event} mouseEvent */ filterOptionClick(mouseEvent) { document.getElementById("input-filter-button").innerText = mouseEvent.target.innerText; this.filterTabSearch(); } /** * Displays the results of a tab search in the find tab box * * @param {object[]} results - List of results objects * */ displayTabSearchResults(results) { const resultsList = document.getElementById("input-search-results"); for (let i = resultsList.children.length - 1; i >= 0; i--) { resultsList.children.item(i).remove(); } for (let i = 0; i < results.length; i++) { const newListItem = document.createElement("li"); newListItem.classList.add("input-filter-result"); newListItem.setAttribute("inputNum", results[i].inputNum); newListItem.innerText = `${results[i].inputNum}: ${results[i].textDisplay}`; resultsList.appendChild(newListItem); } } /** * Handler for clicking on a filter result * * @param {event} e */ filterItemClick(e) { if (!e.target) return; const inputNum = parseInt(e.target.getAttribute("inputNum"), 10); if (inputNum <= 0) return; $("#input-tab-modal").modal("hide"); this.changeTab(inputNum, this.app.options.syncTabs); } /** * Handler for incoming postMessages * If the events data has a `type` property set to `dataSubmit` * the value property is set to the current input * @param {event} e * @param {object} e.data * @param {string} e.data.type - the type of request, currently the only value is "dataSubmit" * @param {string} e.data.value - the value of the message */ handlePostMessage(e) { log.debug(e); if ("data" in e && "id" in e.data && "value" in e.data) { if (e.data.id === "setInput") { this.setInput(e.data.value); } } } } export default InputWaiter; ================================================ FILE: src/web/waiters/OperationsWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import HTMLOperation from "../HTMLOperation.mjs"; import Sortable from "sortablejs"; import {fuzzyMatch, calcMatchRanges} from "../../core/lib/FuzzyMatch.mjs"; /** * Waiter to handle events related to the operations. */ class OperationsWaiter { /** * OperationsWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; this.options = {}; this.removeIntent = false; } /** * Handler for search events. * Finds operations which match the given search term and displays them under the search box. * * @param {event} e */ searchOperations(e) { let ops, selected; if (e.type === "search" || e.keyCode === 13) { // Search or Return e.preventDefault(); ops = document.querySelectorAll("#search-results li"); if (ops.length) { selected = this.getSelectedOp(ops); if (selected > -1) { this.manager.recipe.addOperation(ops[selected].innerHTML); } } } if (e.keyCode === 40) { // Down e.preventDefault(); ops = document.querySelectorAll("#search-results li"); if (ops.length) { selected = this.getSelectedOp(ops); if (selected > -1) { ops[selected].classList.remove("selected-op"); } if (selected === ops.length-1) selected = -1; ops[selected+1].classList.add("selected-op"); } } else if (e.keyCode === 38) { // Up e.preventDefault(); ops = document.querySelectorAll("#search-results li"); if (ops.length) { selected = this.getSelectedOp(ops); if (selected > -1) { ops[selected].classList.remove("selected-op"); } if (selected === 0) selected = ops.length; ops[selected-1].classList.add("selected-op"); } } else { const searchResultsEl = document.getElementById("search-results"); const el = e.target; const str = el.value; while (searchResultsEl.firstChild) { try { $(searchResultsEl.firstChild).popover("dispose"); } catch (err) {} searchResultsEl.removeChild(searchResultsEl.firstChild); } $("#categories .show").collapse("hide"); if (str) { const matchedOps = this.filterOperations(str, true); const matchedOpsHtml = matchedOps .map(v => v.toStubHtml()) .join(""); searchResultsEl.innerHTML = matchedOpsHtml; searchResultsEl.dispatchEvent(this.manager.oplistcreate); } } } /** * Filters operations based on the search string and returns the matching ones. * * @param {string} searchStr * @param {boolean} highlight - Whether or not to highlight the matching string in the operation * name and description * @returns {string[]} */ filterOperations(inStr, highlight) { const matchedOps = []; const matchedDescs = []; // Create version with no whitespace for the fuzzy match // Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP" const inStrNWS = inStr.replace(/\s/g, ""); for (const opName in this.app.operations) { const op = this.app.operations[opName]; // Match op name using fuzzy match const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName); // Match description based on exact match const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase()); if (nameMatch || descPos >= 0) { const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); if (highlight) { operation.highlightSearchStrings(calcMatchRanges(idxs), [[descPos, inStr.length]]); } if (nameMatch) { matchedOps.push([operation, score]); } else { matchedDescs.push(operation); } } } // Sort matched operations based on fuzzy score matchedOps.sort((a, b) => b[1] - a[1]); return matchedOps.map(a => a[0]).concat(matchedDescs); } /** * Finds the operation which has been selected using keyboard shortcuts. This will have the class * 'selected-op' set. Returns the index of the operation within the given list. * * @param {element[]} ops * @returns {number} */ getSelectedOp(ops) { for (let i = 0; i < ops.length; i++) { if (ops[i].classList.contains("selected-op")) { return i; } } return -1; } /** * Handler for oplistcreate events. * * @listens Manager#oplistcreate * @param {event} e */ opListCreate(e) { this.manager.recipe.createSortableSeedList(e.target); // Populate ops total document.querySelector("#operations .title .op-count").innerText = Object.keys(this.app.operations).length; this.enableOpsListPopovers(e.target); } /** * Sets up popovers, allowing the popover itself to gain focus which enables scrolling * and other interactions. * * @param {Element} el - The element to start selecting from */ enableOpsListPopovers(el) { $(el).find("[data-toggle=popover]").addBack("[data-toggle=popover]") .popover({trigger: "manual"}) .on("mouseenter", function(e) { if (e.buttons > 0) return; // Mouse button held down - likely dragging an operation const _this = this; $(this).popover("show"); $(".popover").on("mouseleave", function () { $(_this).popover("hide"); }); }).on("mouseleave", function () { const _this = this; setTimeout(function() { // Determine if the popover associated with this element is being hovered over if ($(_this).data("bs.popover") && ($(_this).data("bs.popover").tip && !$($(_this).data("bs.popover").tip).is(":hover"))) { $(_this).popover("hide"); } }, 50); }); } /** * Handler for operation doubleclick events. * Adds the operation to the recipe and auto bakes. * * @param {event} e */ operationDblclick(e) { const li = e.target; this.manager.recipe.addOperation(li.textContent); } /** * Handler for edit favourites click events. * Sets up the 'Edit favourites' pane and displays it. * * @param {event} e */ editFavouritesClick(e) { e.preventDefault(); e.stopPropagation(); // Add favourites to modal const favCat = this.app.categories.filter(function(c) { return c.name === "Favourites"; })[0]; let html = ""; for (let i = 0; i < favCat.ops.length; i++) { const opName = favCat.ops[i]; const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); html += operation.toStubHtml(true); } const editFavouritesList = document.getElementById("edit-favourites-list"); editFavouritesList.innerHTML = html; this.removeIntent = false; const editableList = Sortable.create(editFavouritesList, { filter: ".remove-icon", onFilter: function (evt) { const el = editableList.closest(evt.item); if (el && el.parentNode) { $(el).popover("dispose"); el.parentNode.removeChild(el); } }, onEnd: function(evt) { if (this.removeIntent) { $(evt.item).popover("dispose"); evt.item.remove(); } }.bind(this), }); Sortable.utils.on(editFavouritesList, "dragleave", function() { this.removeIntent = true; }.bind(this)); Sortable.utils.on(editFavouritesList, "dragover", function() { this.removeIntent = false; }.bind(this)); $("#edit-favourites-list [data-toggle=popover]").popover(); $("#favourites-modal").modal(); } /** * Handler for save favourites click events. * Saves the selected favourites and reloads them. */ saveFavouritesClick() { const favs = document.querySelectorAll("#edit-favourites-list li"); const favouritesList = Array.from(favs, e => e.childNodes[0].textContent); this.app.saveFavourites(favouritesList); this.app.loadFavourites(); this.app.populateOperationsList(); this.manager.recipe.initialiseOperationDragNDrop(); } /** * Handler for reset favourites click events. * Resets favourites to their defaults. */ resetFavouritesClick() { this.app.resetFavourites(); } /** * Sets whether operation counts are displayed next to a category title */ setCatCount() { if (this.app.options.showCatCount) { document.querySelectorAll(".category-title .op-count").forEach(el => el.classList.remove("hidden")); } else { document.querySelectorAll(".category-title .op-count").forEach(el => el.classList.add("hidden")); } } } export default OperationsWaiter; ================================================ FILE: src/web/waiters/OptionsWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ /** * Waiter to handle events related to the CyberChef options. */ class OptionsWaiter { /** * OptionsWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; } /** * Loads options and sets values of switches and inputs to match them. * * @param {Object} options */ load(options) { Object.assign(this.app.options, options); // Set options to match object document.querySelectorAll("#options-body input[type=checkbox]").forEach(cbox => { cbox.checked = this.app.options[cbox.getAttribute("option")]; }); document.querySelectorAll("#options-body input[type=number]").forEach(nbox => { nbox.value = this.app.options[nbox.getAttribute("option")]; nbox.dispatchEvent(new CustomEvent("change", {bubbles: true})); }); document.querySelectorAll("#options-body select").forEach(select => { const val = this.app.options[select.getAttribute("option")]; if (val) { select.value = val; select.dispatchEvent(new CustomEvent("change", {bubbles: true})); } else { select.selectedIndex = 0; } }); // Initialise options this.setWordWrap(); this.manager.ops.setCatCount(); } /** * Handler for options click events. * Displays the options pane. * * @param {event} e */ optionsClick(e) { e.preventDefault(); $("#options-modal").modal(); } /** * Handler for reset options click events. * Resets options back to their default values. */ resetOptionsClick() { this.load(this.app.doptions); } /** * Handler for switch change events. * * @param {event} e */ switchChange(e) { const el = e.target; const option = el.getAttribute("option"); const state = el.checked; this.updateOption(option, state); } /** * Handler for number change events. * * @param {event} e */ numberChange(e) { const el = e.target; const option = el.getAttribute("option"); const val = parseInt(el.value, 10); this.updateOption(option, val); } /** * Handler for select change events. * * @param {event} e */ selectChange(e) { const el = e.target; const option = el.getAttribute("option"); this.updateOption(option, el.value); } /** * Modifies an option value and saves it to local storage. * * @param {string} option - The option to be updated * @param {string|number|boolean} value - The new value of the option */ updateOption(option, value) { log.debug(`Setting ${option} to ${value}`); this.app.options[option] = value; if (this.app.isLocalStorageAvailable()) localStorage.setItem("options", JSON.stringify(this.app.options)); } /** * Sets or unsets word wrap on the input and output depending on the wordWrap option value. */ setWordWrap() { this.manager.input.setWordWrap(this.app.options.wordWrap); this.manager.output.setWordWrap(this.app.options.wordWrap); } /** * Theme change event listener * * @param {Event} e */ themeChange(e) { const themeClass = e.target.value; this.changeTheme(themeClass); } /** * Changes the theme by setting the class of the element. * * @param (string} theme */ changeTheme(theme) { document.querySelector(":root").className = theme; // Update theme selection const themeSelect = document.getElementById("theme"); let themeOption = themeSelect.querySelector(`option[value="${theme}"]`); if (!themeOption) { const preferredColorScheme = this.getPreferredColorScheme(); document.querySelector(":root").className = preferredColorScheme; themeOption = themeSelect.querySelector(`option[value="${preferredColorScheme}"]`); } themeSelect.selectedIndex = themeOption.index; } /** * Applies the user's preferred color scheme using the `prefers-color-scheme` media query. */ applyPreferredColorScheme() { const themeFromStorage = this.app?.options?.theme; let theme = themeFromStorage; if (!theme) { theme = this.getPreferredColorScheme(); } this.changeTheme(theme); } /** * Get the user's preferred color scheme using the `prefers-color-scheme` media query. */ getPreferredColorScheme() { const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches; return prefersDarkScheme ? "dark" : "classic"; } /** * Changes the console logging level. * * @param {Event} e */ logLevelChange(e) { const level = e.target.value; log.setLevel(level, false); this.manager.worker.setLogLevel(); this.manager.input.setLogLevel(); this.manager.output.setLogLevel(); this.manager.background.setLogLevel(); } } export default OptionsWaiter; ================================================ FILE: src/web/waiters/OutputWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Utils, {debounce} from "../../core/Utils.mjs"; import Dish from "../../core/Dish.mjs"; import {isUTF8, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from "../../core/lib/ChrEnc.mjs"; import {detectFileType} from "../../core/lib/FileType.mjs"; import FileSaver from "file-saver"; import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs"; import { EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor } from "@codemirror/view"; import { EditorState, Compartment } from "@codemirror/state"; import { defaultKeymap } from "@codemirror/commands"; import { bracketMatching } from "@codemirror/language"; import { search, searchKeymap, highlightSelectionMatches } from "@codemirror/search"; import {statusBar} from "../utils/statusBar.mjs"; import {htmlPlugin} from "../utils/htmlWidget.mjs"; import {copyOverride} from "../utils/copyOverride.mjs"; import {eolCodeToSeq, eolCodeToName, renderSpecialChar} from "../utils/editorUtils.mjs"; /** * Waiter to handle events related to the output */ class OutputWaiter { /** * OutputWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager */ constructor(app, manager) { this.app = app; this.manager = manager; this.outputTextEl = document.getElementById("output-text"); // Object to handle output HTML state - used by htmlWidget extension this.htmlOutput = { html: "", changed: false }; // Hold a copy of the currently displayed output so that we don't have to update it unnecessarily this.currentOutputCache = null; this.initEditor(); this.outputs = {}; this.zipWorker = null; this.maxTabs = this.manager.tabs.calcMaxTabs(); this.tabTimeout = null; this.eolState = 0; // 0 = unset, 1 = detected, 2 = manual this.encodingState = 0; // 0 = unset, 1 = detected, 2 = manual } /** * Sets up the CodeMirror Editor */ initEditor() { // Mutable extensions this.outputEditorConf = { eol: new Compartment, lineWrapping: new Compartment, drawSelection: new Compartment }; const initialState = EditorState.create({ doc: null, extensions: [ // Editor extensions EditorState.readOnly.of(true), highlightSpecialChars({ render: renderSpecialChar, // Custom character renderer to handle special cases addSpecialChars: /[\ue000-\uf8ff]/g // Add the Unicode Private Use Area which we use for some whitespace chars }), rectangularSelection(), crosshairCursor(), bracketMatching(), highlightSelectionMatches(), search({top: true}), EditorState.allowMultipleSelections.of(true), // Custom extensions statusBar({ label: "Output", timing: this.manager.timing, tabNumGetter: function() { return this.manager.tabs.getActiveTab("output"); }.bind(this), eolHandler: this.eolChange.bind(this), chrEncHandler: this.chrEncChange.bind(this), chrEncGetter: this.getChrEnc.bind(this), getEncodingState: this.getEncodingState.bind(this), getEOLState: this.getEOLState.bind(this), htmlOutput: this.htmlOutput }), htmlPlugin(this.htmlOutput), copyOverride(), // Mutable state this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), this.outputEditorConf.drawSelection.of(drawSelection()), // Keymap keymap.of([ ...defaultKeymap, ...searchKeymap ]), // Event listeners EditorView.updateListener.of(e => { if (e.selectionSet) this.manager.highlighter.selectionChange("output", e); if (e.docChanged || this.docChanging) { this.docChanging = false; this.toggleLoader(false); } }) ] }); if (this.outputEditorView) this.outputEditorView.destroy(); this.outputEditorView = new EditorView({ state: initialState, parent: this.outputTextEl }); } /** * Handler for EOL change events * Sets the line separator * @param {string} eol * @param {boolean} [manual=false] */ async eolChange(eol, manual=false) { const eolVal = eolCodeToSeq[eol]; if (eolVal === undefined) return; this.eolState = manual ? 2 : this.eolState; if (this.eolState < 2 && eolVal === this.getEOLSeq()) return; if (this.eolState === 1) { // Alert this.app.alert(`Output end of line separator has been detected and changed to ${eolCodeToName[eol]}`, 5000); } const currentTabNum = this.manager.tabs.getActiveTab("output"); if (currentTabNum >= 0) { this.outputs[currentTabNum].eolSequence = eolVal; } else { throw new Error(`Cannot change output ${currentTabNum} EOL sequence to ${eolVal}`); } // Update the EOL value this.outputEditorView.dispatch({ effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal)) }); // Reset the output so that lines are recalculated, preserving the old EOL values await this.setOutput(this.currentOutputCache, true); // Update the URL manually since we aren't firing a statechange event this.app.updateURL(true); } /** * Getter for the output EOL sequence * Prefer reading value from `this.outputs` since the editor may not have updated yet. * @returns {string} */ getEOLSeq() { const currentTabNum = this.manager.tabs.getActiveTab("output"); if (currentTabNum < 0) { return this.outputEditorConf.state?.lineBreak || "\n"; } return this.outputs[currentTabNum].eolSequence; } /** * Returns whether the output EOL sequence was set manually or has been detected automatically * @returns {number} - 0 = unset, 1 = detected, 2 = manual */ getEOLState() { return this.eolState; } /** * Handler for Chr Enc change events * Sets the output character encoding * @param {number} chrEncVal * @param {boolean} [manual=false] */ async chrEncChange(chrEncVal, manual=false) { if (typeof chrEncVal !== "number") return; const currentEnc = this.getChrEnc(); const currentTabNum = this.manager.tabs.getActiveTab("output"); if (currentTabNum >= 0) { this.outputs[currentTabNum].encoding = chrEncVal; } else { throw new Error(`Cannot change output ${currentTabNum} chrEnc to ${chrEncVal}`); } this.encodingState = manual ? 2 : this.encodingState; if (this.encodingState > 1) { // Reset the output, forcing it to re-decode the data with the new character encoding await this.setOutput(this.currentOutputCache, true); // Update the URL manually since we aren't firing a statechange event this.app.updateURL(true); } else if (currentEnc !== chrEncVal) { // Alert this.app.alert(`Output character encoding has been detected and changed to ${CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] || "Raw Bytes"}`, 5000); } } /** * Getter for the output character encoding * @returns {number} */ getChrEnc() { const currentTabNum = this.manager.tabs.getActiveTab("output"); if (currentTabNum < 0) { return 0; } return this.outputs[currentTabNum].encoding; } /** * Returns whether the output character encoding was set manually or has been detected automatically * @returns {number} - 0 = unset, 1 = detected, 2 = manual */ getEncodingState() { return this.encodingState; } /** * Sets word wrap on the output editor * @param {boolean} wrap */ setWordWrap(wrap) { this.outputEditorView.dispatch({ effects: this.outputEditorConf.lineWrapping.reconfigure( wrap ? EditorView.lineWrapping : [] ) }); } /** * Sets the value of the current output * @param {string|ArrayBuffer} data * @param {boolean} [force=false] */ async setOutput(data, force=false) { // Don't do anything if the output hasn't changed if (!force && data === this.currentOutputCache) { this.manager.controls.hideStaleIndicator(); this.toggleLoader(false); return; } this.currentOutputCache = data; this.toggleLoader(true); // Remove class to #output-text to change display settings this.outputTextEl.classList.remove("html-output"); // If data is an ArrayBuffer, convert to a string in the correct character encoding const tabNum = this.manager.tabs.getActiveTab("output"); this.manager.timing.recordTime("outputDecodingStart", tabNum); if (data instanceof ArrayBuffer) { await this.detectEncoding(data); data = await this.bufferToStr(data); } this.manager.timing.recordTime("outputDecodingEnd", tabNum); // Turn drawSelection back on this.outputEditorView.dispatch({ effects: this.outputEditorConf.drawSelection.reconfigure( drawSelection() ) }); // Ensure we're not exceeding the maximum line length let wrap = this.app.options.wordWrap; const lineLengthThreshold = 131072; // 128KB if (data.length > lineLengthThreshold) { const lines = data.split(this.getEOLSeq()); const longest = lines.reduce((a, b) => a > b.length ? a : b.length, 0 ); if (longest > lineLengthThreshold) { // If we are exceeding the max line length, turn off word wrap wrap = false; } } // If turning word wrap off, do it before we populate the editor for performance reasons if (!wrap) this.setWordWrap(wrap); // Detect suitable EOL sequence this.detectEOLSequence(data); // We use setTimeout here to delay the editor dispatch until the next event cycle, // ensuring all async actions have completed before attempting to set the contents // of the editor. This is mainly with the above call to setWordWrap() in mind. setTimeout(() => { this.docChanging = true; // Insert data into editor, overwriting any previous contents this.outputEditorView.dispatch({ changes: { from: 0, to: this.outputEditorView.state.doc.length, insert: data } }); // If turning word wrap on, do it after we populate the editor if (wrap) setTimeout(() => { this.setWordWrap(wrap); }); }); } /** * Sets the value of the current output to a rendered HTML value * @param {string} html */ async setHTMLOutput(html) { this.htmlOutput.html = html; this.htmlOutput.changed = true; // This clears the text output, but also fires a View update which // triggers the htmlWidget to render the HTML. We set the force flag // to ensure the loader gets removed and HTML is rendered. await this.setOutput("", true); // Turn off drawSelection this.outputEditorView.dispatch({ effects: this.outputEditorConf.drawSelection.reconfigure([]) }); // Add class to #output-text to change display settings this.outputTextEl.classList.add("html-output"); // Execute script sections const outputHTML = document.getElementById("output-html"); const scriptElements = outputHTML ? outputHTML.querySelectorAll("script") : []; for (let i = 0; i < scriptElements.length; i++) { try { eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval } catch (err) { log.error(err); } } } /** * Clears the HTML output */ clearHTMLOutput() { this.htmlOutput.html = ""; this.htmlOutput.changed = true; // Fire a blank change to force the htmlWidget to update and remove any HTML this.outputEditorView.dispatch({ changes: { from: 0, insert: "" } }); } /** * Checks whether the EOL separator should be updated * * @param {string} data */ detectEOLSequence(data) { // If EOL has been fixed, skip this. if (this.eolState > 1) return; // If data is too long, skip this. if (data.length > 1000000) return; // Detect most likely EOL sequence const eolCharCounts = { "LF": data.count("\u000a"), "VT": data.count("\u000b"), "FF": data.count("\u000c"), "CR": data.count("\u000d"), "CRLF": data.count("\u000d\u000a"), "NEL": data.count("\u0085"), "LS": data.count("\u2028"), "PS": data.count("\u2029") }; // If all zero, leave alone const total = Object.values(eolCharCounts).reduce((acc, curr) => { return acc + curr; }, 0); if (total === 0) return; // Find most prevalent line ending sequence const highest = Object.entries(eolCharCounts).reduce((acc, curr) => { return curr[1] > acc[1] ? curr : acc; }, ["LF", 0]); let choice = highest[0]; // If CRLF not zero and more than half the highest alternative, choose CRLF if ((eolCharCounts.CRLF * 2) > highest[1]) { choice = "CRLF"; } const eolVal = eolCodeToSeq[choice]; if (eolVal === this.getEOLSeq()) return; // Setting automatically this.eolState = 1; this.eolChange(choice); } /** * Checks whether the character encoding should be updated. * * @param {ArrayBuffer} data */ async detectEncoding(data) { // If encoding has been fixed, skip this. if (this.encodingState > 1) return; // If data is too long, skip this. if (data.byteLength > 1000000) return; const enc = isUTF8(data); // 0 = not UTF8, 1 = ASCII, 2 = UTF8 switch (enc) { case 0: // not UTF8 // Set to Raw Bytes this.encodingState = 1; await this.chrEncChange(0, false); break; case 2: // UTF8 // Set to UTF8 this.encodingState = 1; await this.chrEncChange(65001, false); break; case 1: // ASCII default: // Ignore break; } } /** * Calculates the maximum number of tabs to display */ calcMaxTabs() { const numTabs = this.manager.tabs.calcMaxTabs(); if (numTabs !== this.maxTabs) { this.maxTabs = numTabs; this.refreshTabs(this.manager.tabs.getActiveTab("output"), "right"); } } /** * Gets the dish object for an output. * * @param inputNum - The inputNum of the output to get the dish of * @returns {Dish} */ getOutputDish(inputNum) { if (this.outputExists(inputNum) && this.outputs[inputNum].data && this.outputs[inputNum].data.dish) { return this.outputs[inputNum].data.dish; } return null; } /** * Checks if an output exists in the output dictionary * * @param {number} inputNum - The number of the output we're looking for * @returns {boolean} */ outputExists(inputNum) { if (this.outputs[inputNum] === undefined || this.outputs[inputNum] === null) { return false; } return true; } /** * Adds a new output to the output array. * Creates a new tab if we have less than maxtabs tabs open * * @param {number} inputNum - The inputNum of the new output * @param {boolean} [changeTab=true] - If true, change to the new output */ addOutput(inputNum, changeTab = true) { // Remove the output (will only get removed if it already exists) this.removeOutput(inputNum); const newOutput = { data: null, inputNum: inputNum, statusMessage: `Input ${inputNum} has not been baked yet.`, error: null, status: "inactive", bakeId: -1, progress: false, encoding: 0, eolSequence: "\u000a" }; this.outputs[inputNum] = newOutput; this.addTab(inputNum, changeTab); } /** * Updates the value for the output in the output array. * If this is the active output tab, updates the output textarea * * @param {ArrayBuffer | String} data * @param {number} inputNum * @param {boolean} set */ updateOutputValue(data, inputNum, set=true) { if (!this.outputExists(inputNum)) { this.addOutput(inputNum); } if (Object.prototype.hasOwnProperty.call(data, "dish")) { data.dish = new Dish(data.dish); } this.outputs[inputNum].data = data; const tabItem = this.manager.tabs.getTabItem(inputNum, "output"); if (tabItem) tabItem.style.background = ""; if (set) this.set(inputNum); } /** * Updates the status message for the output in the output array. * If this is the active output tab, updates the output textarea * * @param {string} statusMessage * @param {number} inputNum * @param {boolean} [set=true] */ updateOutputMessage(statusMessage, inputNum, set=true) { if (!this.outputExists(inputNum)) return; this.outputs[inputNum].statusMessage = statusMessage; if (set) this.set(inputNum); } /** * Updates the error value for the output in the output array. * If this is the active output tab, calls app.handleError. * Otherwise, the error will be handled when the output is switched to * * @param {Error} error * @param {number} inputNum * @param {number} [progress=0] */ updateOutputError(error, inputNum, progress=0) { if (!this.outputExists(inputNum)) return; const errorString = error.displayStr || error.toString(); this.outputs[inputNum].error = errorString; this.outputs[inputNum].progress = progress; this.updateOutputStatus("error", inputNum); } /** * Updates the status value for the output in the output array * * @param {string} status * @param {number} inputNum */ updateOutputStatus(status, inputNum) { if (!this.outputExists(inputNum)) return; this.outputs[inputNum].status = status; if (status !== "error") { delete this.outputs[inputNum].error; } this.displayTabInfo(inputNum); this.set(inputNum); } /** * Updates the stored bake ID for the output in the output array * * @param {number} bakeId * @param {number} inputNum */ updateOutputBakeId(bakeId, inputNum) { if (!this.outputExists(inputNum)) return; this.outputs[inputNum].bakeId = bakeId; } /** * Updates the stored progress value for the output in the output array * * @param {number} progress * @param {number} total * @param {number} inputNum */ updateOutputProgress(progress, total, inputNum) { if (!this.outputExists(inputNum)) return; this.outputs[inputNum].progress = progress; if (progress !== false) { this.manager.tabs.updateTabProgress(inputNum, progress, total, "output"); } } /** * Removes an output from the output array. * * @param {number} inputNum */ removeOutput(inputNum) { if (!this.outputExists(inputNum)) return; delete this.outputs[inputNum]; } /** * Removes all output tabs */ removeAllOutputs() { this.outputs = {}; const tabsList = document.getElementById("output-tabs"); const tabsListChildren = tabsList.children; tabsList.classList.remove("tabs-left"); tabsList.classList.remove("tabs-right"); for (let i = tabsListChildren.length - 1; i >= 0; i--) { tabsListChildren.item(i).remove(); } } /** * Sets the output in the output pane. * * @param {number} inputNum */ async set(inputNum) { inputNum = parseInt(inputNum, 10); if (inputNum !== this.manager.tabs.getActiveTab("output") || !this.outputExists(inputNum)) return; this.toggleLoader(true); return new Promise(async function(resolve, reject) { const output = this.outputs[inputNum]; this.manager.timing.recordTime("settingOutput", inputNum); // Update the EOL value this.outputEditorView.dispatch({ effects: this.outputEditorConf.eol.reconfigure( EditorState.lineSeparator.of(output.eolSequence) ) }); // If pending or baking, show loader and status message // If error, style the tab and handle the error // If done, display the output if it's the active tab // If inactive, show the last bake value (or blank) if (output.status === "inactive" || output.status === "stale" || (output.status === "baked" && output.bakeId < this.manager.worker.bakeId)) { this.manager.controls.showStaleIndicator(); } else { this.manager.controls.hideStaleIndicator(); } if (output.progress !== undefined && !this.app.baking) { this.manager.recipe.updateBreakpointIndicator(output.progress); } else { this.manager.recipe.updateBreakpointIndicator(false); } if (output.status === "pending" || output.status === "baking") { // show the loader and the status message if it's being shown // otherwise don't do anything document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage; } else if (output.status === "error") { this.clearHTMLOutput(); if (output.error) { await this.setOutput(output.error); } else { await this.setOutput(output.data.result); } } else if (output.status === "baked" || output.status === "inactive") { document.querySelector("#output-loader .loading-msg").textContent = `Loading output ${inputNum}`; if (output.data === null) { this.clearHTMLOutput(); await this.setOutput(""); return; } switch (output.data.type) { case "html": await this.setHTMLOutput(output.data.result); break; case "ArrayBuffer": case "string": default: this.clearHTMLOutput(); await this.setOutput(output.data.result); break; } this.manager.timing.recordTime("complete", inputNum); // Trigger an update so that the status bar recalculates timings this.outputEditorView.dispatch({ changes: { from: 0, to: 0 } }); debounce(this.backgroundMagic, 50, "backgroundMagic", this, [])(); } }.bind(this)); } /** * Retrieves the dish as a string * * @param {Dish} dish * @returns {string} */ async getDishStr(dish) { return await new Promise(resolve => { this.manager.worker.getDishAs(dish, "string", r => { resolve(r.value); }); }); } /** * Retrieves the dish as an ArrayBuffer * * @param {Dish} dish * @returns {ArrayBuffer} */ async getDishBuffer(dish) { return await new Promise(resolve => { this.manager.worker.getDishAs(dish, "ArrayBuffer", r => { resolve(r.value); }); }); } /** * Retrieves the title of the Dish as a string * * @param {Dish} dish * @param {number} maxLength * @returns {string} */ async getDishTitle(dish, maxLength) { return await new Promise(resolve => { this.manager.worker.getDishTitle(dish, maxLength, r => { resolve(r.value); }); }); } /** * Asks a worker to translate an ArrayBuffer into a certain character encoding * * @param {ArrrayBuffer} buffer * @returns {string} */ async bufferToStr(buffer) { const encoding = this.getChrEnc(); if (buffer.byteLength === 0) return ""; return await new Promise(resolve => { this.manager.worker.bufferToStr(buffer, encoding, r => { resolve(r.value); }); }); } /** * Save bombe object then remove it from the DOM so that it does not cause performance issues. */ saveBombe() { this.bombeEl = document.getElementById("bombe"); this.bombeEl.parentNode.removeChild(this.bombeEl); } /** * Shows or hides the output loading screen. * The animated Bombe SVG, whilst quite aesthetically pleasing, is reasonably CPU * intensive, so we remove it from the DOM when not in use. We only show it if the * recipe is taking longer than 200ms. We add it to the DOM just before that so that * it is ready to fade in without stuttering. * * @param {boolean} value - If true, show the loader */ toggleLoader(value) { const outputLoader = document.getElementById("output-loader"), animation = document.getElementById("output-loader-animation"); if (value) { this.manager.controls.hideStaleIndicator(); // Don't add the bombe if it's already there or scheduled to be loaded if (animation.children.length === 0 && !this.appendBombeTimeout) { // Start a timer to add the Bombe to the DOM just before we make it // visible so that there is no stuttering this.appendBombeTimeout = setTimeout(function() { this.appendBombeTimeout = null; animation.appendChild(this.bombeEl); }.bind(this), 150); } if (outputLoader.style.visibility !== "visible" && !this.outputLoaderTimeout) { // Show the loading screen this.outputLoaderTimeout = setTimeout(function() { this.outputLoaderTimeout = null; outputLoader.style.visibility = "visible"; outputLoader.style.opacity = 1; }, 200); } } else if (outputLoader.style.visibility !== "hidden" || this.appendBombeTimeout || this.outputLoaderTimeout) { clearTimeout(this.appendBombeTimeout); clearTimeout(this.outputLoaderTimeout); this.appendBombeTimeout = null; this.outputLoaderTimeout = null; // Remove the Bombe from the DOM to save resources this.outputLoaderTimeout = setTimeout(function () { this.outputLoaderTimeout = null; if (animation.children.length > 0) animation.removeChild(this.bombeEl); }.bind(this), 500); outputLoader.style.opacity = 0; outputLoader.style.visibility = "hidden"; } } /** * Handler for save click events. * Saves the current output to a file. */ saveClick() { this.downloadFile(); } /** * Handler for file download events. */ async downloadFile() { const dish = this.getOutputDish(this.manager.tabs.getActiveTab("output")); if (dish === null) { this.app.alert("Could not find any output data to download. Has this output been baked?", 3000); return; } const data = await dish.get(Dish.ARRAY_BUFFER); let ext = ".dat"; // Detect file type automatically const types = detectFileType(data); if (types.length) { ext = `.${types[0].extension.split(",", 1)[0]}`; } const fileName = window.prompt("Please enter a filename: ", `download${ext}`); // Assume if the user clicks cancel they don't want to download if (fileName === null) return; const file = new File([data], fileName); FileSaver.saveAs(file, fileName, {autoBom: false}); } /** * Handler for save all click event * Saves all outputs to a single archive file */ async saveAllClick() { const downloadButton = document.getElementById("save-all-to-file"); if (downloadButton.firstElementChild.innerHTML === "archive") { this.downloadAllFiles(); } else { const cancel = await new Promise(function(resolve, reject) { this.app.confirm( "Cancel zipping?", "The outputs are currently being zipped for download.
        Cancel zipping?", "Continue zipping", "Cancel zipping", resolve, this); }.bind(this)); if (!cancel) { this.terminateZipWorker(); } } } /** * Spawns a new ZipWorker and sends it the outputs so that they can * be zipped for download */ async downloadAllFiles() { const inputNums = Object.keys(this.outputs); for (let i = 0; i < inputNums.length; i++) { const iNum = inputNums[i]; if (this.outputs[iNum].status !== "baked" || this.outputs[iNum].bakeId !== this.manager.worker.bakeId) { const continueDownloading = await new Promise(function(resolve, reject) { this.app.confirm( "Incomplete outputs", "Not all outputs have been baked yet. Continue downloading outputs?", "Download", "Cancel", resolve, this); }.bind(this)); if (continueDownloading) { break; } else { return; } } } let fileName = window.prompt("Please enter a filename: ", "download.zip"); if (fileName === null || fileName === "") { // Don't zip the files if there isn't a filename this.app.alert("No filename was specified.", 3000); return; } if (!fileName.match(/.zip$/)) { fileName += ".zip"; } let fileExt = window.prompt("Please enter a file extension for the files, or leave blank to detect automatically.", ""); if (fileExt === null) fileExt = ""; if (this.zipWorker !== null) { this.terminateZipWorker(); } const downloadButton = document.getElementById("save-all-to-file"); downloadButton.classList.add("spin"); downloadButton.title = `Zipping ${inputNums.length} files...`; downloadButton.setAttribute("data-original-title", `Zipping ${inputNums.length} files...`); downloadButton.firstElementChild.innerHTML = "autorenew"; log.debug("Creating ZipWorker"); this.zipWorker = new ZipWorker(); this.zipWorker.postMessage({ action: "setLogLevel", data: log.getLevel() }); this.zipWorker.postMessage({ action: "zipFiles", data: { outputs: this.outputs, filename: fileName, fileExtension: fileExt } }); this.zipWorker.addEventListener("message", this.handleZipWorkerMessage.bind(this)); } /** * Terminate the ZipWorker */ terminateZipWorker() { if (this.zipWorker === null) return; // Already terminated log.debug("Terminating ZipWorker."); this.zipWorker.terminate(); this.zipWorker = null; const downloadButton = document.getElementById("save-all-to-file"); downloadButton.classList.remove("spin"); downloadButton.title = "Save all outputs to a zip file"; downloadButton.setAttribute("data-original-title", "Save all outputs to a zip file"); downloadButton.firstElementChild.innerHTML = "archive"; } /** * Handle messages sent back by the ZipWorker */ handleZipWorkerMessage(e) { const r = e.data; if (!("zippedFile" in r)) { log.error("No zipped file was sent in the message."); this.terminateZipWorker(); return; } if (!("filename" in r)) { log.error("No filename was sent in the message."); this.terminateZipWorker(); return; } const file = new File([r.zippedFile], r.filename); FileSaver.saveAs(file, r.filename, {autoBom: false}); this.terminateZipWorker(); } /** * Adds a new output tab. * * @param {number} inputNum * @param {boolean} [changeTab=true] */ addTab(inputNum, changeTab = true) { const tabsWrapper = document.getElementById("output-tabs"); const numTabs = tabsWrapper.children.length; if (!this.manager.tabs.getTabItem(inputNum, "output") && numTabs < this.maxTabs) { // Create a new tab element const newTab = this.manager.tabs.createTabElement(inputNum, changeTab, "output"); tabsWrapper.appendChild(newTab); } else if (numTabs === this.maxTabs) { // Can't create a new tab document.getElementById("output-tabs").lastElementChild.classList.add("tabs-right"); } this.displayTabInfo(inputNum); if (changeTab) { this.changeTab(inputNum, false); } } /** * Changes the active tab * * @param {number} inputNum * @param {boolean} [changeInput = false] */ changeTab(inputNum, changeInput = false) { if (!this.outputExists(inputNum)) return; const currentNum = this.manager.tabs.getActiveTab("output"); this.hideMagicButton(); if (!this.manager.tabs.changeTab(inputNum, "output")) { let direction = "right"; if (currentNum > inputNum) { direction = "left"; } const newOutputs = this.getNearbyNums(inputNum, direction); const tabsLeft = (newOutputs[0] !== this.getSmallestInputNum()); const tabsRight = (newOutputs[newOutputs.length - 1] !== this.getLargestInputNum()); this.manager.tabs.refreshTabs(newOutputs, inputNum, tabsLeft, tabsRight, "output"); for (let i = 0; i < newOutputs.length; i++) { this.displayTabInfo(newOutputs[i]); } } this.set(inputNum); if (changeInput) { this.manager.input.changeTab(inputNum, false); } } /** * Handler for changing tabs event * * @param {event} mouseEvent */ changeTabClick(mouseEvent) { if (!mouseEvent.target) return; const tabNum = mouseEvent.target.parentElement.getAttribute("inputNum"); if (tabNum) { this.changeTab(parseInt(tabNum, 10), this.app.options.syncTabs); } } /** * Handler for scrolling on the output tabs area * * @param {event} wheelEvent */ scrollTab(wheelEvent) { wheelEvent.preventDefault(); if (wheelEvent.deltaY > 0) { this.changeTabLeft(); } else if (wheelEvent.deltaY < 0) { this.changeTabRight(); } } /** * Handler for mouse down on the next tab button */ nextTabClick() { this.mousedown = true; this.changeTabRight(); const time = 200; const func = function(time) { if (this.mousedown) { this.changeTabRight(); const newTime = (time > 50) ? time - 10 : 50; setTimeout(func.bind(this, [newTime]), newTime); } }; this.tabTimeout = setTimeout(func.bind(this, [time]), time); } /** * Handler for mouse down on the previous tab button */ previousTabClick() { this.mousedown = true; this.changeTabLeft(); const time = 200; const func = function(time) { if (this.mousedown) { this.changeTabLeft(); const newTime = (time > 50) ? time - 10 : 50; setTimeout(func.bind(this, [newTime]), newTime); } }; this.tabTimeout = setTimeout(func.bind(this, [time]), time); } /** * Handler for mouse up event on the tab buttons */ tabMouseUp() { this.mousedown = false; clearTimeout(this.tabTimeout); this.tabTimeout = null; } /** * Handler for changing to the left tab */ changeTabLeft() { const currentTab = this.manager.tabs.getActiveTab("output"); this.changeTab(this.getPreviousInputNum(currentTab), this.app.options.syncTabs); } /** * Handler for changing to the right tab */ changeTabRight() { const currentTab = this.manager.tabs.getActiveTab("output"); this.changeTab(this.getNextInputNum(currentTab), this.app.options.syncTabs); } /** * Handler for go to tab button clicked */ goToTab() { const min = this.getSmallestInputNum(), max = this.getLargestInputNum(); let tabNum = window.prompt(`Enter tab number (${min} - ${max}):`, this.manager.tabs.getActiveTab("output").toString()); if (tabNum === null) return; tabNum = parseInt(tabNum, 10); if (this.outputExists(tabNum)) { this.changeTab(tabNum, this.app.options.syncTabs); } } /** * Generates a list of the nearby inputNums * @param inputNum * @param direction */ getNearbyNums(inputNum, direction) { const nums = []; for (let i = 0; i < this.maxTabs; i++) { let newNum; if (i === 0 && this.outputs[inputNum] !== undefined) { newNum = inputNum; } else { switch (direction) { case "left": newNum = this.getNextInputNum(nums[i - 1]); if (newNum === nums[i - 1]) { direction = "right"; newNum = this.getPreviousInputNum(nums[0]); } break; case "right": newNum = this.getPreviousInputNum(nums[i - 1]); if (newNum === nums[i - 1]) { direction = "left"; newNum = this.getNextInputNum(nums[0]); } } } if (!nums.includes(newNum) && (newNum > 0)) { nums.push(newNum); } } nums.sort((a, b) => a - b); // Forces the sort function to treat a and b as numbers return nums; } /** * Gets the largest inputNum * * @returns {number} */ getLargestInputNum() { const inputNums = Object.keys(this.outputs); if (inputNums.length === 0) return -1; return Math.max(...inputNums); } /** * Gets the smallest inputNum * * @returns {number} */ getSmallestInputNum() { const inputNums = Object.keys(this.outputs); if (inputNums.length === 0) return -1; return Math.min(...inputNums); } /** * Gets the previous inputNum * * @param {number} inputNum - The current input number * @returns {number} */ getPreviousInputNum(inputNum) { const inputNums = Object.keys(this.outputs); if (inputNums.length === 0) return -1; let num = Math.min(...inputNums); for (let i = 0; i < inputNums.length; i++) { const iNum = parseInt(inputNums[i], 10); if (iNum < inputNum) { if (iNum > num) { num = iNum; } } } return num; } /** * Gets the next inputNum * * @param {number} inputNum - The current input number * @returns {number} */ getNextInputNum(inputNum) { const inputNums = Object.keys(this.outputs); if (inputNums.length === 0) return -1; let num = Math.max(...inputNums); for (let i = 0; i < inputNums.length; i++) { const iNum = parseInt(inputNums[i], 10); if (iNum > inputNum) { if (iNum < num) { num = iNum; } } } return num; } /** * Removes a tab and it's corresponding output * * @param {number} inputNum */ removeTab(inputNum) { if (!this.outputExists(inputNum)) return; const tabElement = this.manager.tabs.getTabItem(inputNum, "output"); this.removeOutput(inputNum); if (tabElement !== null) { this.refreshTabs(this.getPreviousInputNum(inputNum), "left"); } } /** * Redraw the entire tab bar to remove any outdated tabs * * @param {number} activeTab * @param {string} direction - Either "left" or "right" */ refreshTabs(activeTab, direction) { const newNums = this.getNearbyNums(activeTab, direction), tabsLeft = (newNums[0] !== this.getSmallestInputNum() && newNums.length > 0), tabsRight = (newNums[newNums.length - 1] !== this.getLargestInputNum() && newNums.length > 0); this.manager.tabs.refreshTabs(newNums, activeTab, tabsLeft, tabsRight, "output"); for (let i = 0; i < newNums.length; i++) { this.displayTabInfo(newNums[i]); } } /** * Display output information in the tab header * * @param {number} inputNum */ async displayTabInfo(inputNum) { // Don't display anything if there are no, or only one, tabs if (!this.outputExists(inputNum) || Object.keys(this.outputs).length <= 1) return; const dish = this.getOutputDish(inputNum); let tabStr = ""; if (dish !== null) { tabStr = await this.getDishTitle(this.getOutputDish(inputNum), 100); tabStr = tabStr.replace(/[\n\r]/g, ""); } this.manager.tabs.updateTabHeader(inputNum, tabStr, "output"); if (this.manager.worker.recipeConfig !== undefined) { this.manager.tabs.updateTabProgress(inputNum, this.outputs[inputNum]?.progress, this.manager.worker.recipeConfig.length, "output"); } const tabItem = this.manager.tabs.getTabItem(inputNum, "output"); if (tabItem) { if (this.outputs[inputNum].status === "error") { tabItem.style.color = "#FF0000"; } else { tabItem.style.color = ""; } } } /** * Triggers the BackgroundWorker to attempt Magic on the current output. */ async backgroundMagic() { this.hideMagicButton(); const dish = this.getOutputDish(this.manager.tabs.getActiveTab("output")); if (!this.app.options.autoMagic || dish === null) return; const buffer = await this.getDishBuffer(dish); const sample = buffer.slice(0, 1000) || ""; if (sample.length || sample.byteLength) { this.manager.background.magic(sample); } } /** * Handles the results of a background Magic call. * * @param {Object[]} options */ backgroundMagicResult(options) { if (!options.length) return; const currentRecipeConfig = this.app.getRecipeConfig(); let msg = "", newRecipeConfig; if (options[0].recipe.length) { const opSequence = options[0].recipe.map(o => o.op).join(", "); newRecipeConfig = currentRecipeConfig.concat(options[0].recipe); msg = `${opSequence} will produce "${Utils.escapeHtml(Utils.truncate(options[0].data), 30)}"`; } else if (options[0].fileType && options[0].fileType.name) { const ft = options[0].fileType; newRecipeConfig = currentRecipeConfig.concat([{op: "Detect File Type", args: []}]); msg = `${ft.name} file detected`; } else { return; } this.showMagicButton(msg, newRecipeConfig); } /** * Handler for Magic click events. * * Loads the Magic recipe. * * @fires Manager#statechange */ magicClick() { const magicButton = document.getElementById("magic"); this.app.setRecipeConfig(JSON.parse(magicButton.getAttribute("data-recipe"))); window.dispatchEvent(this.manager.statechange); this.hideMagicButton(); } /** * Displays the Magic button with a title and adds a link to a recipe. * * @param {string} msg * @param {Object[]} recipeConfig */ showMagicButton(msg, recipeConfig) { const magicButton = document.getElementById("magic"); magicButton.setAttribute("data-original-title", msg); magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, ""); magicButton.classList.remove("hidden"); magicButton.classList.add("pulse"); } /** * Hides the Magic button and resets its values. */ hideMagicButton() { const magicButton = document.getElementById("magic"); magicButton.classList.add("hidden"); magicButton.classList.remove("pulse"); magicButton.setAttribute("data-recipe", ""); magicButton.setAttribute("data-original-title", "Magic!"); } /** * Handler for extract file events. * * @param {Event} e */ async extractFileClick(e) { e.preventDefault(); e.stopPropagation(); const el = e.target.nodeName === "I" ? e.target.parentNode : e.target; const blobURL = el.getAttribute("blob-url"); const fileName = el.getAttribute("file-name"); const blob = await fetch(blobURL).then(r => r.blob()); this.manager.input.loadUIFiles([new File([blob], fileName, {type: blob.type})]); } /** * Handler for copy click events. * Copies the output to the clipboard */ async copyClick() { const dish = this.getOutputDish(this.manager.tabs.getActiveTab("output")); if (dish === null) { this.app.alert("Could not find data to copy. Has this output been baked yet?", 3000); return; } const output = await this.getDishStr(dish); const self = this; navigator.clipboard.writeText(output).then(function() { self.app.alert("Copied raw output successfully.", 2000); }, function(err) { self.app.alert("Sorry, the output could not be copied.", 3000); }); } /** * Handler for switch click events. * Moves the current output into the input textarea. */ async switchClick() { const activeTab = this.manager.tabs.getActiveTab("output"); const switchButton = document.getElementById("switch"); switchButton.classList.add("spin"); switchButton.disabled = true; switchButton.firstElementChild.innerHTML = "autorenew"; $(switchButton).tooltip("hide"); const activeData = await this.getDishBuffer(this.getOutputDish(activeTab)); if (this.outputExists(activeTab)) { this.manager.input.set(activeTab, { type: "userinput", buffer: activeData, encoding: this.outputs[activeTab].encoding, eolSequence: this.outputs[activeTab].eolSequence }); } switchButton.classList.remove("spin"); switchButton.disabled = false; switchButton.firstElementChild.innerHTML = "open_in_browser"; } /** * Handler for maximise output click events. * Resizes the output frame to be as large as possible, or restores it to its original size. */ maximiseOutputClick(e) { const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode; if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) { document.body.classList.add("output-maximised"); this.app.initialiseSplitter(true); this.app.columnSplitter.collapse(0); this.app.columnSplitter.collapse(1); this.app.ioSplitter.collapse(0); $(el).attr("data-original-title", "Restore output pane"); $(el).attr("aria-label", "Restore output pane"); el.querySelector("i").innerHTML = "fullscreen_exit"; } else { document.body.classList.remove("output-maximised"); $(el).attr("data-original-title", "Maximise output pane"); $(el).attr("aria-label", "Maximise output pane"); el.querySelector("i").innerHTML = "fullscreen"; this.app.initialiseSplitter(false); this.app.resetLayout(); } } /** * Handler for find tab button clicked */ findTab() { this.filterTabSearch(); $("#output-tab-modal").modal(); } /** * Searches the outputs using the filter settings and displays the results */ async filterTabSearch() { const showPending = document.getElementById("output-show-pending").checked, showBaking = document.getElementById("output-show-baking").checked, showBaked = document.getElementById("output-show-baked").checked, showStale = document.getElementById("output-show-stale").checked, showErrored = document.getElementById("output-show-errored").checked, contentFilter = document.getElementById("output-content-filter").value, resultsList = document.getElementById("output-search-results"), numResults = parseInt(document.getElementById("output-num-results").value, 10), inputNums = Object.keys(this.outputs), results = []; let contentFilterExp; try { contentFilterExp = new RegExp(contentFilter, "i"); } catch (error) { this.app.handleError(error); return; } // Search through the outputs for matching output results for (let i = 0; i < inputNums.length; i++) { const iNum = inputNums[i], output = this.outputs[iNum]; if (output.status === "pending" && showPending || output.status === "baking" && showBaking || output.status === "error" && showErrored || output.status === "stale" && showStale || output.status === "inactive" && showStale) { const outDisplay = { "pending": "Not baked yet", "baking": "Baking", "error": output.error || "Errored", "stale": "Stale (output is out of date)", "inactive": "Not baked yet" }; // If the output has a dish object, check it against the filter if (Object.prototype.hasOwnProperty.call(output, "data") && output.data && Object.prototype.hasOwnProperty.call(output.data, "dish")) { const data = await output.data.dish.get(Dish.STRING); if (contentFilterExp.test(data)) { results.push({ inputNum: iNum, textDisplay: data.slice(0, 100) }); } } else { results.push({ inputNum: iNum, textDisplay: outDisplay[output.status] }); } } else if (output.status === "baked" && showBaked && output.progress === false) { let data = await output.data.dish.get(Dish.STRING); data = data.replace(/[\r\n]/g, ""); if (contentFilterExp.test(data)) { results.push({ inputNum: iNum, textDisplay: data.slice(0, 100) }); } } else if (output.progress !== false && showErrored) { let data = await output.data.dish.get(Dish.STRING); data = data.replace(/[\r\n]/g, ""); if (contentFilterExp.test(data)) { results.push({ inputNum: iNum, textDisplay: data.slice(0, 100) }); } } if (results.length >= numResults) { break; } } for (let i = resultsList.children.length - 1; i >= 0; i--) { resultsList.children.item(i).remove(); } for (let i = 0; i < results.length; i++) { const newListItem = document.createElement("li"); newListItem.classList.add("output-filter-result"); newListItem.setAttribute("inputNum", results[i].inputNum); newListItem.innerText = `${results[i].inputNum}: ${results[i].textDisplay}`; resultsList.appendChild(newListItem); } } /** * Handler for clicking on a filter result. * Changes to the clicked output * * @param {event} e */ filterItemClick(e) { if (!e.target) return; const inputNum = parseInt(e.target.getAttribute("inputNum"), 10); if (inputNum <= 0) return; $("#output-tab-modal").modal("hide"); this.changeTab(inputNum, this.app.options.syncTabs); } /** * Sets the console log level in the workers. */ setLogLevel() { if (!this.zipWorker) return; this.zipWorker.postMessage({ action: "setLogLevel", data: log.getLevel() }); } } export default OutputWaiter; ================================================ FILE: src/web/waiters/RecipeWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import HTMLOperation from "../HTMLOperation.mjs"; import Sortable from "sortablejs"; import Utils from "../../core/Utils.mjs"; import {escapeControlChars} from "../utils/editorUtils.mjs"; import DOMPurify from "dompurify"; /** * Waiter to handle events related to the recipe. */ class RecipeWaiter { /** * RecipeWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; this.removeIntent = false; } /** * Sets up the drag and drop capability for operations in the operations and recipe areas. */ initialiseOperationDragNDrop() { const recList = document.getElementById("rec-list"); // Recipe list Sortable.create(recList, { group: "recipe", sort: true, animation: 0, delay: 0, filter: ".arg", preventOnFilter: false, setData: function(dataTransfer, dragEl) { dataTransfer.setData("Text", dragEl.querySelector(".op-title").textContent); }, onEnd: function(evt) { if (this.removeIntent) { evt.item.remove(); evt.target.dispatchEvent(this.manager.operationremove); } }.bind(this), onSort: function(evt) { if (evt.from.id === "rec-list") { document.dispatchEvent(this.manager.statechange); } }.bind(this) }); Sortable.utils.on(recList, "dragover", function() { this.removeIntent = false; }.bind(this)); Sortable.utils.on(recList, "dragleave", function() { this.removeIntent = true; this.app.progress = 0; }.bind(this)); Sortable.utils.on(recList, "touchend", function(e) { const loc = e.changedTouches[0]; const target = document.elementFromPoint(loc.clientX, loc.clientY); this.removeIntent = !recList.contains(target); }.bind(this)); // Favourites category document.querySelector("#categories a").addEventListener("dragover", this.favDragover.bind(this)); document.querySelector("#categories a").addEventListener("dragleave", this.favDragleave.bind(this)); document.querySelector("#categories a").addEventListener("drop", this.favDrop.bind(this)); } /** * Creates a drag-n-droppable seed list of operations. * * @param {element} listEl - The list to initialise */ createSortableSeedList(listEl) { Sortable.create(listEl, { group: { name: "recipe", pull: "clone", put: false, }, sort: false, setData: function(dataTransfer, dragEl) { dataTransfer.setData("Text", dragEl.textContent); }, onStart: function(evt) { // Removes popover element and event bindings from the dragged operation but not the // event bindings from the one left in the operations list. Without manually removing // these bindings, we cannot re-initialise the popover on the stub operation. $(evt.item) .popover("dispose") .removeData("bs.popover") .off("mouseenter") .off("mouseleave") .attr("data-toggle", "popover-disabled"); $(evt.clone) .off(".popover") .removeData("bs.popover"); }, onEnd: this.opSortEnd.bind(this) }); } /** * Handler for operation sort end events. * Removes the operation from the list if it has been dropped outside. If not, adds it to the list * at the appropriate place and initialises it. * * @fires Manager#operationadd * @param {event} evt */ opSortEnd(evt) { if (this.removeIntent && evt.item.parentNode.id === "rec-list") { evt.item.remove(); return; } // Reinitialise the popover on the original element in the ops list because for some reason it // gets destroyed and recreated. If the clone isn't in the ops list, we use the original item instead. let enableOpsElement; if (evt.clone?.parentNode?.classList?.contains("op-list")) { enableOpsElement = evt.clone; } else { enableOpsElement = evt.item; $(evt.item).attr("data-toggle", "popover"); } this.manager.ops.enableOpsListPopovers(enableOpsElement); if (evt.item.parentNode.id !== "rec-list") { return; } this.buildRecipeOperation(evt.item); evt.item.dispatchEvent(this.manager.operationadd); } /** * Handler for favourite dragover events. * If the element being dragged is an operation, displays a visual cue so that the user knows it can * be dropped here. * * @param {event} e */ favDragover(e) { if (e.dataTransfer.effectAllowed !== "move") return false; e.stopPropagation(); e.preventDefault(); if (e.target?.className?.indexOf("category-title") > -1) { // Hovering over the a e.target.classList.add("favourites-hover"); } else if (e.target?.parentNode?.className?.indexOf("category-title") > -1) { // Hovering over the Edit button e.target.parentNode.classList.add("favourites-hover"); } else if (e.target?.parentNode?.parentNode?.className?.indexOf("category-title") > -1) { // Hovering over the image on the Edit button e.target.parentNode.parentNode.classList.add("favourites-hover"); } } /** * Handler for favourite dragleave events. * Removes the visual cue. * * @param {event} e */ favDragleave(e) { e.stopPropagation(); e.preventDefault(); document.querySelector("#categories a").classList.remove("favourites-hover"); } /** * Handler for favourite drop events. * Adds the dragged operation to the favourites list. * * @param {event} e */ favDrop(e) { e.stopPropagation(); e.preventDefault(); e.target.classList.remove("favourites-hover"); const opName = e.dataTransfer.getData("Text"); this.app.addFavourite(opName); } /** * Handler for ingredient change events. * * @fires Manager#statechange */ ingChange(e) { if (e && e?.target?.classList?.contains("no-state-change")) return; window.dispatchEvent(this.manager.statechange); } /** * Handler for hide-args click events. * Updates the icon status. * * @fires Manager#statechange * @param {event} e */ hideArgsClick(e) { const icon = e.target; if (icon.getAttribute("hide-args") === "false") { icon.setAttribute("hide-args", "true"); icon.innerText = "keyboard_arrow_down"; icon.classList.add("hide-args-selected"); icon.parentNode.previousElementSibling.style.display = "none"; } else { icon.setAttribute("hide-args", "false"); icon.innerText = "keyboard_arrow_up"; icon.classList.remove("hide-args-selected"); icon.parentNode.previousElementSibling.style.display = "grid"; } const icons = Array.from(document.getElementsByClassName("hide-args-icon")); if (icons.length > 1) { // Check if ALL the icons are hidden/shown const uniqueIcons = icons.map(function(item) { return item.getAttribute("hide-args"); }).unique(); const controlsIconStatus = document.getElementById("hide-icon").getAttribute("hide-args"); // If all icons are in the same state and the global icon isn't, fix it if (uniqueIcons.length === 1 && icon.getAttribute("hide-args") !== controlsIconStatus) { this.manager.controls.hideRecipeArgsClick(); } } window.dispatchEvent(this.manager.statechange); } /** * Handler for disable click events. * Updates the icon status. * * @fires Manager#statechange * @param {event} e */ disableClick(e) { const icon = e.target; if (icon.getAttribute("disabled") === "false") { icon.setAttribute("disabled", "true"); icon.classList.add("disable-icon-selected"); icon.parentNode.parentNode.classList.add("disabled"); } else { icon.setAttribute("disabled", "false"); icon.classList.remove("disable-icon-selected"); icon.parentNode.parentNode.classList.remove("disabled"); } this.app.progress = 0; window.dispatchEvent(this.manager.statechange); } /** * Handler for breakpoint click events. * Updates the icon status. * * @fires Manager#statechange * @param {event} e */ breakpointClick(e) { const bp = e.target; if (bp.getAttribute("break") === "false") { bp.setAttribute("break", "true"); bp.classList.add("breakpoint-selected"); } else { bp.setAttribute("break", "false"); bp.classList.remove("breakpoint-selected"); } window.dispatchEvent(this.manager.statechange); } /** * Handler for operation doubleclick events. * Removes the operation from the recipe and auto bakes. * * @fires Manager#statechange * @param {event} e */ operationDblclick(e) { e.target.remove(); this.opRemove(e); } /** * Handler for operation child doubleclick events. * Removes the operation from the recipe. * * @fires Manager#statechange * @param {event} e */ operationChildDblclick(e) { e.target.parentNode.remove(); this.opRemove(e); } /** * Generates a configuration object to represent the current recipe. * * @returns {recipeConfig} */ getConfig() { const config = []; let ingredients, ingList, disabled, bp, item; const operations = document.querySelectorAll("#rec-list li.operation"); for (let i = 0; i < operations.length; i++) { ingredients = []; disabled = operations[i].querySelector(".disable-icon"); bp = operations[i].querySelector(".breakpoint"); ingList = operations[i].querySelectorAll(".arg"); for (let j = 0; j < ingList.length; j++) { if (ingList[j].getAttribute("type") === "checkbox") { // checkbox ingredients[j] = ingList[j].checked; } else if (ingList[j].classList.contains("toggle-string")) { // toggleString ingredients[j] = { option: ingList[j].parentNode.parentNode.querySelector("button").textContent, string: ingList[j].value }; } else if (ingList[j].getAttribute("type") === "number") { // number ingredients[j] = parseFloat(ingList[j].value); } else { // all others ingredients[j] = ingList[j].value; } } item = { op: operations[i].querySelector(".op-title").textContent, args: ingredients }; if (disabled && disabled.getAttribute("disabled") === "true") { item.disabled = true; } if (bp && bp.getAttribute("break") === "true") { item.breakpoint = true; } config.push(item); } return config; } /** * Moves or removes the breakpoint indicator in the recipe based on the position. * * @param {number|boolean} position - If boolean, turn off all indicators */ updateBreakpointIndicator(position) { const operations = document.querySelectorAll("#rec-list li.operation"); if (typeof position === "boolean") position = operations.length; for (let i = 0; i < operations.length; i++) { if (i === position) { operations[i].classList.add("break"); } else { operations[i].classList.remove("break"); } } } /** * Given an operation stub element, this function converts it into a full recipe element with * arguments. * * @param {element} el - The operation stub element from the operations pane */ buildRecipeOperation(el) { const opName = el.textContent; const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); el.innerHTML = op.toFullHtml(); if (this.app.operations[opName].flowControl) { el.classList.add("flow-control-op"); } $(el).find("[data-toggle='tooltip']").tooltip(); // Disable auto-bake if this is a manual op if (op.manualBake && this.app.autoBake_) { this.manager.controls.setAutoBake(false); this.app.alert("Auto-Bake is disabled by default when using this operation.", 5000); } } /** * Adds the specified operation to the recipe. * * @fires Manager#operationadd * @param {string} name - The name of the operation to add * @returns {element} */ addOperation(name) { const item = document.createElement("li"); item.classList.add("operation"); const clean = DOMPurify.sanitize(name); item.innerHTML = clean; this.buildRecipeOperation(item); document.getElementById("rec-list").appendChild(item); item.dispatchEvent(this.manager.operationadd); return item; } /** * Removes all operations from the recipe. * * @fires Manager#operationremove */ clearRecipe() { const recList = document.getElementById("rec-list"); while (recList.firstChild) { recList.removeChild(recList.firstChild); } recList.dispatchEvent(this.manager.operationremove); } /** * Handler for operation dropdown events from toggleString arguments. * Sets the selected option as the name of the button. * * @param {event} e */ dropdownToggleClick(e) { e.stopPropagation(); e.preventDefault(); const el = e.target; const button = el.parentNode.parentNode.querySelector("button"); button.innerHTML = el.textContent; this.ingChange(); } /** * Triggers various change events for operation arguments that have just been initialised. * * @param {HTMLElement} op */ triggerArgEvents(op) { // Trigger populateOption and argSelector events const triggerableOptions = op.querySelectorAll(".populate-option, .arg-selector"); const evt = new Event("change", {bubbles: true}); if (triggerableOptions.length) { for (const el of triggerableOptions) { el.dispatchEvent(evt); } } } /** * Handler for operationadd events. * * @listens Manager#operationadd * @fires Manager#statechange * @param {event} e */ opAdd(e) { log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`); this.triggerArgEvents(e.target); window.dispatchEvent(this.manager.statechange); } /** * Handler for operationremove events. * * @listens Manager#operationremove * @fires Manager#statechange * @param {event} e */ opRemove(e) { log.debug("Operation removed from recipe"); window.dispatchEvent(this.manager.statechange); } /** * Handler for text argument dragover events. * Gives the user a visual cue to show that items can be dropped here. * * @param {event} e */ textArgDragover (e) { // This will be set if we're dragging an operation if (e.dataTransfer.effectAllowed === "move") return false; e.stopPropagation(); e.preventDefault(); e.target.closest("textarea.arg").classList.add("dropping-file"); } /** * Handler for text argument dragleave events. * Removes the visual cue. * * @param {event} e */ textArgDragLeave (e) { e.stopPropagation(); e.preventDefault(); e.target.classList.remove("dropping-file"); } /** * Handler for text argument drop events. * Loads the dragged data into the argument textarea. * * @param {event} e */ textArgDrop(e) { // This will be set if we're dragging an operation if (e.dataTransfer.effectAllowed === "move") return false; e.stopPropagation(); e.preventDefault(); const targ = e.target; const file = e.dataTransfer.files[0]; const text = e.dataTransfer.getData("Text"); targ.classList.remove("dropping-file"); if (text) { targ.value = text; return; } if (file) { const reader = new FileReader(); const self = this; reader.onload = function (e) { targ.value = e.target.result; // Trigger floating label move const changeEvent = new Event("change"); targ.dispatchEvent(changeEvent); window.dispatchEvent(self.manager.statechange); }; reader.readAsText(file); } } /** * Sets register values. * * @param {number} opIndex * @param {number} numPrevRegisters * @param {string[]} registers */ setRegisters(opIndex, numPrevRegisters, registers) { const op = document.querySelector(`#rec-list .operation:nth-child(${opIndex + 1})`), prevRegList = op.querySelector(".register-list"); // Remove previous div if (prevRegList) prevRegList.remove(); const registerList = []; for (let i = 0; i < registers.length; i++) { registerList.push(`$R${numPrevRegisters + i} = ${escapeControlChars(Utils.escapeHtml(Utils.truncate(registers[i], 100)))}`); } const registerListEl = `
        ${registerList.join("
        ")}
        `; op.insertAdjacentHTML("beforeend", registerListEl); } /** * Adjusts the number of ingredient columns as the width of the recipe changes. */ adjustWidth() { const recList = document.getElementById("rec-list"); // Hide Chef icon on Bake button if the page is compressed const bakeIcon = document.querySelector("#bake img"); if (recList.clientWidth < 370) { // Hide Chef icon on Bake button bakeIcon.style.display = "none"; } else { bakeIcon.style.display = "inline-block"; } // Scale controls to fit pane width const controls = document.getElementById("controls"); const controlsContent = document.getElementById("controls-content"); const scale = (controls.clientWidth - 1) / controlsContent.scrollWidth; controlsContent.style.transform = `scale(${scale})`; } } export default RecipeWaiter; ================================================ FILE: src/web/waiters/SeasonalWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ /** * Waiter to handle seasonal events and easter eggs. */ class SeasonalWaiter { /** * SeasonalWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; } /** * Loads all relevant items depending on the current date. */ load() { // Konami code this.kkeys = []; window.addEventListener("keydown", this.konamiCodeListener.bind(this)); // CyberChef Challenge 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"); } /** * Listen for the Konami code sequence of keys. Turn the page upside down if they are all heard in * sequence. * #konamicode */ konamiCodeListener(e) { this.kkeys.push(e.keyCode); const konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; for (let i = 0; i < this.kkeys.length; i++) { if (this.kkeys[i] !== konami[i]) { this.kkeys = []; break; } if (i === konami.length - 1) { $("body").children().toggleClass("konami"); this.kkeys = []; } } } } export default SeasonalWaiter; ================================================ FILE: src/web/waiters/TabWaiter.mjs ================================================ /** * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ /** * Waiter to handle events related to the input and output tabs */ class TabWaiter { /** * TabWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager */ constructor(app, manager) { this.app = app; this.manager = manager; } /** * Calculates the maximum number of tabs to display * * @returns {number} */ calcMaxTabs() { let numTabs = Math.floor((document.getElementById("IO").offsetWidth - 75) / 120); numTabs = (numTabs > 1) ? numTabs : 2; return numTabs; } /** * Gets the currently active input or active tab number * * @param {string} io - Either "input" or "output" * @returns {number} - The currently active tab or -1 */ getActiveTab(io) { const activeTabs = document.getElementsByClassName(`active-${io}-tab`); if (activeTabs.length > 0) { if (!activeTabs.item(0).hasAttribute("inputNum")) return -1; const tabNum = activeTabs.item(0).getAttribute("inputNum"); return parseInt(tabNum, 10); } return -1; } /** * Gets the li element for the tab of a given input number * * @param {number} inputNum - The inputNum of the tab we're trying to get * @param {string} io - Either "input" or "output" * @returns {Element} */ getTabItem(inputNum, io) { const tabs = document.getElementById(`${io}-tabs`).children; for (let i = 0; i < tabs.length; i++) { if (parseInt(tabs.item(i).getAttribute("inputNum"), 10) === inputNum) { return tabs.item(i); } } return null; } /** * Gets a list of tab numbers for the currently displayed tabs * * @param {string} io - Either "input" or "output" * @returns {number[]} */ getTabList(io) { const nums = [], tabs = document.getElementById(`${io}-tabs`).children; for (let i = 0; i < tabs.length; i++) { nums.push(parseInt(tabs.item(i).getAttribute("inputNum"), 10)); } return nums; } /** * Creates a new tab element for the tab bar * * @param {number} inputNum - The inputNum of the new tab * @param {boolean} active - If true, sets the tab to active * @param {string} io - Either "input" or "output" * @returns {Element} */ createTabElement(inputNum, active, io) { const newTab = document.createElement("li"); newTab.setAttribute("inputNum", inputNum.toString()); if (active) newTab.classList.add(`active-${io}-tab`); const newTabContent = document.createElement("div"); newTabContent.classList.add(`${io}-tab-content`); newTabContent.innerText = `Tab ${inputNum.toString()}`; newTabContent.addEventListener("wheel", this.manager[io].scrollTab.bind(this.manager[io]), {passive: false}); newTab.appendChild(newTabContent); if (io === "input") { const newTabButton = document.createElement("button"), newTabButtonIcon = document.createElement("i"); newTabButton.type = "button"; newTabButton.className = "btn btn-primary bmd-btn-icon btn-close-tab"; newTabButtonIcon.classList.add("material-icons"); newTabButtonIcon.innerText = "clear"; newTabButton.appendChild(newTabButtonIcon); newTabButton.addEventListener("click", this.manager.input.removeTabClick.bind(this.manager.input)); newTab.appendChild(newTabButton); } return newTab; } /** * Displays the tab bar for both the input and output */ showTabBar() { document.getElementById("input-tabs-wrapper").style.display = "block"; document.getElementById("output-tabs-wrapper").style.display = "block"; document.getElementById("input-wrapper").classList.add("show-tabs"); document.getElementById("output-wrapper").classList.add("show-tabs"); document.getElementById("save-all-to-file").style.display = "inline-block"; } /** * Hides the tab bar for both the input and output */ hideTabBar() { document.getElementById("input-tabs-wrapper").style.display = "none"; document.getElementById("output-tabs-wrapper").style.display = "none"; document.getElementById("input-wrapper").classList.remove("show-tabs"); document.getElementById("output-wrapper").classList.remove("show-tabs"); document.getElementById("save-all-to-file").style.display = "none"; } /** * Redraws the tab bar with an updated list of tabs, then changes to activeTab * * @param {number[]} nums - The inputNums of the tab bar to be drawn * @param {number} activeTab - The inputNum of the activeTab * @param {boolean} tabsLeft - True if there are tabs to the left of the displayed tabs * @param {boolean} tabsRight - True if there are tabs to the right of the displayed tabs * @param {string} io - Either "input" or "output" */ refreshTabs(nums, activeTab, tabsLeft, tabsRight, io) { const tabsList = document.getElementById(`${io}-tabs`); // Remove existing tab elements for (let i = tabsList.children.length - 1; i >= 0; i--) { tabsList.children.item(i).remove(); } // Create and add new tab elements for (let i = 0; i < nums.length; i++) { const active = (nums[i] === activeTab); tabsList.appendChild(this.createTabElement(nums[i], active, io)); } // Display shadows if there are tabs left / right of the displayed tabs if (tabsLeft) { tabsList.classList.add("tabs-left"); } else { tabsList.classList.remove("tabs-left"); } if (tabsRight) { tabsList.classList.add("tabs-right"); } else { tabsList.classList.remove("tabs-right"); } // Show or hide the tab bar depending on how many tabs we have if (nums.length > 1) { this.showTabBar(); } else { this.hideTabBar(); } } /** * Changes the active tab to a different tab * * @param {number} inputNum - The inputNum of the tab to change to * @param {string} io - Either "input" or "output" * @return {boolean} - False if the tab is not currently being displayed */ changeTab(inputNum, io) { const tabsList = document.getElementById(`${io}-tabs`); let found = false; for (let i = 0; i < tabsList.children.length; i++) { const tabNum = parseInt(tabsList.children.item(i).getAttribute("inputNum"), 10); if (tabNum === inputNum) { tabsList.children.item(i).classList.add(`active-${io}-tab`); found = true; } else { tabsList.children.item(i).classList.remove(`active-${io}-tab`); } } return found; } /** * Updates the tab header to display a preview of the tab contents * * @param {number} inputNum - The inputNum of the tab to update the header of * @param {string} data - The data to display in the tab header * @param {string} io - Either "input" or "output" */ updateTabHeader(inputNum, data, io) { const tab = this.getTabItem(inputNum, io); if (tab === null) return; let headerData = `Tab ${inputNum}`; if (data.length > 0) { headerData = data.slice(0, 100); headerData = `${inputNum}: ${headerData}`; } tab.firstElementChild.innerText = headerData; } /** * Updates the tab background to display the progress of the current tab * * @param {number} inputNum - The inputNum of the tab * @param {number} progress - The current progress * @param {number} total - The total which the progress is a percent of * @param {string} io - Either "input" or "output" */ updateTabProgress(inputNum, progress, total, io) { const tabItem = this.getTabItem(inputNum, io); if (tabItem === null) return; const percentComplete = (progress / total) * 100; if (percentComplete >= 100 || progress === false) { tabItem.style.background = ""; } else { tabItem.style.background = `linear-gradient(to right, var(--title-background-colour) ${percentComplete}%, var(--primary-background-colour) ${percentComplete}%)`; } } } export default TabWaiter; ================================================ FILE: src/web/waiters/TimingWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ /** * Waiter to handle timing of the baking process. */ class TimingWaiter { /** * TimingWaiter constructor. * * @param {App} app - The main view object for CyberChef. * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; this.manager = manager; this.inputs = {}; /* Inputs example: "1": { "inputEncodingStart": 0, "inputEncodingEnd": 0, "trigger": 0 "chefWorkerTasked": 0, "bakeComplete": 0, "bakeDuration": 0, "settingOutput": 0, "outputDecodingStart": 0, "outputDecodingEnd": 0, "complete": 0 } */ } /** * Record the time for an input * * @param {string} event * @param {number} inputNum * @param {number} value */ recordTime(event, inputNum, value=Date.now()) { inputNum = inputNum.toString(); if (!Object.keys(this.inputs).includes(inputNum)) { this.inputs[inputNum] = {}; } log.debug(`Recording ${event} for input ${inputNum}`); this.inputs[inputNum][event] = value; } /** * The duration of the main stages of a bake * * @param {number} inputNum * @returns {number} */ duration(inputNum) { const input = this.inputs[inputNum.toString()]; // If this input has not been encoded yet, we cannot calculate a time if (!input || !input.trigger || !input.inputEncodingEnd || !input.inputEncodingStart) return 0; // input encoding can happen before a bake is triggered, so it is calculated separately const inputEncodingTotal = input.inputEncodingEnd - input.inputEncodingStart; let total = 0, outputDecodingTotal = 0; if (input.bakeComplete && input.bakeComplete > input.trigger) total = input.bakeComplete - input.trigger; if (input.settingOutput && input.settingOutput > input.trigger) total = input.settingOutput - input.trigger; if (input.outputDecodingStart && (input.outputDecodingStart > input.trigger) && input.outputDecodingEnd && (input.outputDecodingEnd > input.trigger)) { total = input.outputDecodingEnd - input.trigger; outputDecodingTotal = input.outputDecodingEnd - input.outputDecodingStart; } if (input.complete && input.complete > input.trigger) total = inputEncodingTotal + input.bakeDuration + outputDecodingTotal; return total; } /** * The total time for a completed bake * * @param {number} inputNum * @returns {number} */ overallDuration(inputNum) { const input = this.inputs[inputNum.toString()]; // If this input has not been encoded yet, we cannot calculate a time if (!input || !input.trigger || !input.inputEncodingEnd || !input.inputEncodingStart) return 0; // input encoding can happen before a bake is triggered, so it is calculated separately const inputEncodingTotal = input.inputEncodingEnd - input.inputEncodingStart; let total = 0; if (input.bakeComplete && input.bakeComplete > input.trigger) total = input.bakeComplete - input.trigger; if (input.settingOutput && input.settingOutput > input.trigger) total = input.settingOutput - input.trigger; if (input.outputDecodingStart && input.outputDecodingStart > input.trigger) total = input.outputDecodingStart - input.trigger; if (input.outputDecodingEnd && input.outputDecodingEnd > input.trigger) total = input.outputDecodingEnd - input.trigger; if (input.complete && input.complete > input.trigger) total = input.complete - input.trigger; return total + inputEncodingTotal; } /** * Prints out the time between stages * * @param {number} inputNum * @returns {string} */ printStages(inputNum) { const input = this.inputs[inputNum.toString()]; if (!input || !input.trigger) return ""; const total = this.overallDuration(inputNum), inputEncoding = input.inputEncodingEnd - input.inputEncodingStart, outputDecoding = input.outputDecodingEnd - input.outputDecodingStart, overhead = total - inputEncoding - outputDecoding - input.bakeDuration; return `Input encoding: ${inputEncoding}ms Recipe duration: ${input.bakeDuration}ms Output decoding: ${outputDecoding}ms Threading overhead: ${overhead}ms`; } /** * Logs every interval * * @param {number} inputNum */ logAllTimes(inputNum) { const input = this.inputs[inputNum.toString()]; if (!input || !input.trigger) return; try { log.debug(`Trigger: ${input.trigger} inputEncodingStart: ${input.inputEncodingStart} | ${input.inputEncodingStart - input.trigger}ms since trigger inputEncodingEnd: ${input.inputEncodingEnd} | ${input.inputEncodingEnd - input.inputEncodingStart}ms input encoding time chefWorkerTasked: ${input.chefWorkerTasked} | ${input.chefWorkerTasked - input.trigger}ms since trigger bakeDuration: | ${input.bakeDuration}ms duration in worker bakeComplete: ${input.bakeComplete} | ${input.bakeComplete - input.chefWorkerTasked}ms since worker tasked settingOutput: ${input.settingOutput} | ${input.settingOutput - input.bakeComplete}ms since worker finished outputDecodingStart: ${input.outputDecodingStart} | ${input.outputDecodingStart - input.settingOutput}ms since output set outputDecodingEnd: ${input.outputDecodingEnd} | ${input.outputDecodingEnd - input.outputDecodingStart}ms output encoding time complete: ${input.complete} | ${input.complete - input.outputDecodingEnd}ms since output decoded Total: | ${input.complete - input.trigger}ms since trigger`); } catch (err) {} } } export default TimingWaiter; ================================================ FILE: src/web/waiters/WindowWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import { debounce } from "../../core/Utils.mjs"; /** * Waiter to handle events related to the window object. */ class WindowWaiter { /** * WindowWaiter constructor. * * @param {App} app - The main view object for CyberChef. */ constructor(app) { this.app = app; } /** * Handler for window resize events. * Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause * continuous resetting). */ windowResize() { debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])(); } /** * Handler for window blur events. * Saves the current time so that we can calculate how long the window was unfocused for when * focus is returned. */ windowBlur() { this.windowBlurTime = Date.now(); } /** * Handler for window focus events. * * When a browser tab is unfocused and the browser has to run lots of dynamic content in other * tabs, it swaps out the memory for that tab. * If the CyberChef tab has been unfocused for more than a minute, we run a silent bake which will * force the browser to load and cache all the relevant JavaScript code needed to do a real bake. * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for * a long time and the browser has swapped out all its memory. */ windowFocus() { const unfocusedTime = Date.now() - this.windowBlurTime; if (unfocusedTime > 60000) { this.app.silentBake(); } } } export default WindowWaiter; ================================================ FILE: src/web/waiters/WorkerWaiter.mjs ================================================ /** * @author n1474335 [n1474335@gmail.com] * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import ChefWorker from "worker-loader?inline=no-fallback!../../core/ChefWorker.js"; import DishWorker from "worker-loader?inline=no-fallback!../workers/DishWorker.mjs"; import { debounce } from "../../core/Utils.mjs"; /** * Waiter to handle conversations with the ChefWorker */ class WorkerWaiter { /** * WorkerWaiter constructor * * @param {App} app - The main view object for CyberChef * @param {Manager} manager - The CyberChef event manager */ constructor(app, manager) { this.app = app; this.manager = manager; this.loaded = false; this.chefWorkers = []; this.inputs = []; this.inputNums = []; this.totalOutputs = 0; this.loadingOutputs = 0; this.bakeId = 0; this.callbacks = {}; this.callbackID = 0; this.maxWorkers = 1; if (navigator.hardwareConcurrency !== undefined && navigator.hardwareConcurrency > 1) { this.maxWorkers = navigator.hardwareConcurrency - 1; } // Store dishWorker action (getDishAs or getDishTitle) this.dishWorker = { worker: null, currentAction: "" }; this.dishWorkerQueue = []; } /** * Terminates any existing ChefWorkers and sets up a new worker */ setupChefWorker() { for (let i = this.chefWorkers.length - 1; i >= 0; i--) { this.removeChefWorker(this.chefWorkers[i]); } this.addChefWorker(); this.setupDishWorker(); } /** * Sets up a DishWorker to be used for performing Dish operations */ setupDishWorker() { if (this.dishWorker.worker !== null) { this.dishWorker.worker.terminate(); this.dishWorker.currentAction = ""; } log.debug("Adding new DishWorker"); this.dishWorker.worker = new DishWorker(); this.dishWorker.worker.addEventListener("message", this.handleDishMessage.bind(this)); this.dishWorker.worker.postMessage({ action: "setLogLevel", data: log.getLevel() }); if (this.dishWorkerQueue.length > 0) { this.postDishMessage(this.dishWorkerQueue.splice(0, 1)[0]); } } /** * Adds a new ChefWorker * * @returns {number} The index of the created worker */ addChefWorker() { if (this.chefWorkers.length === this.maxWorkers) { // Can't create any more workers return -1; } log.debug(`Adding new ChefWorker (${this.chefWorkers.length + 1}/${this.maxWorkers})`); // Create a new ChefWorker and send it the docURL const newWorker = new ChefWorker(); newWorker.addEventListener("message", this.handleChefMessage.bind(this)); newWorker.postMessage({ action: "setLogPrefix", data: "ChefWorker" }); newWorker.postMessage({ action: "setLogLevel", data: log.getLevel() }); let docURL = document.location.href.split(/[#?]/)[0]; const index = docURL.lastIndexOf("/"); if (index > 0) { docURL = docURL.substring(0, index); } newWorker.postMessage({"action": "docURL", "data": docURL}); // Store the worker, whether or not it's active, and the inputNum as an object const newWorkerObj = { worker: newWorker, active: false, inputNum: -1 }; this.chefWorkers.push(newWorkerObj); return this.chefWorkers.indexOf(newWorkerObj); } /** * Gets an inactive ChefWorker to be used for baking * * @param {boolean} [setActive=true] - If true, set the worker status to active * @returns {number} - The index of the ChefWorker */ getInactiveChefWorker(setActive=true) { for (let i = 0; i < this.chefWorkers.length; i++) { if (!this.chefWorkers[i].active) { this.chefWorkers[i].active = setActive; return i; } } return -1; } /** * Removes a ChefWorker * * @param {Object} workerObj */ removeChefWorker(workerObj) { const index = this.chefWorkers.indexOf(workerObj); if (index === -1) { return; } if (this.chefWorkers.length > 1 || this.chefWorkers[index].active) { log.debug(`Removing ChefWorker at index ${index}`); this.chefWorkers[index].worker.terminate(); this.chefWorkers.splice(index, 1); } // There should always be a ChefWorker loaded if (this.chefWorkers.length === 0) { this.addChefWorker(); } } /** * Finds and returns the object for the ChefWorker of a given inputNum * * @param {number} inputNum */ getChefWorker(inputNum) { for (let i = 0; i < this.chefWorkers.length; i++) { if (this.chefWorkers[i].inputNum === inputNum) { return this.chefWorkers[i]; } } } /** * Handler for messages sent back by the ChefWorkers * * @param {MessageEvent} e */ handleChefMessage(e) { const r = e.data; let inputNum = 0; log.debug(`Receiving '${r.action}' from ChefWorker.`); if (Object.prototype.hasOwnProperty.call(r.data, "inputNum")) { inputNum = r.data.inputNum; } const currentWorker = this.getChefWorker(inputNum); switch (r.action) { case "bakeComplete": log.debug(`Bake ${inputNum} complete.`); this.manager.timing.recordTime("bakeComplete", inputNum); this.manager.timing.recordTime("bakeDuration", inputNum, r.data.duration); if (r.data.error) { this.app.handleError(r.data.error); this.manager.output.updateOutputError(r.data.error, inputNum, r.data.progress); } else { this.updateOutput(r.data, r.data.inputNum, r.data.bakeId, r.data.progress); } this.app.progress = r.data.progress; if (r.data.progress === this.recipeConfig.length) { this.step = false; } this.workerFinished(currentWorker); break; case "bakeError": this.app.handleError(r.data.error); this.manager.output.updateOutputError(r.data.error, inputNum, r.data.progress); this.app.progress = r.data.progress; this.workerFinished(currentWorker); break; case "dishReturned": this.callbacks[r.data.id](r.data); break; case "silentBakeComplete": break; case "workerLoaded": this.app.workerLoaded = true; log.debug("ChefWorker loaded"); if (!this.loaded) { this.app.loaded(); this.loaded = true; } else { this.bakeNextInput(this.getInactiveChefWorker(false)); } break; case "statusMessage": this.manager.output.updateOutputMessage(r.data.message, r.data.inputNum, true); break; case "progressMessage": this.manager.output.updateOutputProgress(r.data.progress, r.data.total, r.data.inputNum); break; case "optionUpdate": log.debug(`Setting ${r.data.option} to ${r.data.value}`); this.app.options[r.data.option] = r.data.value; break; case "setRegisters": this.manager.recipe.setRegisters(r.data.opIndex, r.data.numPrevRegisters, r.data.registers); break; case "highlightsCalculated": this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction); break; default: log.error("Unrecognised message from ChefWorker", e); break; } } /** * Update the value of an output * * @param {Object} data * @param {number} inputNum * @param {number} bakeId * @param {number} progress */ updateOutput(data, inputNum, bakeId, progress) { this.manager.output.updateOutputBakeId(bakeId, inputNum); if (progress === this.recipeConfig.length) { progress = false; } this.manager.output.updateOutputProgress(progress, this.recipeConfig.length, inputNum); this.manager.output.updateOutputValue(data, inputNum, false); if (progress !== false) { this.manager.output.updateOutputStatus("error", inputNum); if (inputNum === this.manager.tabs.getActiveTab("input")) { this.manager.recipe.updateBreakpointIndicator(progress); } } else { this.manager.output.updateOutputStatus("baked", inputNum); } } /** * Updates the UI to show if baking is in progress or not. * * @param {boolean} bakingStatus */ setBakingStatus(bakingStatus) { this.app.baking = bakingStatus; debounce(this.manager.controls.toggleBakeButtonFunction, 20, "toggleBakeButton", this, [bakingStatus ? "cancel" : "bake"])(); if (bakingStatus) this.manager.output.hideMagicButton(); } /** * Get the progress of the ChefWorkers */ getBakeProgress() { const pendingInputs = this.inputNums.length + this.loadingOutputs + this.inputs.length; let bakingInputs = 0; for (let i = 0; i < this.chefWorkers.length; i++) { if (this.chefWorkers[i].active) { bakingInputs++; } } const total = this.totalOutputs; const bakedInputs = total - pendingInputs - bakingInputs; return { total: total, pending: pendingInputs, baking: bakingInputs, baked: bakedInputs }; } /** * Cancels the current bake making it possible to autobake again */ cancelBakeForAutoBake() { if (this.totalOutputs > 1) { this.cancelBake(); } else { // In this case the UI changes can be skipped for (let i = this.chefWorkers.length - 1; i >= 0; i--) { if (this.chefWorkers[i].active) { this.removeChefWorker(this.chefWorkers[i]); } } this.inputs = []; this.inputNums = []; this.totalOutputs = 0; this.loadingOutputs = 0; } } /** * Cancels the current bake by terminating and removing all ChefWorkers * * @param {boolean} [silent=false] - If true, don't set the output * @param {boolean} [killAll=false] - If true, kills all chefWorkers regardless of status */ cancelBake(silent=false, killAll=false) { const deactiveOutputs = new Set(); for (let i = this.chefWorkers.length - 1; i >= 0; i--) { if (this.chefWorkers[i].active || killAll) { const inputNum = this.chefWorkers[i].inputNum; this.removeChefWorker(this.chefWorkers[i]); deactiveOutputs.add(inputNum); } } this.setBakingStatus(false); this.inputs.forEach(input => { deactiveOutputs.add(input.inputNum); }); this.inputNums.forEach(inputNum => { deactiveOutputs.add(inputNum); }); deactiveOutputs.forEach(num => { this.manager.output.updateOutputStatus("inactive", num); }); const tabList = this.manager.tabs.getTabList("output"); tabList.forEach(tab => { this.manager.tabs.getTabItem(tab, "output").style.background = ""; }); this.inputs = []; this.inputNums = []; this.totalOutputs = 0; this.loadingOutputs = 0; if (!silent) this.manager.output.set(this.manager.tabs.getActiveTab("output")); } /** * Handle a worker completing baking * * @param {object} workerObj - Object containing the worker information * @param {ChefWorker} workerObj.worker - The actual worker object * @param {number} workerObj.inputNum - The inputNum of the input being baked by the worker * @param {boolean} workerObj.active - If true, the worker is currently baking an input */ workerFinished(workerObj) { const workerIdx = this.chefWorkers.indexOf(workerObj); this.chefWorkers[workerIdx].active = false; if (this.inputs.length > 0) { this.bakeNextInput(workerIdx); } else if (this.inputNums.length === 0 && this.loadingOutputs === 0) { // The ChefWorker is no longer needed log.debug("No more inputs to bake."); const progress = this.getBakeProgress(); if (progress.total === progress.baked) { this.bakingComplete(); } } } /** * Handler for completed bakes */ bakingComplete() { this.setBakingStatus(false); let duration = Date.now() - this.bakeStartTime; duration = duration.toLocaleString() + "ms"; const progress = this.getBakeProgress(); if (progress.total > 1) { let width = progress.total.toLocaleString().length; if (duration.length > width) { width = duration.length; } width = width < 2 ? 2 : width; const totalStr = progress.total.toLocaleString().padStart(width, " ").replace(/ /g, " "); const durationStr = duration.padStart(width, " ").replace(/ /g, " "); const inputNums = Object.keys(this.manager.output.outputs); let avgTime = 0, numOutputs = 0; for (let i = 0; i < inputNums.length; i++) { const output = this.manager.output.outputs[inputNums[i]]; if (output.status === "baked") { numOutputs++; avgTime += output.data.duration; } } avgTime = Math.round(avgTime / numOutputs).toLocaleString() + "ms"; avgTime = avgTime.padStart(width, " ").replace(/ /g, " "); const msg = `total: ${totalStr}
        time: ${durationStr}
        average: ${avgTime}`; const bakeInfo = document.getElementById("bake-info"); bakeInfo.innerHTML = msg; bakeInfo.style.display = ""; } else { document.getElementById("bake-info").style.display = "none"; } document.getElementById("bake").style.background = ""; this.totalOutputs = 0; // Reset for next time log.debug("--- Bake complete ---"); } /** * Bakes the next input and tells the inputWorker to load the next input * * @param {number} workerIdx - The index of the worker to bake with */ bakeNextInput(workerIdx) { if (this.inputs.length === 0) return; if (workerIdx === -1) return; if (!this.chefWorkers[workerIdx]) return; this.chefWorkers[workerIdx].active = true; const nextInput = this.inputs.splice(0, 1)[0]; if (typeof nextInput.inputNum === "string") nextInput.inputNum = parseInt(nextInput.inputNum, 10); log.debug(`Baking input ${nextInput.inputNum}.`); this.manager.output.updateOutputMessage(`Baking input ${nextInput.inputNum}...`, nextInput.inputNum, false); this.manager.output.updateOutputStatus("baking", nextInput.inputNum); this.chefWorkers[workerIdx].inputNum = nextInput.inputNum; const input = nextInput.input, recipeConfig = this.recipeConfig; if (this.step) { // Remove all breakpoints from the recipe up to progress if (nextInput.progress !== false) { for (let i = 0; i < nextInput.progress; i++) { if ("breakpoint" in recipeConfig[i]) { delete recipeConfig[i].breakpoint; } } } // Set a breakpoint at the next operation so we stop baking there if (recipeConfig[this.app.progress]) recipeConfig[this.app.progress].breakpoint = true; } let transferable; if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) { transferable = [input]; } this.manager.timing.recordTime("chefWorkerTasked", nextInput.inputNum); this.chefWorkers[workerIdx].worker.postMessage({ action: "bake", data: { input: input, recipeConfig: recipeConfig, options: this.options, inputNum: nextInput.inputNum, bakeId: this.bakeId } }, transferable); if (this.inputNums.length > 0) { this.manager.input.inputWorker.postMessage({ action: "bakeNext", data: { inputNum: this.inputNums.splice(0, 1)[0], bakeId: this.bakeId } }); this.loadingOutputs++; } } /** * Bakes the current input using the current recipe. * * @param {Object[]} recipeConfig * @param {Object} options * @param {number} progress * @param {boolean} step */ bake(recipeConfig, options, progress, step) { this.setBakingStatus(true); this.manager.recipe.updateBreakpointIndicator(false); this.bakeStartTime = Date.now(); this.bakeId++; this.recipeConfig = recipeConfig; this.options = options; this.progress = progress; this.step = step; this.displayProgress(); } /** * Queues an input ready to be baked * * @param {object} inputData * @param {string | ArrayBuffer} inputData.input * @param {number} inputData.inputNum * @param {number} inputData.bakeId */ queueInput(inputData) { this.loadingOutputs--; if (this.app.baking && inputData.bakeId === this.bakeId) { this.inputs.push(inputData); this.bakeNextInput(this.getInactiveChefWorker(true)); } } /** * Handles if an error is thrown by QueueInput * * @param {object} inputData * @param {number} inputData.inputNum * @param {number} inputData.bakeId */ queueInputError(inputData) { this.loadingOutputs--; if (this.app.baking && inputData.bakeId === this.bakeId) { this.manager.output.updateOutputError("Error queueing the input for a bake.", inputData.inputNum, 0); if (this.inputNums.length === 0) return; // Load the next input this.manager.input.inputWorker.postMessage({ action: "bakeNext", data: { inputNum: this.inputNums.splice(0, 1)[0], bakeId: this.bakeId } }); this.loadingOutputs++; } } /** * Queues a list of inputNums to be baked by ChefWorkers, and begins baking * * @param {object} inputData * @param {number[]} inputData.nums - The inputNums to be queued for baking * @param {boolean} inputData.step - If true, only execute the next operation in the recipe * @param {number} inputData.progress - The current progress through the recipe. Used when stepping */ async bakeInputs(inputData) { log.debug(`Baking input list [${inputData.nums.join(",")}]`); return await new Promise(resolve => { if (this.app.baking) return; const inputNums = inputData.nums.filter(n => n > 0); const step = inputData.step; // Use cancelBake to clear out the inputs this.cancelBake(true, false); this.inputNums = inputNums; this.totalOutputs = inputNums.length; this.app.progress = inputData.progress; let inactiveWorkers = 0; for (let i = 0; i < this.chefWorkers.length; i++) { if (!this.chefWorkers[i].active) { inactiveWorkers++; } } for (let i = 0; i < inputNums.length - inactiveWorkers; i++) { if (this.addChefWorker() === -1) break; } this.app.bake(step); for (let i = 0; i < this.inputNums.length; i++) { this.manager.output.updateOutputMessage(`Input ${inputNums[i]} has not been baked yet.`, inputNums[i], false); this.manager.output.updateOutputStatus("pending", inputNums[i]); } let numBakes = this.chefWorkers.length; if (this.inputNums.length < numBakes) { numBakes = this.inputNums.length; } for (let i = 0; i < numBakes; i++) { this.manager.timing.recordTime("trigger", this.inputNums[0]); this.manager.input.inputWorker.postMessage({ action: "bakeNext", data: { inputNum: this.inputNums.splice(0, 1)[0], bakeId: this.bakeId } }); this.loadingOutputs++; } if (numBakes === 0) this.bakingComplete(); }); } /** * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant * JavaScript code needed to do a real bake. * * @param {Object[]} [recipeConfig] */ silentBake(recipeConfig) { // If there aren't any active ChefWorkers, try to add one let workerId = this.getInactiveChefWorker(); if (workerId === -1) { workerId = this.addChefWorker(); } if (workerId === -1) return; this.chefWorkers[workerId].worker.postMessage({ action: "silentBake", data: { recipeConfig: recipeConfig } }); } /** * Handler for messages sent back from DishWorker * * @param {MessageEvent} e */ handleDishMessage(e) { const r = e.data; log.debug(`Receiving '${r.action}' from DishWorker`); switch (r.action) { case "dishReturned": this.dishWorker.currentAction = ""; this.callbacks[r.data.id](r.data); if (this.dishWorkerQueue.length > 0) { this.postDishMessage(this.dishWorkerQueue.splice(0, 1)[0]); } break; default: log.error("Unrecognised message from DishWorker", e); break; } } /** * Asks the DishWorker to return the dish as the specified type * * @param {Dish} dish * @param {string} type * @param {Function} callback */ getDishAs(dish, type, callback) { const id = this.callbackID++; this.callbacks[id] = callback; if (this.dishWorker.worker === null) this.setupDishWorker(); this.postDishMessage({ action: "getDishAs", data: { dish: dish, type: type, id: id } }); } /** * Asks the DishWorker to get the title of the dish * * @param {Dish} dish * @param {number} maxLength * @param {Function} callback * @returns {string} */ getDishTitle(dish, maxLength, callback) { const id = this.callbackID++; this.callbacks[id] = callback; if (this.dishWorker.worker === null) this.setupDishWorker(); this.postDishMessage({ action: "getDishTitle", data: { dish: dish, maxLength: maxLength, id: id } }); } /** * Asks the DishWorker to translate a buffer into a specific character encoding * * @param {ArrayBuffer} buffer * @param {number} encoding * @param {Function} callback * @returns {string} */ bufferToStr(buffer, encoding, callback) { const id = this.callbackID++; this.callbacks[id] = callback; if (this.dishWorker.worker === null) this.setupDishWorker(); this.postDishMessage({ action: "bufferToStr", data: { buffer: buffer, encoding: encoding, id: id } }); } /** * Queues a message to be sent to the dishWorker * * @param {object} message * @param {string} message.action * @param {object} message.data * @param {Dish} message.data.dish * @param {number} message.data.id */ queueDishMessage(message) { if (message.action === "getDishAs") { this.dishWorkerQueue = [message].concat(this.dishWorkerQueue); } else { this.dishWorkerQueue.push(message); } } /** * Sends a message to the DishWorker * * @param {object} message * @param {string} message.action * @param {object} message.data */ postDishMessage(message) { if (this.dishWorker.currentAction !== "") { this.queueDishMessage(message); } else { this.dishWorker.currentAction = message.action; this.dishWorker.worker.postMessage(message); } } /** * Sets the console log level in the workers. */ setLogLevel() { this.chefWorkers.forEach(cw => { cw.worker.postMessage({ action: "setLogLevel", data: log.getLevel() }); }); if (!this.dishWorker.worker) return; this.dishWorker.worker.postMessage({ action: "setLogLevel", data: log.getLevel() }); } /** * Display the bake progress in the output bar and bake button */ displayProgress() { const progress = this.getBakeProgress(); if (progress.total === progress.baked) return; const percentComplete = ((progress.pending + progress.baking) / progress.total) * 100; const bakeButton = document.getElementById("bake"); if (this.app.baking) { if (percentComplete < 100) { bakeButton.style.background = `linear-gradient(to left, #fea79a ${percentComplete}%, #f44336 ${percentComplete}%)`; } else { bakeButton.style.background = ""; } } else { // not baking bakeButton.style.background = ""; } const bakeInfo = document.getElementById("bake-info"); if (progress.total > 1) { let width = progress.total.toLocaleString().length; width = width < 2 ? 2 : width; const totalStr = progress.total.toLocaleString().padStart(width, " ").replace(/ /g, " "); const bakedStr = progress.baked.toLocaleString().padStart(width, " ").replace(/ /g, " "); const pendingStr = progress.pending.toLocaleString().padStart(width, " ").replace(/ /g, " "); const bakingStr = progress.baking.toLocaleString().padStart(width, " ").replace(/ /g, " "); let msg = "total: " + totalStr; msg += "
        baked: " + bakedStr; if (progress.pending > 0) { msg += "
        pending: " + pendingStr; } else if (progress.baking > 0) { msg += "
        baking: " + bakingStr; } bakeInfo.innerHTML = msg; bakeInfo.style.display = ""; } else { bakeInfo.style.display = "none"; } if (progress.total !== progress.baked) { setTimeout(function() { this.displayProgress(); }.bind(this), 100); } } /** * Asks the ChefWorker to calculate highlight offsets if possible. * * @param {Object[]} recipeConfig * @param {string} direction * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. */ highlight(recipeConfig, direction, pos) { let workerIdx = this.getInactiveChefWorker(false); if (workerIdx === -1) { workerIdx = this.addChefWorker(); } if (workerIdx === -1) return; this.chefWorkers[workerIdx].worker.postMessage({ action: "highlight", data: { recipeConfig: recipeConfig, direction: direction, pos: pos } }); } } export default WorkerWaiter; ================================================ FILE: src/web/workers/DishWorker.mjs ================================================ /** * Web worker to handle dish conversion operations. * * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Dish from "../../core/Dish.mjs"; import DishError from "../../core/errors/DishError.mjs"; import { CHR_ENC_SIMPLE_REVERSE_LOOKUP } from "../../core/lib/ChrEnc.mjs"; import Utils from "../../core/Utils.mjs"; import cptable from "codepage"; import loglevelMessagePrefix from "loglevel-message-prefix"; loglevelMessagePrefix(log, { prefixes: [], staticPrefixes: ["DishWorker"] }); self.addEventListener("message", function(e) { // Handle message from the main thread const r = e.data; log.debug(`Receiving command '${r.action}'`); switch (r.action) { case "getDishAs": getDishAs(r.data); break; case "getDishTitle": getDishTitle(r.data); break; case "bufferToStr": bufferToStr(r.data); break; case "setLogLevel": log.setLevel(r.data, false); break; default: log.error(`Unknown action: '${r.action}'`); } }); /** * Translates the dish to a given type * * @param {object} data * @param {Dish} data.dish * @param {string} data.type * @param {number} data.id */ async function getDishAs(data) { const newDish = new Dish(data.dish), value = await newDish.get(data.type), transferable = (data.type === "ArrayBuffer") ? [value] : undefined; self.postMessage({ action: "dishReturned", data: { value: value, id: data.id } }, transferable); } /** * Gets the title of the given dish * * @param {object} data * @param {Dish} data.dish * @param {number} data.id * @param {number} data.maxLength */ async function getDishTitle(data) { const newDish = new Dish(data.dish), title = await newDish.getTitle(data.maxLength); self.postMessage({ action: "dishReturned", data: { value: title, id: data.id } }); } /** * Translates a buffer to a string using a specified encoding * * @param {object} data * @param {ArrayBuffer} data.buffer * @param {number} data.id * @param {number} data.encoding */ async function bufferToStr(data) { let str; if (data.encoding === 0) { str = Utils.arrayBufferToStr(data.buffer); } else { try { str = cptable.utils.decode(data.encoding, new Uint8Array(data.buffer)); } catch (err) { str = new DishError(`Error decoding buffer with encoding ${CHR_ENC_SIMPLE_REVERSE_LOOKUP[data.encoding]}: ${err.message}`).toString(); } } self.postMessage({ action: "dishReturned", data: { value: str, id: data.id } }); } ================================================ FILE: src/web/workers/InputWorker.mjs ================================================ /** * Web worker to handle the inputs. * Handles storage, modification and retrieval of the inputs. * * @author j433866 [j433866@gmail.com] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Utils from "../../core/Utils.mjs"; import loglevelMessagePrefix from "loglevel-message-prefix"; loglevelMessagePrefix(log, { prefixes: [], staticPrefixes: ["InputWorker"] }); // Default max values // These will be correctly calculated automatically self.maxWorkers = 4; self.maxTabs = 1; /** * Dictionary of inputs keyed on the inputNum * Each entry is an object with the following type: * @typedef {Object} Input * @property {string} type * @property {ArrayBuffer} buffer * @property {string} stringSample * @property {Object} file * @property {string} file.name * @property {number} file.size * @property {string} file.type * @property {string} status * @property {number} progress * @property {number} encoding * @property {string} eolSequence */ self.inputs = {}; self.loaderWorkers = []; self.pendingFiles = []; self.currentInputNum = 1; self.numInputs = 0; self.pendingInputs = 0; self.loadingInputs = 0; /** * Respond to message from parent thread. * * @param {MessageEvent} e */ self.addEventListener("message", function(e) { const r = e.data; if (!("action" in r)) { log.error("No action"); return; } log.debug(`Receiving command '${r.action}'`); switch (r.action) { case "loadUIFiles": self.loadFiles(r.data); break; case "loaderWorkerReady": self.loaderWorkerReady(r.data); break; case "updateMaxWorkers": self.maxWorkers = r.data; break; case "updateMaxTabs": self.updateMaxTabs(r.data.maxTabs, r.data.activeTab); break; case "updateInputValue": self.updateInputValue(r.data); break; case "updateInputProgress": self.updateInputProgress(r.data); break; case "bakeAll": self.bakeAllInputs(); break; case "bakeNext": self.bakeInput(r.data.inputNum, r.data.bakeId); break; case "getLoadProgress": self.getLoadProgress(r.data); break; case "setInput": self.setInput(r.data); break; case "setLogLevel": log.setLevel(r.data, false); break; case "addInput": self.addInput(r.data, "userinput"); break; case "refreshTabs": self.refreshTabs(r.data.inputNum, r.data.direction); break; case "removeInput": self.removeInput(r.data); break; case "changeTabRight": self.changeTabRight(r.data.activeTab); break; case "changeTabLeft": self.changeTabLeft(r.data.activeTab); break; case "autobake": self.autoBake(r.data.activeTab, 0, false); break; case "filterTabs": self.filterTabs(r.data); break; case "loaderWorkerMessage": self.handleLoaderMessage(r.data); break; case "updateTabHeader": self.updateTabHeader(r.data); break; case "step": self.autoBake(r.data.activeTab, r.data.progress, true); break; case "getInput": self.getInput(r.data); break; case "getInputNums": self.getInputNums(r.data); break; default: log.error(`Unknown action '${r.action}'.`); } }); /** * Gets the load progress of the input files, and the * load progress for the input given in inputNum * * @param {number} inputNum - The input to get the file loading progress for */ self.getLoadProgress = function(inputNum) { const total = self.numInputs; const pending = self.pendingFiles.length; const loading = self.loadingInputs; const loaded = total - pending - loading; self.postMessage({ action: "loadingInfo", data: { pending: pending, loading: loading, loaded: loaded, total: total, activeProgress: { inputNum: inputNum, progress: self.getInputProgress(inputNum) } } }); }; /** * Fired when an autobake is initiated. * Queues the active input and sends a bake command. * * @param {number} inputNum - The input to be baked * @param {number} progress - The current progress of the bake through the recipe * @param {boolean} [step=false] - Set to true if we should only execute one operation instead of the * whole recipe */ self.autoBake = function(inputNum, progress, step=false) { const input = self.inputs[inputNum]; if (input) { self.postMessage({ action: "bakeInputs", data: { nums: [parseInt(inputNum, 10)], step: step, progress: progress } }); } }; /** * Fired when we want to bake all inputs (bake button clicked) * Sends a list of inputNums to the workerwaiter */ self.bakeAllInputs = function() { const inputNums = Object.keys(self.inputs); const nums = inputNums .filter(n => self.inputs[n].status === "loaded") .map(n => parseInt(n, 10)); self.postMessage({ action: "bakeInputs", data: { nums: nums, step: false, progress: 0 } }); }; /** * Gets the data for the provided inputNum and sends it to the WorkerWaiter * * @param {number} inputNum * @param {number} bakeId */ self.bakeInput = function(inputNum, bakeId) { const inputObj = self.inputs[inputNum]; if (inputObj === null || inputObj === undefined || inputObj.status !== "loaded") { self.postMessage({ action: "queueInputError", data: { inputNum: inputNum, bakeId: bakeId } }); return; } self.postMessage({ action: "queueInput", data: { input: inputObj.buffer, inputNum: inputNum, bakeId: bakeId } }); }; /** * Gets the stored value or object for a specific inputNum and sends it to the inputWaiter. * * @param {object} inputData - Object containing data about the input to retrieve * @param {number} inputData.inputNum - The inputNum of the input to get * @param {boolean} inputData.getObj - If true, returns the entire input object instead of just the value * @param {number} inputData.id - The callback ID for the callback to run when returned to the inputWaiter */ self.getInput = function(inputData) { const input = self.inputs[inputData.inputNum]; self.postMessage({ action: "getInput", data: { data: inputData.getObj ? input : input.buffer, id: inputData.id } }); }; /** * Gets a list of the stored inputNums, along with the minimum and maximum * * @param {number} id - The callback ID to be executed when returned to the inputWaiter */ self.getInputNums = function(id) { const inputNums = Object.keys(self.inputs), min = self.getSmallestInputNum(inputNums), max = self.getLargestInputNum(inputNums); self.postMessage({ action: "getInputNums", data: { inputNums: inputNums, min: min, max: max, id: id } }); }; /** * Gets the load progress for a specific inputNum * * @param {number} inputNum - The input we want to get the progress of * @returns {number | string} - Returns "error" if there was a load error */ self.getInputProgress = function(inputNum) { const inputObj = self.inputs[inputNum]; if (!inputObj) return; if (inputObj.status === "error") { return "error"; } return inputObj.progress; }; /** * Gets the largest inputNum of all the inputs * * @param {string[]} inputNums - The numbers to find the largest of * @returns {number} */ self.getLargestInputNum = function(inputNums) { return inputNums.reduce((acc, val) => { val = parseInt(val, 10); return val > acc ? val : acc; }, -1); }; /** * Gets the smallest inputNum of all the inputs * * @param {string[]} inputNums - The numbers to find the smallest of * @returns {number} */ self.getSmallestInputNum = function(inputNums) { const min = inputNums.reduce((acc, val) => { val = parseInt(val, 10); return val < acc ? val : acc; }, Number.MAX_SAFE_INTEGER); // Assume we don't have this many tabs! if (min === Number.MAX_SAFE_INTEGER) return -1; return min; }; /** * Gets the next smallest inputNum * * @param {number} inputNum - The current input number * @returns {number} */ self.getPreviousInputNum = function(inputNum) { const inputNums = Object.keys(self.inputs); if (inputNums.length === 0) return -1; return inputNums.reduce((acc, val) => { val = parseInt(val, 10); return (val < inputNum && val > acc) ? val : acc; }, self.getSmallestInputNum(inputNums)); }; /** * Gets the next largest inputNum * * @param {number} inputNum - The current input number * @returns {number} */ self.getNextInputNum = function(inputNum) { const inputNums = Object.keys(self.inputs); return inputNums.reduce((acc, val) => { val = parseInt(val, 10); return (val > inputNum && val < acc) ? val : acc; }, self.getLargestInputNum(inputNums)); }; /** * Gets a list of inputNums starting from the provided inputNum. * If direction is "left", gets the inputNums higher than the provided number. * If direction is "right", gets the inputNums lower than the provided number. * @param {number} inputNum - The inputNum we want to get the neighbours of * @param {string} direction - Either "left" or "right". Determines which direction we search for nearby numbers in * @returns {number[]} */ self.getNearbyNums = function(inputNum, direction) { const nums = []; for (let i = 0; i < self.maxTabs; i++) { let newNum; if (i === 0 && self.inputs[inputNum] !== undefined) { newNum = inputNum; } else { switch (direction) { case "left": newNum = self.getNextInputNum(nums[i - 1]); if (newNum === nums[i - 1]) { direction = "right"; newNum = self.getPreviousInputNum(nums[0]); } break; case "right": newNum = self.getPreviousInputNum(nums[i - 1]); if (newNum === nums[i - 1]) { direction = "left"; newNum = self.getNextInputNum(nums[0]); } } } if (!nums.includes(newNum) && (newNum > 0)) { nums.push(newNum); } } nums.sort(function(a, b) { return a - b; }); return nums; }; /** * Gets the data to display in the tab header for an input, and * posts it back to the inputWaiter * * @param {number} inputNum - The inputNum of the tab header */ self.updateTabHeader = function(inputNum) { const input = self.inputs[inputNum]; if (!input) return; let header = input.type === "file" ? input.file.name : input.stringSample; header = header.slice(0, 100).replace(/[\n\r\u2028\u2029]/g, ""); self.postMessage({ action: "updateTabHeader", data: { inputNum: inputNum, input: header } }); }; /** * Gets the input for a specific inputNum, and posts it to the inputWaiter * so that it can be displayed in the input area * * @param {object} inputData * @param {number} inputData.inputNum - The input to get the data for * @param {boolean} inputData.silent - If false, the manager statechange event will be fired */ self.setInput = function(inputData) { const {inputNum, silent} = inputData; const input = self.inputs[inputNum]; if (!input) return; self.postMessage({ action: "setInput", data: { inputNum: inputNum, inputObj: input, silent: silent } }); self.updateTabHeader(inputNum); }; /** * Gets the nearby inputNums to the provided number, and posts them * to the inputWaiter to be displayed on the page. * * @param {number} inputNum - The inputNum to find the nearby numbers for * @param {string} direction - The direction to search for inputNums in. Either "left" or "right" */ self.refreshTabs = function(inputNum, direction) { const nums = self.getNearbyNums(inputNum, direction), inputNums = Object.keys(self.inputs), tabsLeft = (self.getSmallestInputNum(inputNums) !== nums[0] && nums.length > 0), tabsRight = (self.getLargestInputNum(inputNums) !== nums[nums.length - 1] && nums.length > 0); self.postMessage({ action: "refreshTabs", data: { nums: nums, activeTab: (nums.includes(inputNum)) ? inputNum : self.getNextInputNum(inputNum), tabsLeft: tabsLeft, tabsRight: tabsRight } }); // Update the tab headers for the new tabs for (let i = 0; i < nums.length; i++) { self.updateTabHeader(nums[i]); } }; /** * Update the stored status for an input * * @param {number} inputNum - The input that's having its status changed * @param {string} status - The status of the input */ self.updateInputStatus = function(inputNum, status) { if (self.inputs[inputNum] !== undefined) { self.inputs[inputNum].status = status; } }; /** * Update the stored load progress of an input * * @param {object} inputData * @param {number} inputData.inputNum - The input that's having its progress updated * @param {number} inputData.progress - The load progress of the input */ self.updateInputProgress = function(inputData) { const {inputNum, progress} = inputData; if (self.inputs[inputNum] !== undefined) { self.inputs[inputNum].progress = progress; } }; /** * Update the stored value of an input. * * @param {object} inputData * @param {number} inputData.inputNum - The input that's having its value updated * @param {ArrayBuffer} inputData.buffer - The new value of the input as a buffer * @param {number} [inputData.encoding] - The character encoding of the input data * @param {string} [inputData.eolSequence] - The end of line sequence of the input data * @param {string} [inputData.stringSample] - A sample of the value as a string (truncated to 4096 chars) */ self.updateInputValue = function(inputData) { const inputNum = parseInt(inputData.inputNum, 10); if (inputNum < 1) return; if (!Object.prototype.hasOwnProperty.call(self.inputs, inputNum)) throw new Error(`No input with ID ${inputNum} exists`); self.inputs[inputNum].buffer = inputData.buffer; if ("encoding" in inputData) { self.inputs[inputNum].encoding = inputData.encoding; } if ("eolSequence" in inputData) { self.inputs[inputNum].eolSequence = inputData.eolSequence; } if (!("stringSample" in inputData)) { inputData.stringSample = Utils.arrayBufferToStr(inputData.buffer.slice(0, 4096)); } self.inputs[inputNum].stringSample = inputData.stringSample; self.inputs[inputNum].status = "loaded"; self.inputs[inputNum].progress = 100; }; /** * Get the index of a loader worker object. * Returns -1 if the worker could not be found * * @param {number} workerId - The ID of the worker we're searching for * @returns {number} */ self.getLoaderWorkerIdx = function(workerId) { for (let i = 0; i < self.loaderWorkers.length; i++) { if (self.loaderWorkers[i].id === workerId) { return i; } } return -1; }; /** * Fires when a loaderWorker is ready to load files. * Stores data about the new loaderWorker in the loaderWorkers array, * and sends the next file to the loaderWorker to be loaded. * * @param {object} workerData * @param {number} workerData.id - The ID of the new loaderWorker */ self.loaderWorkerReady = function(workerData) { const newWorkerObj = { id: workerData.id, inputNum: -1, active: true }; self.loaderWorkers.push(newWorkerObj); self.loadNextFile(self.loaderWorkers.indexOf(newWorkerObj)); }; /** * Handler for messages sent by loaderWorkers. * (Messages are sent between the inputWorker and loaderWorkers via the main thread) * * @param {object} r - The data sent by the loaderWorker * @param {number} r.inputNum - The inputNum which the message corresponds to * @param {string} r.error - Present if an error is fired by the loaderWorker. Contains the error message string. * @param {ArrayBuffer} r.fileBuffer - Present if a file has finished loading. Contains the loaded file buffer. */ self.handleLoaderMessage = function(r) { let inputNum = 0; if ("inputNum" in r) { inputNum = r.inputNum; } if ("error" in r) { self.updateInputProgress(r.inputNum, 0); self.updateInputStatus(r.inputNum, "error"); log.error(r.error); self.loadingInputs--; self.terminateLoaderWorker(r.id); self.activateLoaderWorker(); self.setInput({inputNum: inputNum, silent: true}); return; } if ("fileBuffer" in r) { log.debug(`Input file ${inputNum} loaded.`); self.loadingInputs--; self.updateInputValue({ inputNum: inputNum, buffer: r.fileBuffer }); self.postMessage({ action: "fileLoaded", data: { inputNum: inputNum } }); const idx = self.getLoaderWorkerIdx(r.id); self.loadNextFile(idx); } else if ("progress" in r) { self.updateInputProgress(r); } }; /** * Loads the next file using a loaderWorker * * @param {number} - The loaderWorker which will load the file */ self.loadNextFile = function(workerIdx) { if (workerIdx === -1) return; const id = self.loaderWorkers[workerIdx].id; if (self.pendingFiles.length === 0) { const workerObj = self.loaderWorkers.splice(workerIdx, 1)[0]; self.terminateLoaderWorker(workerObj.id); return; } const nextFile = self.pendingFiles.splice(0, 1)[0]; self.loaderWorkers[workerIdx].inputNum = nextFile.inputNum; self.loadingInputs++; self.postMessage({ action: "loadInput", data: { file: nextFile.file, inputNum: nextFile.inputNum, workerId: id } }); }; /** * Sends a message to the inputWaiter to create a new loaderWorker. * If there's an inactive loaderWorker that already exists, use that instead. */ self.activateLoaderWorker = function() { for (let i = 0; i < self.loaderWorkers.length; i++) { if (!self.loaderWorkers[i].active) { self.loaderWorkers[i].active = true; self.loadNextFile(i); return; } } self.postMessage({ action: "activateLoaderWorker" }); }; /** * Sends a message to the inputWaiter to terminate a loaderWorker. * * @param {number} id - The ID of the worker to be terminated */ self.terminateLoaderWorker = function(id) { self.postMessage({ action: "terminateLoaderWorker", data: id }); // If we still have pending files, spawn a worker if (self.pendingFiles.length > 0) { self.activateLoaderWorker(); } }; /** * Loads files using LoaderWorkers * * @param {object} filesData * @param {FileList} filesData.files - The list of files to be loaded * @param {number} filesData.activeTab - The active tab in the UI */ self.loadFiles = function(filesData) { const {files, activeTab} = filesData; let lastInputNum = -1; const inputNums = []; for (let i = 0; i < files.length; i++) { // If the first input is empty, replace it rather than adding a new one if (i === 0 && (!self.inputs[activeTab].buffer || self.inputs[activeTab].buffer.byteLength === 0)) { self.removeInput({ inputNum: activeTab, refreshTabs: false, removeChefWorker: false }); lastInputNum = self.addInput(false, "file", { name: files[i].name, size: files[i].size.toLocaleString(), type: files[i].type || "unknown" }, activeTab); } else { lastInputNum = self.addInput(false, "file", { name: files[i].name, size: files[i].size.toLocaleString(), type: files[i].type || "unknown" }); } inputNums.push(lastInputNum); self.pendingFiles.push({ file: files[i], inputNum: lastInputNum }); } let max = self.maxWorkers; if (self.pendingFiles.length < self.maxWorkers) max = self.pendingFiles.length; // Create loaderWorkers to load the new files for (let i = 0; i < max; i++) { self.activateLoaderWorker(); } self.getLoadProgress(); self.setInput({inputNum: lastInputNum, silent: true}); }; /** * Adds an input to the input dictionary * * @param {boolean} [changetab=false] - Whether or not to change to the new input * @param {string} type - Either "userinput" or "file" * @param {Object} fileData - Contains information about the file to be added to the input (only used when type is "file") * @param {string} fileData.name - The filename of the input being added * @param {number} fileData.size - The file size (in bytes) of the input being added * @param {string} fileData.type - The MIME type of the input being added * @param {number} inputNum - Defaults to auto-incrementing self.currentInputNum */ self.addInput = function( changeTab = false, type, fileData = { name: "unknown", size: 0, type: "unknown" }, inputNum = self.currentInputNum++ ) { self.numInputs++; const newInputObj = { type: null, buffer: new ArrayBuffer(), stringSample: "", file: null, status: "pending", progress: 0, encoding: 0, eolSequence: "\u000a" }; switch (type) { case "userinput": newInputObj.type = "userinput"; newInputObj.status = "loaded"; newInputObj.progress = 100; break; case "file": newInputObj.type = "file"; newInputObj.file = { name: fileData.name, size: fileData.size, type: fileData.type }; newInputObj.status = "pending"; newInputObj.progress = 0; break; default: log.error(`Invalid input type '${type}'.`); return -1; } self.inputs[inputNum] = newInputObj; // Tell the inputWaiter we've added an input, so it can create a tab to display it self.postMessage({ action: "inputAdded", data: { changeTab: changeTab, inputNum: inputNum } }); return inputNum; }; /** * Remove an input from the inputs dictionary * * @param {object} removeInputData * @param {number} removeInputData.inputNum - The number of the input to be removed * @param {boolean} removeInputData.refreshTabs - If true, refresh the tabs after removing the input * @param {boolean} removeInputData.removeChefWorker - If true, remove a chefWorker from the WorkerWaiter */ self.removeInput = function(removeInputData) { const inputNum = removeInputData.inputNum; const refreshTabs = removeInputData.refreshTabs; self.numInputs--; for (let i = 0; i < self.loaderWorkers.length; i++) { if (self.loaderWorkers[i].inputNum === inputNum) { // Terminate any loaderWorker that's loading the removed input self.loadingInputs--; self.terminateLoaderWorker(self.loaderWorkers[i].id); break; } } for (let i = 0; i < self.pendingFiles.length; i++) { // Remove the input from the pending files list if (self.pendingFiles[i].inputNum === inputNum) { self.pendingFiles.splice(i, 1); break; } } delete self.inputs[inputNum]; if (refreshTabs) { self.refreshTabs(self.getPreviousInputNum(inputNum), "left"); } if (self.numInputs < self.maxWorkers && removeInputData.removeChefWorker) { self.postMessage({ action: "removeChefWorker" }); } }; /** * Change to the next tab. * * @param {number} inputNum - The inputNum of the tab to change to */ self.changeTabRight = function(inputNum) { const newInput = self.getNextInputNum(inputNum); self.postMessage({ action: "changeTab", data: newInput }); }; /** * Change to the previous tab. * * @param {number} inputNum - The inputNum of the tab to change to */ self.changeTabLeft = function(inputNum) { const newInput = self.getPreviousInputNum(inputNum); self.postMessage({ action: "changeTab", data: newInput }); }; /** * Updates the maximum number of tabs, and refreshes them if it changes * * @param {number} maxTabs - The new max number of tabs * @param {number} activeTab - The currently selected tab */ self.updateMaxTabs = function(maxTabs, activeTab) { if (self.maxTabs !== maxTabs) { self.maxTabs = maxTabs; self.refreshTabs(activeTab, "right"); } }; /** * Search the inputs for any that match the filters provided, * posting the results back to the inputWaiter * * @param {object} searchData - Object containing the search filters * @param {boolean} searchData.showPending - If true, include pending inputs in the results * @param {boolean} searchData.showLoading - If true, include loading inputs in the results * @param {boolean} searchData.showLoaded - If true, include loaded inputs in the results * @param {string} searchData.filter - A regular expression to match the inputs on * @param {string} searchData.filterType - Either "CONTENT" or "FILENAME". Determines what should be matched with filter * @param {number} searchData.numResults - The maximum number of results to be returned */ self.filterTabs = function(searchData) { const showPending = searchData.showPending, showLoading = searchData.showLoading, showLoaded = searchData.showLoaded, filterType = searchData.filterType; let filterExp; try { filterExp = new RegExp(searchData.filter, "i"); } catch (error) { self.postMessage({ action: "filterTabError", data: error.message }); return; } const numResults = searchData.numResults; const inputs = []; const inputNums = Object.keys(self.inputs); for (let i = 0; i < inputNums.length; i++) { const iNum = inputNums[i]; let textDisplay = ""; let addInput = false; if (self.inputs[iNum].status === "pending" && showPending || self.inputs[iNum].status === "loading" && showLoading || self.inputs[iNum].status === "loaded" && showLoaded) { try { if (self.inputs[iNum].type === "userinput") { if (filterType.toLowerCase() === "content" && filterExp.test(self.inputs[iNum].stringSample)) { textDisplay = self.inputs[iNum].stringSample; addInput = true; } } else { if ((filterType.toLowerCase() === "filename" && filterExp.test(self.inputs[iNum].file.name)) || (filterType.toLowerCase() === "content" && filterExp.test(self.inputs[iNum].stringSample))) { textDisplay = self.inputs[iNum].file.name; addInput = true; } } } catch (error) { self.postMessage({ action: "filterTabError", data: error.message }); return; } } if (addInput) { if (textDisplay === "" || textDisplay === undefined) { textDisplay = "New Tab"; } const inputItem = { inputNum: iNum, textDisplay: textDisplay }; inputs.push(inputItem); } if (inputs.length >= numResults) { break; } } // Send the results back to the inputWaiter self.postMessage({ action: "displayTabSearchResults", data: inputs }); }; ================================================ FILE: src/web/workers/LoaderWorker.js ================================================ /** * Web Worker to load large amounts of data without locking up the UI. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import loglevelMessagePrefix from "loglevel-message-prefix"; loglevelMessagePrefix(log, { prefixes: [], staticPrefixes: ["LoaderWorker"] }); self.id = null; /** * Respond to message from parent thread. */ self.addEventListener("message", function(e) { // Handle message const r = e.data; log.debug(`Receiving command '${r.action}'`); switch (r.action) { case "setID": self.id = r.data.id; break; case "loadFile": self.loadFile(r.data.file, r.data?.inputNum ?? ""); break; case "setLogLevel": log.setLevel(r.data, false); break; default: log.error(`Unknown action '${r.action}'.`); } }); /** * Loads a file object into an ArrayBuffer, then transfers it back to the parent thread. * * @param {File} file * @param {string} inputNum */ self.loadFile = function(file, inputNum) { const reader = new FileReader(); if (file.size >= 256*256*256*128) { self.postMessage({"error": "File size too large.", "inputNum": inputNum, "id": self.id}); return; } const data = new Uint8Array(file.size); let offset = 0; const CHUNK_SIZE = 10485760; // 10MiB const seek = function() { if (offset >= file.size) { self.postMessage({"fileBuffer": data.buffer, "inputNum": inputNum, "id": self.id}, [data.buffer]); return; } self.postMessage({"progress": Math.round(offset / file.size * 100), "inputNum": inputNum}); const slice = file.slice(offset, offset + CHUNK_SIZE); reader.readAsArrayBuffer(slice); }; reader.onload = function(e) { data.set(new Uint8Array(reader.result), offset); offset += CHUNK_SIZE; seek(); }; reader.onerror = function(e) { self.postMessage({"error": reader.error.message, "inputNum": inputNum, "id": self.id}); }; seek(); }; ================================================ FILE: src/web/workers/ZipWorker.mjs ================================================ /** * Web Worker to handle zipping the outputs for download. * * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import zip from "zlibjs/bin/zip.min.js"; import Utils from "../../core/Utils.mjs"; import Dish from "../../core/Dish.mjs"; import {detectFileType} from "../../core/lib/FileType.mjs"; import loglevelMessagePrefix from "loglevel-message-prefix"; loglevelMessagePrefix(log, { prefixes: [], staticPrefixes: ["ZipWorker"], }); const Zlib = zip.Zlib; /** * Respond to message from parent thread. */ self.addEventListener("message", function(e) { // Handle message from the main thread const r = e.data; log.debug(`Receiving command '${r.action}'`); switch (r.action) { case "zipFiles": self.zipFiles(r.data.outputs, r.data.filename, r.data.fileExtension); break; case "setLogLevel": log.setLevel(r.data, false); break; default: log.error(`Unknown action: '${r.action}'`); } }); self.setOption = function(...args) {}; /** * Compress the files into a zip file and send the zip back * to the OutputWaiter. * * @param {object} outputs * @param {string} filename * @param {string} fileExtension */ self.zipFiles = async function(outputs, filename, fileExtension) { const zip = new Zlib.Zip(); const inputNums = Object.keys(outputs); for (let i = 0; i < inputNums.length; i++) { const iNum = inputNums[i]; let ext = fileExtension; const cloned = new Dish(outputs[iNum].data.dish); const output = new Uint8Array(await cloned.get(Dish.ARRAY_BUFFER)); if (fileExtension === undefined || fileExtension === "") { // Detect automatically const types = detectFileType(output); if (!types.length) { ext = ".dat"; } else { ext = `.${types[0].extension.split(",", 1)[0]}`; } } const name = Utils.strToByteArray(iNum + ext); zip.addFile(output, {filename: name}); } const zippedFile = zip.compress(); self.postMessage({ zippedFile: zippedFile.buffer, filename: filename }, [zippedFile.buffer]); }; ================================================ FILE: tests/browser/00_nightwatch.js ================================================ /** * Tests to ensure that the app loads correctly in a reasonable time and that operations can be run. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ const utils = require("./browserUtils.js"); module.exports = { before: browser => { browser .resizeWindow(1280, 800) .url(browser.launchUrl); }, "Loading screen": browser => { // Check that the loading screen appears and then disappears within a reasonable time browser .waitForElementVisible("#preloader", 300) .waitForElementNotPresent("#preloader", 10000); }, "App loaded": browser => { browser.useCss(); // Check that various important elements are loaded browser.expect.element("#operations").to.be.visible; browser.expect.element("#recipe").to.be.visible; browser.expect.element("#input").to.be.present; browser.expect.element("#output").to.be.present; browser.expect.element(".op-list").to.be.present; browser.expect.element("#rec-list").to.be.visible; browser.expect.element("#controls").to.be.visible; browser.expect.element("#input-text").to.be.visible; browser.expect.element("#output-text").to.be.visible; }, "Operations loaded": browser => { browser.useXpath(); // Check that an operation in every category has been populated browser.expect.element("//li[contains(@class, 'operation') and text()='To Base64']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='To Binary']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='AES Decrypt']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='PEM to Hex']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Power Set']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Parse IP range']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Remove Diacritics']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Sort']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='To UNIX Timestamp']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Extract dates']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Gzip']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Keccak']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='JSON Beautify']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Detect File Type']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Play Media']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Disassemble x86']").to.be.present; browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present; }, "Recipe can be run": browser => { const toHex = "//li[contains(@class, 'operation') and text()='To Hex']"; const op = "#rec-list .operation .op-title"; // Check that operation is visible browser .useXpath() .expect.element(toHex).to.be.visible; // Add it to the recipe by double clicking browser .useXpath() .moveToElement(toHex, 10, 10) .useCss() .waitForElementVisible(".popover-body", 1000) .doubleClick("xpath", toHex); // Confirm that it has been added to the recipe browser .useCss() .waitForElementVisible(op, 100) .expect.element(op).text.to.contain("To Hex"); // Enter input browser .useCss() .sendKeys("#input-text .cm-content", "Don't Panic.") .pause(1000) .click("#bake"); // Check output browser .useCss() .waitForElementNotVisible("#stale-indicator", 1000) .expect.element("#output-text .cm-content").text.that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e"); // Clear recipe browser .useCss() .moveToElement(op, 10, 10) .waitForElementNotPresent(".popover-body", 1000) .click("#clr-recipe") .waitForElementNotPresent(op); }, "Test every module": browser => { browser.useCss(); // BSON loadOp("BSON deserialise", browser) .waitForElementNotVisible("#output-loader", 5000); // Charts loadOp("Entropy", browser) .waitForElementNotVisible("#output-loader", 5000); // Ciphers loadOp("AES Encrypt", browser) .waitForElementNotVisible("#output-loader", 5000); // Code loadOp("XPath expression", browser) .waitForElementNotVisible("#output-loader", 5000); // Compression loadOp("Gzip", browser) .waitForElementNotVisible("#output-loader", 5000); // Crypto loadOp("MD5", browser) .waitForElementNotVisible("#output-loader", 5000); // Default loadOp("Fork", browser) .waitForElementNotVisible("#output-loader", 5000); // Diff loadOp("Diff", browser) .waitForElementNotVisible("#output-loader", 5000); // Encodings loadOp("Encode text", browser) .waitForElementNotVisible("#output-loader", 5000); // Hashing loadOp("Streebog", browser) .waitForElementNotVisible("#output-loader", 5000); // Image loadOp("Extract EXIF", browser) .waitForElementNotVisible("#output-loader", 5000); // PGP loadOp("PGP Encrypt", browser) .waitForElementNotVisible("#output-loader", 5000); // PublicKey loadOp("Hex to PEM", browser) .waitForElementNotVisible("#output-loader", 5000); // Regex loadOp("Strings", browser) .waitForElementNotVisible("#output-loader", 5000); // Shellcode loadOp("Disassemble x86", browser) .waitForElementNotVisible("#output-loader", 5000); // URL loadOp("URL Encode", browser) .waitForElementNotVisible("#output-loader", 5000); // UserAgent loadOp("Parse User Agent", browser) .waitForElementNotVisible("#output-loader", 5000); // YARA loadOp("YARA Rules", browser) .waitForElementNotVisible("#output-loader", 5000); browser.click("#clr-recipe"); }, "Move around the UI": browser => { const otherCat = "//a[contains(@class, 'category-title') and contains(@data-target, '#catOther')]", genUUID = "//li[contains(@class, 'operation') and text()='Generate UUID']"; browser.useXpath(); // Scroll to a lower category browser .getLocationInView(otherCat) .expect.element(otherCat).to.be.visible; // Open category browser .useCss() .waitForElementNotVisible("#snackbar-container", 10000) .useXpath() .click(otherCat) .expect.element(genUUID).to.be.visible; // Add op to recipe /* mouseButtonUp drops wherever the actual cursor is, not necessarily in the right place, so we can't test Sortable.js properly using Nightwatch. html-dnd doesn't work either. Instead of relying on drag and drop, we double click on the op to load it. */ browser .getLocationInView(genUUID) .moveToElement(genUUID, 10, 10) .doubleClick("xpath", genUUID) .useCss() .waitForElementVisible(".operation .op-title", 1000) .waitForElementNotVisible("#stale-indicator", 1000) .expect.element("#output-text .cm-content").text.which.matches(/[\da-f-]{36}/); browser.click("#clr-recipe"); }, "Search": browser => { // Search for an op browser .useCss() .clearValue("#search") .setValue("#search", "md5") .useXpath() .waitForElementVisible("//ul[@id='search-results']//b[text()='MD5']", 1000); }, "Alert bar": browser => { // Bake nothing to create an empty output which can be copied utils.clear(browser); utils.bake(browser); // Alert bar shows and contains correct content browser .waitForElementNotVisible("#snackbar-container") .click("#copy-output") .waitForElementVisible("#snackbar-container .snackbar-content") .expect.element("#snackbar-container .snackbar-content").text.to.equal("Copied raw output successfully."); // Alert bar disappears after the correct amount of time // Should disappear after 2000ms browser .waitForElementNotPresent("#snackbar-container .snackbar-content", 2500) .waitForElementNotVisible("#snackbar-container"); }, after: browser => { browser.end(); } }; /** * Clears the current recipe and loads a new operation. * * @param {string} opName * @param {Browser} browser */ function loadOp(opName, browser) { return browser .useCss() .click("#clr-recipe") .urlHash("op=" + opName); } ================================================ FILE: tests/browser/01_io.js ================================================ /** * Tests for input and output of various types to ensure the editors work as expected * and retain data integrity, especially when it comes to special characters. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ // import { // clear, // utils.setInput, // bake, // setChrEnc, // setEOLSeq, // copy, // paste, // loadRecipe, // expectOutput, // uploadFile, // uploadFolder // } from "./browserUtils.js"; const utils = require("./browserUtils.js"); const SPECIAL_CHARS = [ "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f", "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f", "\u007f", "\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f", "\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f", "\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9\ufffa\ufffb\ufffc" ].join(""); const ALL_BYTES = [ "\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", ].join(""); const PUA_CHARS = "\ue000\ue001\uf8fe\uf8ff"; const 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." "Why, what did she tell you?" "I don't know, I didn't listen."`; const SELECTABLE_STRING = `ONE two ONE three ONE four ONE`; // Descriptions for named control characters const CONTROL_CHAR_NAMES = { 0: "null", 7: "bell", 8: "backspace", 10: "line feed", 11: "vertical tab", 13: "carriage return", 27: "escape", 8203: "zero width space", 8204: "zero width non-joiner", 8205: "zero width joiner", 8206: "left-to-right mark", 8207: "right-to-left mark", 8232: "line separator", 8237: "left-to-right override", 8238: "right-to-left override", 8294: "left-to-right isolate", 8295: "right-to-left isolate", 8297: "pop directional isolate", 8233: "paragraph separator", 65279: "zero width no-break space", 65532: "object replacement" }; module.exports = { before: browser => { browser .resizeWindow(1280, 800) .url(browser.launchUrl) .useCss() .waitForElementNotPresent("#preloader", 10000) .click("#auto-bake-label"); }, "CodeMirror has loaded correctly": browser => { /* Editor has initialised */ browser .useCss() // Input .waitForElementVisible("#input-text") .waitForElementVisible("#input-text .cm-editor") .waitForElementVisible("#input-text .cm-editor .cm-scroller") .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content") .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content .cm-line") // Output .waitForElementVisible("#output-text") .waitForElementVisible("#output-text .cm-editor") .waitForElementVisible("#output-text .cm-editor .cm-scroller") .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content") .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content .cm-line"); /* Status bar is showing and has correct values */ browser // Input .waitForElementVisible("#input-text .cm-status-bar") .waitForElementVisible("#input-text .cm-status-bar .stats-length-value") .expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("0"); browser.waitForElementVisible("#input-text .cm-status-bar .stats-lines-value") .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); browser.waitForElementVisible("#input-text .cm-status-bar .chr-enc-value") .expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); browser.waitForElementVisible("#input-text .cm-status-bar .eol-value") .expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF"); browser // Output .waitForElementVisible("#output-text .cm-status-bar") .waitForElementVisible("#output-text .cm-status-bar .stats-length-value") .expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0"); browser.waitForElementVisible("#output-text .cm-status-bar .stats-lines-value") .expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); browser.waitForElementVisible("#output-text .cm-status-bar .baking-time-info") .expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); browser.waitForElementVisible("#output-text .cm-status-bar .chr-enc-value") .expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); browser.waitForElementVisible("#output-text .cm-status-bar .eol-value") .expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); }, "Adding content": browser => { /* Status bar updates correctly */ utils.setInput(browser, MULTI_LINE_STRING); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3"); browser.expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); browser.expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF"); browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0"); browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); /* Output updates correctly */ utils.bake(browser); browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); }, "Autobaking the latest input": browser => { // Use the sleep recipe to simulate a long running task utils.loadRecipe(browser, "Sleep", "input", [2000]); browser.waitForElementVisible("#stale-indicator"); // Enable previously disabled autobake browser.expect.element("#auto-bake").to.not.be.selected; browser.click("#auto-bake-label"); browser.expect.element("#auto-bake").to.be.selected.before(1000); // Add content to the input browser.pause(100); browser.sendKeys("#input-text .cm-content", "1"); browser.waitForElementVisible("#output-loader"); browser.pause(500); // Make another change while the previous input is being baked browser .sendKeys("#input-text .cm-content", "2") .waitForElementNotVisible("#stale-indicator") .waitForElementNotVisible("#output-loader"); // Ensure we got the latest input baked utils.expectOutput(browser, "input12"); // Turn autobake off again browser.click("#auto-bake-label"); browser.expect.element("#auto-bake").to.not.be.selected.before(1000); }, "Special content": browser => { /* Special characters are rendered correctly */ utils.setInput(browser, SPECIAL_CHARS, false); // First line for (let i = 0x0; i <= 0x8; i++) { browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) .text.to.equal(String.fromCharCode(0x2400 + i)); } // Tab \u0009 browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/); // Line feed \u000a browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); // Second line for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) { const index = SPECIAL_CHARS.charCodeAt(i); const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16); const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index); browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) .to.have.property("title").equals(`Control character ${name}`); browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) .text.to.equal(value); } /* Output renders correctly */ utils.setChrEnc(browser, "output", "UTF-8"); utils.bake(browser); // First line for (let i = 0x0; i <= 0x8; i++) { browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) .text.to.equal(String.fromCharCode(0x2400 + i)); } // Tab \u0009 browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/); // Line feed \u000a browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/); browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); // Second line for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) { const index = SPECIAL_CHARS.charCodeAt(i); const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16); const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index); browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) .to.have.property("title").equals(`Control character ${name}`); browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) .text.to.equal(value); } /* Bytes are rendered correctly */ utils.setInput(browser, ALL_BYTES, false); // Expect length to be 255, since one character is creating a newline browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{255}$/); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("256"); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); /* PUA \ue000-\uf8ff */ utils.setInput(browser, PUA_CHARS, false); utils.setChrEnc(browser, "output", "UTF-8"); utils.bake(browser); // Confirm input and output as expected /* In order to render whitespace characters as control character pictures in the output, even when they are the designated line separator, CyberChef sometimes chooses to represent them internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas). See `Utils.escapeWhitespace()` for an example of this. Therefore, PUA characters should be rendered normally in the Input but as control character pictures in the output. */ browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^\ue000\ue001\uf8fe\uf8ff$/); browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^\u2400\u2401\u3cfe\u3cff$/); /* Can be copied */ utils.setInput(browser, SPECIAL_CHARS, false); utils.setChrEnc(browser, "output", "UTF-8"); utils.bake(browser); // Manual copy browser .doubleClick("#output-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)") .waitForElementVisible("#output-text .cm-selectionBackground"); utils.copy(browser); utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values // Ensure that the values are as expected browser.expect.element("#search").to.have.value.that.equals("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008"); browser.clearValue("#search"); // Raw copy browser .click("#copy-output") .pause(100); utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values // Ensure that the values are as expected browser.expect.element("#search").to.have.value.that.matches(/^\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009/); browser.clearValue("#search"); }, "HTML output": browser => { /* Displays correctly */ utils.loadRecipe(browser, "Entropy", ALL_BYTES); utils.bake(browser); browser .waitForElementVisible("#output-html") .waitForElementVisible("#output-html #chart-area"); /* Status bar widgets are disabled */ browser.expect.element("#output-text .cm-status-bar .disabled .stats-length-value").to.be.visible; browser.expect.element("#output-text .cm-status-bar .disabled .stats-lines-value").to.be.visible; browser.expect.element("#output-text .cm-status-bar .disabled .chr-enc-value").to.be.visible; browser.expect.element("#output-text .cm-status-bar .disabled .eol-value").to.be.visible; /* Displays special chars correctly */ utils.loadRecipe(browser, "To Table", ",\u0000\u0001\u0002\u0003\u0004", [",", "\\r\\n", false, "HTML"]); utils.bake(browser); for (let i = 0x0; i <= 0x4; i++) { browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`) .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`) .text.to.equal(String.fromCharCode(0x2400 + i)); } /* Can be copied */ // Raw copy browser .click("#copy-output") .pause(100); utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values // Ensure that the values are as expected browser.expect.element("#search").to.have.value.that.matches(/\u0000\u0001\u0002\u0003\u0004/); browser.clearValue("#search"); }, "Highlighting": browser => { utils.setInput(browser, SELECTABLE_STRING); utils.bake(browser); /* Selecting input text also selects other instances in input and output */ browser // Input .click("#auto-bake-label") .doubleClick("#input-text .cm-content .cm-line:nth-of-type(1)") .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); browser // Output .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); /* Selecting output text highlights in input */ browser // Output .click("#output-text") .waitForElementNotPresent("#input-text .cm-selectionLayer .cm-selectionBackground") .waitForElementNotPresent("#output-text .cm-selectionLayer .cm-selectionBackground") .waitForElementNotPresent("#input-text .cm-content .cm-line .cm-selectionMatch") .waitForElementNotPresent("#output-text .cm-content .cm-line .cm-selectionMatch") .doubleClick("#output-text .cm-content .cm-line:nth-of-type(7)") .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground") .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); browser // Input .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground") .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); // Turn autobake off again browser.click("#auto-bake-label"); }, "Character encoding": browser => { const CHINESE_CHARS = "不要恐慌。"; /* Dropup works */ /* Selecting changes output correctly */ utils.setInput(browser, CHINESE_CHARS, false); utils.setChrEnc(browser, "input", "UTF-8"); utils.bake(browser); /* Output encoding should be autodetected */ browser .waitForElementVisible("#snackbar-container .snackbar-content", 5000) .expect.element("#snackbar-container .snackbar-content").text.to.equal("Output character encoding has been detected and changed to UTF-8"); utils.expectOutput(browser, CHINESE_CHARS); /* Change the output encoding manually to test for URL presence */ utils.setChrEnc(browser, "output", "UTF-8"); /* Encodings appear in the URL */ browser.assert.urlContains("ienc=65001"); browser.assert.urlContains("oenc=65001"); /* Preserved when changing tabs */ browser .click("#btn-new-tab") .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab"); browser.expect.element("#input-text .chr-enc-value").text.that.equals("Raw Bytes"); browser.expect.element("#output-text .chr-enc-value").text.that.equals("Raw Bytes"); utils.setChrEnc(browser, "input", "UTF-7"); utils.setChrEnc(browser, "output", "UTF-7"); browser .click("#input-tabs li:nth-of-type(1)") .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab"); browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-8"); browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-8"); /* Try various encodings */ // These are not meant to be realistic encodings for this data utils.setInput(browser, CHINESE_CHARS, false); utils.setChrEnc(browser, "input", "UTF-8"); utils.setChrEnc(browser, "output", "UTF-16LE"); utils.bake(browser); utils.expectOutput(browser, "\uB8E4\uE88D\u81A6\u81E6\uE690\u8C85\u80E3"); utils.setChrEnc(browser, "output", "Simplified Chinese GBK"); utils.bake(browser); utils.expectOutput(browser, "\u6D93\u5D88\uFDFF\u93AD\u612D\u53A1\u9286\u0000"); utils.setChrEnc(browser, "input", "UTF-7"); utils.bake(browser); utils.expectOutput(browser, "+Tg0-+iYE-+YFA-+YUw-"); utils.setChrEnc(browser, "input", "Traditional Chinese Big5"); utils.bake(browser); utils.expectOutput(browser, "\u3043\u74B6\uFDFF\u7A3A\uFDFF"); utils.setChrEnc(browser, "output", "Windows-1251 Cyrillic"); utils.bake(browser); utils.expectOutput(browser, "\u00A4\u0408\u00ADn\u00AE\u0408\u00B7W\u040EC"); }, "Line endings": browser => { /* Dropup works */ /* Selecting changes view in input */ utils.setInput(browser, MULTI_LINE_STRING); // Line endings: LF // Input browser .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(3)") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4)") .waitForElementNotPresent("#input-text .cm-content .cm-specialChar"); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3"); // Output utils.bake(browser); browser .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)") .waitForElementNotPresent("#output-text .cm-content .cm-specialChar"); browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); // Input EOL: VT utils.setEOLSeq(browser, "input", "VT"); // Input browser .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)") .waitForElementPresent("#input-text .cm-content .cm-specialChar"); browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊"); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); // Output utils.bake(browser); browser .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)") .waitForElementNotPresent("#output-text .cm-content .cm-specialChar"); browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); // Output EOL: VT utils.setEOLSeq(browser, "output", "VT"); // Input browser .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)") .waitForElementPresent("#input-text .cm-content .cm-specialChar"); browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊"); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); // Output browser .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(1)") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2)") .waitForElementPresent("#output-text .cm-content .cm-specialChar"); browser.expect.element("#output-text .cm-content .cm-specialChar").text.to.equal("␊"); browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); /* Adding new line ending changes output correctly */ browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN); // Input browser .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)"); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("302"); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); // Output utils.bake(browser); browser .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)"); browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("302"); browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); // Input EOL: CRLF utils.setEOLSeq(browser, "input", "CRLF"); // Output EOL: CR utils.setEOLSeq(browser, "output", "CR"); browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN); // Input browser .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)") .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)") .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)"); browser.expect.element("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)").text.to.equal("␋"); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("304"); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); // Output utils.bake(browser); browser .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)") .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)") .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar"); browser.expect.element("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar").text.to.equal("␊"); browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("304"); browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); /* Line endings appear in the URL */ browser.assert.urlContains("ieol=CRLF"); browser.assert.urlContains("oeol=CR"); /* Preserved when changing tabs */ browser .click("#btn-new-tab") .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab"); browser.expect.element("#input-text .eol-value").text.that.equals("LF"); browser.expect.element("#output-text .eol-value").text.that.equals("LF"); utils.setEOLSeq(browser, "input", "FF"); utils.setEOLSeq(browser, "output", "LS"); browser .click("#input-tabs li:nth-of-type(1)") .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab"); browser.expect.element("#input-text .eol-value").text.that.equals("CRLF"); browser.expect.element("#output-text .eol-value").text.that.equals("CR"); }, "File inputs": browser => { utils.clear(browser); /* Side panel displays correct info */ utils.uploadFile(browser, "files/TowelDay.jpeg"); browser .waitForElementVisible("#input-text .cm-file-details") .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") .waitForElementVisible("#input-text .cm-file-details .file-details-name") .waitForElementVisible("#input-text .cm-file-details .file-details-size") .waitForElementVisible("#input-text .cm-file-details .file-details-type") .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg"); browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes"); browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); /* Side panel can be hidden */ browser .click("#input-text .cm-file-details .file-details-toggle-shown") .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-shown") .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-hidden") .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("1px"); browser .click("#input-text .cm-file-details .file-details-toggle-hidden") .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-hidden") .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("200px"); }, "Folder inputs": browser => { utils.clear(browser); /* Side panel displays correct info */ utils.uploadFolder(browser, "files"); // Loop through tabs for (let i = 1; i < 3; i++) { browser .click(`#input-tabs li:nth-of-type(${i})`) .waitForElementVisible(`#input-tabs li:nth-of-type(${i}).active-input-tab`); browser .waitForElementVisible("#input-text .cm-file-details") .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") .waitForElementVisible("#input-text .cm-file-details .file-details-name") .waitForElementVisible("#input-text .cm-file-details .file-details-size") .waitForElementVisible("#input-text .cm-file-details .file-details-type") .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); browser.getText("#input-text .cm-file-details .file-details-name", function(result) { switch (result.value) { case "TowelDay.jpeg": browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg"); browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes"); browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); break; case "Hitchhikers_Guide.jpeg": browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("Hitchhikers_Guide.jpeg"); browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("36,595 bytes"); browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); break; default: break; } }); } }, // "Loading from URL": browser => { // utils.clear(browser); // /* Side panel displays correct info */ // utils.uploadFile(browser, "files/TowelDay.jpeg"); // browser // .waitForElementVisible("#input-text .cm-file-details") // .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") // .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") // .waitForElementVisible("#input-text .cm-file-details .file-details-name") // .waitForElementVisible("#input-text .cm-file-details .file-details-size") // .waitForElementVisible("#input-text .cm-file-details .file-details-type") // .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); // /* Complex deep link populates the input correctly (encoding, eol, input) */ // browser // .urlHash("recipe=To_Base64('A-Za-z0-9%2B/%3D')&input=VGhlIHNoaXBzIGh1bmcgaW4gdGhlIHNreSBpbiBtdWNoIHRoZSBzYW1lIHdheSB0aGF0IGJyaWNrcyBkb24ndC4M&ienc=21866&oenc=1201&ieol=FF&oeol=PS") // .waitForElementVisible("#rec-list li.operation"); // browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{65}$/); // browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("66"); // browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); // browser.expect.element("#input-text .chr-enc-value").text.that.equals("KOI8-U Ukrainian Cyrillic"); // browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-16BE"); // browser.expect.element("#input-text .eol-value").text.that.equals("FF"); // browser.expect.element("#output-text .eol-value").text.that.equals("PS"); // utils.bake(browser); // browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^.{44}$/); // browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("44"); // browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); // }, "Replace input with output": browser => { /* Input is correctly populated */ 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]); utils.setChrEnc(browser, "input", "UTF-32LE"); utils.setChrEnc(browser, "output", "UTF-7"); utils.setEOLSeq(browser, "input", "CRLF"); utils.setEOLSeq(browser, "output", "LS"); browser .sendKeys("#input-text .cm-content", browser.Keys.RETURN) .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); utils.bake(browser); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("67"); browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-32LE"); browser.expect.element("#input-text .eol-value").text.that.equals("CRLF"); browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("268"); browser .click("#switch") .waitForElementVisible("#stale-indicator"); browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("268"); /* Special characters, encodings and line endings all as expected */ browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-7"); browser.expect.element("#input-text .eol-value").text.that.equals("LS"); browser.expect.element("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)").text.to.equal("␍"); browser.expect.element("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(49)").text.to.equal("␑"); browser.waitForElementNotPresent("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(50)"); }, after: browser => { browser.end(); } }; ================================================ FILE: tests/browser/02_ops.js ================================================ /** * Tests for operations. * The primary purpose for these test is to ensure that the operations * output something vaguely expected (i.e. they aren't completely broken * after a dependency update or changes to the UI), rather than to confirm * that this output is actually accurate. Accuracy of output and testing * of edge cases should be carried out in the operations test suite found * in /tests/operations as this is much faster and easier to configure * than the UI tests found here. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ const utils = require("./browserUtils.js"); module.exports = { before: browser => { browser .resizeWindow(1280, 800) .url(browser.launchUrl) .useCss() .waitForElementNotPresent("#preloader", 10000) .click("#auto-bake-label"); }, "Sanity check operations": async browser => { const Images = await import("../samples/Images.mjs"); testOp(browser, "A1Z26 Cipher Decode", "20 5 19 20 15 21 20 16 21 20", "testoutput"); testOp(browser, "A1Z26 Cipher Encode", "test input", "20 5 19 20 9 14 16 21 20"); testOp(browser, "ADD", "test input", "Ê»ÉÊv¿ÄÆËÊ", [{ "option": "Hex", "string": "56" }]); testOp(browser, "AES Decrypt", "b443f7f7c16ac5396a34273f6f639caa", "test output", [{ "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, { "option": "Hex", "string": "00000000000000000000000000000000" }, "CBC", "Hex", "Raw", { "option": "Hex", "string": "" }]); testOp(browser, "AES Encrypt", "test input", "e42eb8fbfb7a98fff061cd2c1a794d92", [{"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CBC", "Raw", "Hex"]); testOp(browser, "AND", "test input", "4$04 $044", [{ "option": "Hex", "string": "34" }]); testOp(browser, "Add line numbers", "test input", "1 test input"); testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "50cdf8ea483c55564a091650c2bccb4586f919b721e5fe9d6a61660505b4346d6ebdb2ef0cf075a7728cd84cb26ea3e477b5bd86a94a49a27d79423994afb60a", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto"], []]); testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "78b3055463d9167dd039e47f451acaf06c593d209f8e405b4e18011cdcf190dc0af5952be887d93c0ebd38738e978120c1294c71104e6b00d3f9de8d6320ec1c", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Black"], []]); testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "4ab4d4b6cb22ad700f6cd144c2c8ecad2a094f21a1d1d5d48eb6c8f97417192f89b4512f6a78276d49668ebef5e89c3a4d14860cb79399a0dafce98c92209e07", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Mono"], []]); testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "11490db4907516b4d9e256da1ac0b02b561fa7547971e6316a8a0b90c9c66585a11f3145672c6d972b1a221d3bfad9c8a97de7ff77fd9442ebc40f39c1ef9ef7", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Slab"], []]); testOp(browser, ["From Hex", "Dither Image", "SHA2"], Images.PNG_HEX, "cbf587a78915cfb14546ba83080b13e5054800802488dd0cb786b8951e7dc0b48f055260917bd0ccfc075e422b9d6aff112948562653995d74e70f0b66367ac3", [[], [], []]); testOp(browser, ["From Hex", "Generate Image", "SHA2"], Images.PNG_HEX, "2c451762a6c9192fd31dc80765eab3f447be70ea51f6fdb6911ade4d89d4a98bd0a1ff00b08d76aac472faeceb54b66092e3f3be7bbf899bf3e55ca9c96a56aa", [[], [], []]); testOp(browser, ["From Hex", "Image Hue/Saturation/Lightness", "SHA2"], Images.PNG_HEX, "522dfc0bbef00e05c5d6861a002039fa2952e4bbb7fe8d21d0d538ef6f9d65da82065929b4150dc5b8b49460ee6c9bef7f660b86f8d4e7442a07c61c0a152a4b", [[], [50, 50, 50], []]); testOp(browser, ["From Hex", "Resize Image", "SHA2"], Images.PNG_HEX, "654bfbf0a0537c901459c4bc22c5fb0bacbf01af775a0733e3a1c46cda5b699bcc4ed85322d813c7bb9b245d62d64425c0766fe03d3d20bc63634e2a4df17626", [[], [64, 64], []]); testOp(browser, "Adler-32 Checksum", "test input", "16160411"); testOp(browser, "Affine Cipher Decode", "test input", "rcqr glnsr", [1, 2]); testOp(browser, "Affine Cipher Encode", "test input", "gndg zoujg", [3, 1]); testOp(browser, "AMF Decode", "\u000A\u0013\u0001\u0003a\u0006\u0009test", /"\$value": "test"/); testOp(browser, "AMF Encode", '{"a": "test"}', "\u000A\u0013\u0001\u0003a\u0006\u0009test"); testOp(browser, "Analyse hash", "0123456789abcdef", /CRC-64/); testOp(browser, "Atbash Cipher", "test input", "gvhg rmkfg"); // testOp(browser, "Avro to JSON", "test input", "test_output"); testOp(browser, [ "From Hex", "Avro to JSON" ], "4f626a0104166176726f2e736368656d6196017b2274797065223a227265636f7264222c226e616d65223a22736d616c6c222c226669656c6473223a5b7b226e616d65223a226e616d65222c2274797065223a22737472696e67227d5d7d146176726f2e636f646563086e756c6c004e0247632e3702e5b75cdab9a62f1541020e0c6d796e616d654e0247632e3702e5b75cdab9a62f1541", '{"name":"myname"}\n', [[], [false]] ); testOp(browser, "BLAKE2b", "test input", "33ebdc8f38177f3f3f334eeb117a84e11f061bbca4db6b8923e5cec85103f59f415551a5d5a933fdb6305dc7bf84671c2540b463dbfa08ee1895cfaa5bd780b5", ["512", "Hex", { "option": "UTF8", "string": "pass" }]); testOp(browser, "BLAKE2s", "test input", "defe73d61dfa6e5807e4f9643e159a09ccda6be3c26dcd65f8a9bb38bfc973a7", ["256", "Hex", { "option": "UTF8", "string": "pass" }]); testOp(browser, "BSON deserialise", "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000", '{\u000A "a": "test"\u000A}'); testOp(browser, "BSON serialise", '{"a":"test"}', "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000"); // testOp(browser, "Bacon Cipher Decode", "test input", "test_output"); // testOp(browser, "Bacon Cipher Encode", "test input", "test_output"); testOp(browser, "Bcrypt", "test input", /^\$2a\$06\$.{53}$/, [6]); testOp(browser, "Bcrypt compare", "test input", "Match: test input", ["$2a$05$FCfBSVX7OeRkK.9kQVFCiOYu9XtwtIbePqUiroD1lkASW9q5QClzG"]); testOp(browser, "Bcrypt parse", "$2a$05$kXWtAIGB/R8VEzInoM5ocOTBtyc0m2YTIwFiBU/0XoW032f9QrkWW", /Rounds: 5/); testOp(browser, "Bifid Cipher Decode", "qblb tfovy", "test input", ["pass"]); testOp(browser, "Bifid Cipher Encode", "test input", "qblb tfovy", ["pass"]); testOp(browser, "Bit shift left", "test input", "\u00E8\u00CA\u00E6\u00E8@\u00D2\u00DC\u00E0\u00EA\u00E8"); testOp(browser, "Bit shift right", "test input", ":29:\u0010478::"); testOp(browser, "Blowfish Decrypt", "10884e15427dd84ec35204e9c8e921ae", "test_output", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Hex", "Raw"]); testOp(browser, "Blowfish Encrypt", "test input", "f0fadbd1d90d774f714248cf26b96410", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Raw", "Hex"]); testOp(browser, ["From Hex", "Blur Image", "SHA2"], Images.PNG_HEX, "24f2e89f3e00cc35f551bbc48ea82e76474946ce0282183494d1ca3d3b0012c27b6102c4368ae056dc7fecb6df7886d86ff3d29b7e5965493f30c371eee9a24e"); testOp(browser, ["From Hex", "Blur Image", "SHA2"], Images.PNG_HEX, "2c49d89fc10c94352c9a19f82de353c37928831d6f976a6b36eb918825a0ba027980801838228a4a0da63f1886e4fa59b6666f992ad2d2b7d4622253dc034052", [[], [5, "Gaussian"], []]); testOp(browser, ["From Hex", "Sharpen Image", "SHA2"], Images.PNG_HEX, "acc7027642c2eeb67d7356a80ed8a1bdce9adabf656ea1294e47723f506626a7aa41f1660fa844a1e1e83b17180017ab0d5bccd7f6a341692832020dc887eaa5"); testOp(browser, ["From Hex", "Contain Image", "SHA2"], Images.PNG_HEX, "cb871ad0722d487d56a2b18247b1aa30ecc244eb717e08e23a55cae78759553312dc1717196d7cb9daa04743e57c56fc3901ba92be5a68fb03c377f718e8efe7"); 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 { browser.end(); } }; /** @function * Clears the current recipe and bakes a new operation. * * @param {Browser} browser - Nightwatch client * @param {string|Array} opName - name of operation to be tested, array for multiple ops * @param {string} input - input text for test * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops */ function bakeOp(browser, opName, input, args=[]) { browser.perform(function() { console.log(`Current test: ${opName}`); }); utils.loadRecipe(browser, opName, input, args); browser.waitForElementVisible("#stale-indicator", 5000); utils.bake(browser); } /** @function * Clears the current recipe and tests a new operation. * * @param {Browser} browser - Nightwatch client * @param {string|Array} opName - name of operation to be tested, array for multiple ops * @param {string} input - input text * @param {string|RegExp} output - expected output * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops */ function testOp(browser, opName, input, output, args=[]) { bakeOp(browser, opName, input, args); utils.expectOutput(browser, output, true); } /** @function * Clears the current recipe and tests a new operation with HTML output. * * @param {Browser} browser - Nightwatch client * @param {string|Array} opName - name of operation to be tested array for multiple ops * @param {string} input - input text * @param {string} cssSelector - CSS selector for HTML output * @param {string|RegExp} output - expected output * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops */ function testOpHtml(browser, opName, input, cssSelector, output, args=[]) { bakeOp(browser, opName, input, args); if (typeof output === "string") { browser.expect.element("#output-html " + cssSelector).text.that.equals(output); } else if (output instanceof RegExp) { browser.expect.element("#output-html " + cssSelector).text.that.matches(output); } } /** @function * Clears the current recipe and tests a new Image-based operation. * * @param {Browser} browser - Nightwatch client * @param {string|Array} opName - name of operation to be tested array for multiple ops * @param {string} filename - filename of image file from samples directory * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops */ function testOpImage(browser, opName, filename, args=[]) { browser.perform(function() { console.log(`Current test: ${opName}`); }); utils.loadRecipe(browser, opName, "", args); utils.uploadFile(browser, filename); browser.waitForElementVisible("#stale-indicator", 5000); utils.bake(browser); browser .waitForElementVisible("#output-html img") .expect.element("#output-html img").to.have.css("width").which.matches(/^[^0]\d*px/); } /** @function * Clears the current recipe and tests a new File-based operation. * * @param {Browser} browser - Nightwatch client * @param {string|Array} opName - name of operation to be tested array for multiple ops * @param {string} filename - filename of file from samples directory * @param {string|boolean} cssSelector - CSS selector for HTML output or false for normal text output * @param {string|RegExp} output - expected output * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops * @param {number} [waitWindow=1000] - The number of milliseconds to wait for the output to be correct */ function testOpFile(browser, opName, filename, cssSelector, output, args=[], waitWindow=1000) { browser.perform(function() { console.log(`Current test: ${opName}`); }); utils.loadRecipe(browser, opName, "", args); utils.uploadFile(browser, filename); browser.pause(100).waitForElementVisible("#stale-indicator", 5000); utils.bake(browser); if (!cssSelector) { // Text output utils.expectOutput(browser, output, true, waitWindow); } else if (typeof output === "string") { // HTML output - string match browser.expect.element("#output-html " + cssSelector).text.that.equals(output); } else if (output instanceof RegExp) { // HTML output - RegEx match browser.expect.element("#output-html " + cssSelector).text.that.matches(output); } } ================================================ FILE: tests/browser/browserUtils.js ================================================ /** * Utility functions for browser tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ /** @function * Clears the recipe and input * * @param {Browser} browser - Nightwatch client */ function clear(browser) { browser .useCss() .click("#clr-recipe") .click("#clr-io") .waitForElementNotPresent("#rec-list li.operation") .expect.element("#input-text .cm-content").text.that.equals(""); } /** @function * Sets the input to the desired string * * @param {Browser} browser - Nightwatch client * @param {string} input - The text to populate the input with * @param {boolean} [type=true] - Whether to type the characters in by using sendKeys, * or to set the value of the editor directly (useful for special characters) */ function setInput(browser, input, type=true) { clear(browser); if (type) { browser .useCss() .sendKeys("#input-text .cm-content", input) .pause(100); } else { browser.execute(text => { window.app.setInput(text); }, [input]); browser.pause(100); } expectInput(browser, input); } /** @function * Triggers a bake * * @param {Browser} browser - Nightwatch client */ function bake(browser) { browser // Ensure we're not currently busy .waitForElementNotVisible("#output-loader", 5000) .expect.element("#bake span").text.to.equal("BAKE!"); browser .click("#bake") .waitForElementNotVisible("#stale-indicator", 5000) .waitForElementNotVisible("#output-loader", 5000); } /** @function * Sets the character encoding in the input or output * * @param {Browser} browser - Nightwatch client * @param {string} io - Either "input" or "output" * @param {string} enc - The encoding to be set */ function setChrEnc(browser, io, enc) { io = `#${io}-text`; browser .useCss() .waitForElementNotVisible("#snackbar-container", 6000) .click(io + " .chr-enc-value") .waitForElementVisible(io + " .chr-enc-select .cm-status-bar-select-scroll") .click("link text", enc) .waitForElementNotVisible(io + " .chr-enc-select .cm-status-bar-select-scroll") .expect.element(io + " .chr-enc-value").text.that.equals(enc); } /** @function * Sets the end of line sequence in the input or output * * @param {Browser} browser - Nightwatch client * @param {string} io - Either "input" or "output" * @param {string} eol - The sequence to set */ function setEOLSeq(browser, io, eol) { io = `#${io}-text`; browser .useCss() .waitForElementNotVisible("#snackbar-container", 6000) .click(io + " .eol-value") .waitForElementVisible(io + " .eol-select .cm-status-bar-select-content") .click(`${io} .cm-status-bar-select-content a[data-val=${eol}]`) .waitForElementNotVisible(io + " .eol-select .cm-status-bar-select-content") .expect.element(io + " .eol-value").text.that.equals(eol); } /** @function * Copies whatever is currently selected * * @param {Browser} browser - Nightwatch client */ function copy(browser) { browser.perform(function() { const actions = this.actions({async: true}); // Ctrl + Ins used as this works on Windows, Linux and Mac return actions .keyDown(browser.Keys.CONTROL) .keyDown(browser.Keys.INSERT) .keyUp(browser.Keys.INSERT) .keyUp(browser.Keys.CONTROL); }); } /** @function * Pastes into the target element * * @param {Browser} browser - Nightwatch client * @param {string} el - Target element selector */ function paste(browser, el) { browser .click(el) .perform(function() { const actions = this.actions({async: true}); // Shift + Ins used as this works on Windows, Linux and Mac return actions .keyDown(browser.Keys.SHIFT) .keyDown(browser.Keys.INSERT) .keyUp(browser.Keys.INSERT) .keyUp(browser.Keys.SHIFT); }) .pause(100); } /** @function * Loads a recipe and input * * @param {Browser} browser - Nightwatch client * @param {string|Array} opName - name of operation to be loaded, array for multiple ops * @param {string} input - input text for test * @param {Array|Array>} args - arguments, nested if multiple ops */ function loadRecipe(browser, opName, input, args) { let recipeConfig; if (typeof(opName) === "string") { recipeConfig = JSON.stringify([{ "op": opName, "args": args }]); } else if (opName instanceof Array) { recipeConfig = JSON.stringify( opName.map((op, i) => { return { op: op, args: args.length ? args[i] : [] }; }) ); } else { throw new Error("Invalid operation type. Must be string or array of strings. Received: " + typeof(opName)); } setInput(browser, input, false); browser .urlHash("recipe=" + recipeConfig) .waitForElementPresent("#rec-list li.operation"); } /** @function * Tests whether the output matches a given value * * @param {Browser} browser - Nightwatch client * @param {string|RegExp} expected - The expected output value * @param {boolean} [waitNotNull=false] - Wait for the output to not be empty before testing the value * @param {number} [waitWindow=1000] - The number of milliseconds to wait for the output to be correct */ function expectOutput(browser, expected, waitNotNull=false, waitWindow=1000) { if (waitNotNull && expected !== "") { browser.waitUntil(async function() { const output = await this.execute(function() { return window.app.manager.output.outputEditorView.state.doc.toString(); }); return output.length; }, waitWindow); } browser.execute(expected => { return window.app.manager.output.outputEditorView.state.doc.toString(); }, [expected], function({value}) { if (expected instanceof RegExp) { browser.expect(value).match(expected); } else { browser.expect(value).to.be.equal(expected); } }); } /** @function * Tests whether the input matches a given value * * @param {Browser} browser - Nightwatch client * @param {string|RegExp} expected - The expected input value */ function expectInput(browser, expected) { browser.execute(expected => { return window.app.manager.input.inputEditorView.state.doc.toString(); }, [expected], function({value}) { if (expected instanceof RegExp) { browser.expect(value).match(expected); } else { browser.expect(value).to.be.equal(expected); } }); } /** @function * Uploads a file using the #open-file input * * @param {Browser} browser - Nightwatch client * @param {string} filename - A path to a file in the samples directory */ function uploadFile(browser, filename) { const filepath = require("path").resolve(__dirname + "/../samples/" + filename); // The file input cannot be interacted with by nightwatch while it is hidden, // so we temporarily expose it for the purposes of this test. browser.execute(() => { document.getElementById("open-file").style.display = "block"; }); browser .pause(100) .setValue("#open-file", filepath) .pause(100); browser.execute(() => { document.getElementById("open-file").style.display = "none"; }); browser.waitForElementVisible("#input-text .cm-file-details"); } /** @function * Uploads a folder using the #open-folder input * * @param {Browser} browser - Nightwatch client * @param {string} foldername - A path to a folder in the samples directory */ function uploadFolder(browser, foldername) { const folderpath = require("path").resolve(__dirname + "/../samples/" + foldername); // The folder input cannot be interacted with by nightwatch while it is hidden, // so we temporarily expose it for the purposes of this test. browser.execute(() => { document.getElementById("open-folder").style.display = "block"; }); browser .pause(100) .setValue("#open-folder", folderpath) .pause(500); browser.execute(() => { document.getElementById("open-folder").style.display = "none"; }); browser.waitForElementVisible("#input-text .cm-file-details"); } module.exports = { clear: clear, setInput: setInput, bake: bake, setChrEnc: setChrEnc, setEOLSeq: setEOLSeq, copy: copy, paste: paste, loadRecipe: loadRecipe, expectOutput: expectOutput, expectInput: expectInput, uploadFile: uploadFile, uploadFolder: uploadFolder }; ================================================ FILE: tests/lib/TestRegister.mjs ================================================ /** * TestRegister.js * * This is so individual files can register their tests in one place, and * ensure that they will get run by the frontend. * * @author tlwr [toby@toby.codes] * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Chef from "../../src/core/Chef.mjs"; import Utils from "../../src/core/Utils.mjs"; import cliProgress from "cli-progress"; import log from "loglevel"; /** * Object to store and run the list of tests. * * @class * @constructor */ class TestRegister { /** * initialise with no tests */ constructor() { this.tests = []; this.apiTests = []; } /** * Add a list of tests to the register. * * @param {Object[]} tests */ addTests(tests) { this.tests = this.tests.concat(tests); } /** * Add a list of api tests to the register * @param {Object[]} tests */ addApiTests(tests) { this.apiTests = this.apiTests.concat(tests); } /** * Runs all the tests in the register. */ async runTests () { // Turn off logging to avoid messy errors log.setLevel("silent", false); const progBar = new cliProgress.SingleBar({ format: formatter, stopOnComplete: true }, cliProgress.Presets.shades_classic); const testResults = []; console.log("Running operation tests..."); progBar.start(this.tests.length, 0, { msg: "Setting up" }); for (const test of this.tests) { progBar.update(testResults.length, { msg: test.name }); const chef = new Chef(); const result = await chef.bake( test.input, test.recipeConfig, { returnType: "string" } ); const ret = { test: test, status: null, output: null, duration: result.duration }; if (result.error) { if (test.expectedError) { if (result.error.displayStr === test.expectedOutput) { ret.status = "passing"; } else { ret.status = "failing"; ret.output = [ "Expected", "\t" + test.expectedOutput.replace(/\n/g, "\n\t"), "Received", "\t" + result.error.displayStr.replace(/\n/g, "\n\t"), ].join("\n"); } } else { ret.status = "erroring"; ret.output = result.error.displayStr; } } else { if (test.expectedError) { ret.status = "failing"; ret.output = "Expected an error but did not receive one."; } else if (result.result === test.expectedOutput) { ret.status = "passing"; } else if ("expectedMatch" in test && test.expectedMatch.test(result.result)) { ret.status = "passing"; } else if ("unexpectedMatch" in test && !test.unexpectedMatch.test(result.result)) { ret.status = "passing"; } else { ret.status = "failing"; const expected = test.expectedOutput ? test.expectedOutput : test.expectedMatch ? test.expectedMatch.toString() : test.unexpectedMatch ? "to not find " + test.unexpectedMatch.toString() : "unknown"; ret.output = [ "Expected", "\t" + expected.replace(/\n/g, "\n\t"), "Received", "\t" + result.result.replace(/\n/g, "\n\t"), ].join("\n"); } } testResults.push(ret); progBar.increment(); } // Turn logging back on log.setLevel("info", false); return testResults; } /** * Run all api related tests and wrap results in report format */ async runApiTests() { const progBar = new cliProgress.SingleBar({ format: formatter, stopOnComplete: true }, cliProgress.Presets.shades_classic); const testResults = []; console.log("Running Node API tests..."); progBar.start(this.apiTests.length, 0, { msg: "Setting up" }); global.TESTING = true; for (const test of this.apiTests) { progBar.update(testResults.length, { msg: test.name }); const result = { test: test, status: null, output: null }; try { await test.run(); result.status = "passing"; } catch (e) { result.status = "erroring"; result.output = `${e.message}\nError: ${e.stack}`; } testResults.push(result); progBar.increment(); } return testResults; } } /** * Formatter for the progress bar * * @param {Object} options * @param {Object} params * @param {Object} payload * @returns {string} */ function formatter(options, params, payload) { const bar = options.barCompleteString.substr(0, Math.round(params.progress * options.barsize)) + options.barIncompleteString.substr(0, Math.round((1-params.progress) * options.barsize)); const percentage = Math.floor(params.progress * 100), duration = Math.floor((Date.now() - params.startTime) / 1000); let testName = payload.msg ? payload.msg : ""; if (params.value >= params.total) testName = "Tests completed"; testName = Utils.truncate(testName, 25).padEnd(25, " "); return `${testName} ${bar} ${params.value}/${params.total} | ${percentage}% | Duration: ${duration}s`; } // Export an instance to make a singleton export default new TestRegister(); ================================================ FILE: tests/lib/utils.mjs ================================================ /** * Utils for test suite * * @author d98762625@gmail.com * @author tlwr [toby@toby.codes] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ /** * Helper function to convert a status to an icon. * * @param {string} status * @returns {string} */ function statusToIcon(status) { return { erroring: "🔥", failing: "❌", passing: "✔️️", }[status] || "?"; } /** * Counts test statuses. * * @param {Object} testStatus * @param {Object} testResult */ function handleTestResult(testStatus, testResult) { testStatus.allTestsPassing = testStatus.allTestsPassing && testResult.status === "passing"; testStatus.counts[testResult.status] = (testStatus.counts[testResult.status] || 0) + 1; testStatus.counts.total += 1; if (testResult.duration > 2000) { console.log(`'${testResult.test.name}' took ${(testResult.duration / 1000).toFixed(1)}s to complete`); } } /** * Log each test result, count tests and failures. * * @param {Object} testStatus - object describing test run data * @param {Object[]} results - results from TestRegister */ export function logTestReport(testStatus, results) { results.forEach(r => handleTestResult(testStatus, r)); console.log(); for (const testStatusCount in testStatus.counts) { const count = testStatus.counts[testStatusCount]; if (count > 0) { console.log(testStatusCount.toUpperCase() + "\t" + count); } } console.log(); // Print error messages for tests that didn't pass results.filter(res => res.status !== "passing").forEach(testResult => { console.log([ statusToIcon(testResult.status), testResult.test.name ].join(" ")); if (testResult.output) { console.log( testResult.output .trim() .replace(/^/, "\t") .replace(/\n/g, "\n\t") ); } }); console.log(); process.exit(testStatus.allTestsPassing ? 0 : 1); } /** * Fail if the process takes longer than 60 seconds. */ export function setLongTestFailure() { const timeLimit = 120; setTimeout(function() { console.log(`Tests took longer than ${timeLimit} seconds to run, returning.`); process.exit(1); }, timeLimit * 1000); } ================================================ FILE: tests/node/assertionHandler.mjs ================================================ /** * assertionHandler.mjs * * Pair native node assertions with a description for * the benefit of the TestRegister. * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ /* eslint no-console: 0 */ /** * Print useful stack on error */ const wrapRun = (run) => async () => { try { await run(); } catch (e) { console.dir(e); throw e; } }; /** * it - wrapper for assertions to provide a helpful description * to the TestRegister * @namespace ApiTests * @param {String} description - The description of the test * @param {Function} assertion - The test * * @example * // One assertion * it("should run one assertion", () => assert.equal(1,1)) * * @example * // multiple assertions * it("should handle multiple assertions", () => { * assert.equal(1,1) * assert.notEqual(3,4) * }) * * @example * // async assertions * it("should handle async", async () => { * let r = await asyncFunc() * assert(r) * }) */ export function it(name, run) { return { name: `Node API: ${name}`, run: wrapRun(run), }; } export default it; ================================================ FILE: tests/node/consumers/cjs-consumer.js ================================================ /** * Tests to ensure that a consuming app can use CJS require * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ const assert = require("assert"); require("cyberchef").then(chef => { const d = chef.bake("Testing, 1 2 3", [ chef.toHex, chef.reverse, { op: chef.unique, args: { delimiter: "Space", } }, { op: chef.multiply, args: { delimiter: "Space", } } ]); assert.equal(d.value, "630957449041920"); }); ================================================ FILE: tests/node/consumers/esm-consumer.mjs ================================================ /** * Tests to ensure that a consuming app can use ESM imports * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import assert from "assert"; import chef from "cyberchef"; import { bake, toHex, reverse, unique, multiply } from "cyberchef"; const a = bake("Testing, 1 2 3", [ toHex, reverse, { op: unique, args: { delimiter: "Space", } }, { op: multiply, args: { delimiter: "Space", } } ]); assert.equal(a.value, "630957449041920"); const b = chef.bake("Testing, 1 2 3", [ chef.toHex, chef.reverse, { op: chef.unique, args: { delimiter: "Space", } }, { op: chef.multiply, args: { delimiter: "Space", } } ]); assert.equal(b.value, "630957449041920"); ================================================ FILE: tests/node/index.mjs ================================================ /* eslint no-console: 0 */ /** * Node API Test Runner * * @author d98762625 [d98762625@gmail.com] * @author tlwr [toby@toby.codes] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import { setLongTestFailure, logTestReport, } from "../lib/utils.mjs"; import TestRegister from "../lib/TestRegister.mjs"; import "./tests/nodeApi.mjs"; import "./tests/operations.mjs"; import "./tests/File.mjs"; import "./tests/Dish.mjs"; import "./tests/NodeDish.mjs"; import "./tests/Utils.mjs"; import "./tests/Categories.mjs"; import "./tests/lib/BigIntUtils.mjs"; const testStatus = { allTestsPassing: true, counts: { total: 0, } }; setLongTestFailure(); const logOpsTestReport = logTestReport.bind(null, testStatus); (async function() { const results = await TestRegister.runApiTests(); logOpsTestReport(results); })(); ================================================ FILE: tests/node/tests/Categories.mjs ================================================ import TestRegister from "../../lib/TestRegister.mjs"; import Categories from "../../../src/core/config/Categories.json" assert {type: "json"}; import OperationConfig from "../../../src/core/config/OperationConfig.json" assert {type: "json"}; import it from "../assertionHandler.mjs"; import assert from "assert"; TestRegister.addApiTests([ it("Categories: operations should be in a category", () => { const catOps = []; Categories.forEach(cat => { catOps.push(...cat.ops); }); for (const op in OperationConfig) { assert(catOps.includes(op), `'${op}' operation is not present in any category`); } }), ]); ================================================ FILE: tests/node/tests/Dish.mjs ================================================ import TestRegister from "../../lib/TestRegister.mjs"; import Dish from "../../../src/core/Dish.mjs"; import it from "../../node/assertionHandler.mjs"; import assert from "assert"; TestRegister.addApiTests([ it("Dish - presentAs: should exist", () => { const dish = new Dish(); assert(dish.presentAs); }), ]); ================================================ FILE: tests/node/tests/File.mjs ================================================ import assert from "assert"; import it from "../assertionHandler.mjs"; import TestRegister from "../../lib/TestRegister.mjs"; import File from "../../../src/node/File.mjs"; import {zip, Dish} from "../../../src/node/index.mjs"; TestRegister.addApiTests([ it("File: should exist", () => { assert(File); }), it("File: Should have same properties as DOM File object", () => { const uint8Array = new Uint8Array(Buffer.from("hello")); const file = new File([uint8Array], "name.txt"); assert.equal(file.name, "name.txt"); assert(typeof file.lastModified, "number"); assert(file.lastModifiedDate instanceof Date); assert.equal(file.size, uint8Array.length); assert.equal(file.type, "application/unknown"); }), it("File: Should determine the type of a file", () => { const zipped = zip("hello"); const file = new File([zipped.value]); assert(file); assert.strictEqual(file.type, "application/zip"); }), it("File: unknown type should have a type of application/unknown", () => { const uint8Array = new Uint8Array(Buffer.from("hello")); const file = new File([uint8Array], "sample.txt"); assert.strictEqual(file.type, "application/unknown"); }), it("File: should be able to make a dish from it", () => { const uint8Array = new Uint8Array(Buffer.from("hello")); const file = new File([uint8Array], "sample.txt"); try { const dish = new Dish(file, 7); assert.ok(dish.valid()); } catch (e) { assert.fail(e.message); } }), it("File: should allow dish to translate to ArrayBuffer", () => { const uint8Array = new Uint8Array(Buffer.from("hello")); const file = new File([uint8Array], "sample.txt"); try { const dish = new Dish(file, 7); assert.ok(dish.value); dish.get(4); assert.strictEqual(dish.type, 4); assert.ok(dish.valid()); } catch (e) { assert.fail(e.message); } }), it("File: should allow dish to translate from ArrayBuffer to File", () => { const uint8Array = new Uint8Array(Buffer.from("hello")); const file = new File([uint8Array], "sample.txt"); try { const dish = new Dish(file, 7); assert.ok(dish.value); // translate to ArrayBuffer dish.get(4); assert.ok(dish.valid()); // translate back to File dish.get(7); assert.ok(dish.valid()); } catch (e) { assert.fail(e.message); } }) ]); ================================================ FILE: tests/node/tests/NodeDish.mjs ================================================ import assert from "assert"; import it from "../assertionHandler.mjs"; import fs from "fs"; import BigNumber from "bignumber.js"; import { Dish, toBase32, SHA3 } from "../../../src/node/index.mjs"; import File from "../../../src/node/File.mjs"; import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addApiTests([ it("Composable Dish: Should have top level Dish object", () => { assert.ok(Dish); }), it("Composable Dish: Should construct empty dish object", () => { const dish = new Dish(); assert.strictEqual(dish.value.byteLength, new ArrayBuffer(0).byteLength); assert.strictEqual(dish.type, 4); }), it("Composable Dish: constructed dish should have apply prototype functions", () => { const dish = new Dish(); assert.ok(dish.apply); assert.throws(() => dish.someInvalidFunction()); }), it("Composable Dish: composed function returns another dish", () => { const result = new Dish("some input").apply(toBase32); assert.ok(result instanceof Dish); }), it("Composable dish: infers type from input if needed", () => { const dish = new Dish("string input"); assert.strictEqual(dish.type, 1); const numberDish = new Dish(333); assert.strictEqual(numberDish.type, 2); const arrayBufferDish = new Dish(Buffer.from("some buffer input").buffer); assert.strictEqual(arrayBufferDish.type, 4); const byteArrayDish = new Dish(Buffer.from("some buffer input")); assert.strictEqual(byteArrayDish.type, 0); const JSONDish = new Dish({key: "value"}); assert.strictEqual(JSONDish.type, 6); }), it("Composable dish: Buffer type dishes should be converted to strings", () => { fs.writeFileSync("test.txt", "abc"); const dish = new Dish(fs.readFileSync("test.txt")); assert.strictEqual(dish.type, 0); fs.unlinkSync("test.txt"); }), it("Composable Dish: apply should allow set of arguments for operation", () => { const result = new Dish("input").apply(SHA3, {size: "256"}); assert.strictEqual(result.toString(), "7640cc9b7e3662b2250a43d1757e318bb29fb4860276ac4373b67b1650d6d3e3"); }), it("Composable Dish: apply functions can be chained", () => { const result = new Dish("input").apply(toBase32).apply(SHA3, {size: "224"}); assert.strictEqual(result.toString(), "493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0"); }), it("Dish translation: ArrayBuffer to ArrayBuffer", () => { const dish = new Dish(new ArrayBuffer(10), 4); dish.get("array buffer"); assert.strictEqual(dish.value.byteLength, 10); assert.strictEqual(dish.type, 4); }), it("Dish translation: ArrayBuffer and String", () => { const dish = new Dish("some string", 1); dish.get("array buffer"); assert.strictEqual(dish.type, 4); assert.deepStrictEqual(dish.value, new Uint8Array([0x73, 0x6f, 0x6d, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67]).buffer); assert.deepEqual(dish.value.byteLength, 11); dish.get("string"); assert.strictEqual(dish.type, 1); assert.strictEqual(dish.value, "some string"); }), it("Dish translation: ArrayBuffer and number", () => { const dish = new Dish(100, 2); dish.get(4); assert.strictEqual(dish.type, 4); assert.deepStrictEqual(dish.value, new Uint8Array([0x31, 0x30, 0x30]).buffer); assert.strictEqual(dish.value.byteLength, 3); // Check the data in ArrayBuffer represents 100 as a string. const view = new DataView(dish.value, 0); assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2)), "100"); dish.get("number"); assert.strictEqual(dish.type, 2); assert.strictEqual(dish.value, 100); }), it("Dish translation: ArrayBuffer and byte array", () => { const dish = new Dish(new Uint8Array([1, 2, 3]), 0); dish.get(4); // Check intermediate value const check = new Uint8Array(dish.value); assert.deepEqual(check, new Uint8Array([1, 2, 3])); // Check converts back OK dish.get(0); assert.deepEqual(dish.value, [1, 2, 3]); }), it("Dish translation: ArrayBuffer and HTML", () => { const html = ` Click here `.replace(/\n|\s{4}/g, ""); // remove newlines, tabs const dish = new Dish(html, Dish.HTML); dish.get(4); dish.get(3); assert.strictEqual(dish.value, "Click here"); }), it("Dish translation: ArrayBuffer and BigNumber", () => { const number = BigNumber(4001); const dish = new Dish(number, Dish.BIG_NUMBER); dish.get(Dish.ARRAY_BUFFER); assert.deepStrictEqual(dish.value, new Uint8Array([0x34, 0x30, 0x30, 0x31]).buffer); assert.strictEqual(dish.value.byteLength, 4); // Check the data in ArrayBuffer represents 4001 as a string. const view = new DataView(dish.value, 0); assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)), "4001"); dish.get(5); assert.deepStrictEqual(dish.value, number); }), it("Dish translation: ArrayBuffer and JSON", () => { const jsonString = "{\"a\": 123455, \"b\": { \"aa\": [1,2,3]}}"; const dish = new Dish(JSON.parse(jsonString), Dish.JSON); dish.get(Dish.ARRAY_BUFFER); dish.get(Dish.JSON); assert.deepStrictEqual(dish.value, JSON.parse(jsonString)); }), it("Dish translation: ArrayBuffer and File", () => { const file = new File("abcd", "unknown"); const dish = new Dish(file, Dish.FILE); dish.get(Dish.ARRAY_BUFFER); assert.deepStrictEqual(dish.value, new Uint8Array([0x61, 0x62, 0x63, 0x64]).buffer); assert.strictEqual(dish.value.byteLength, 4); // Check the data in ArrayBuffer represents "abcd" const view = new DataView(dish.value, 0); assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)), "abcd"); dish.get(Dish.FILE); assert.deepStrictEqual(dish.value.data, file.data); assert.strictEqual(dish.value.name, file.name); assert.strictEqual(dish.value.type, file.type); // Do not test lastModified }), it("Dish translation: ArrayBuffer and ListFile", () => { const file1 = new File("abcde", "unknown"); const file2 = new File("fghijk", "unknown"); const dish = new Dish([file1, file2], Dish.LIST_FILE); dish.get(Dish.ARRAY_BUFFER); assert.deepStrictEqual(dish.value, [new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]), new Uint8Array([0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b])]); assert.strictEqual(dish.value.length, 2); dish.get(Dish.LIST_FILE); const dataArray = new Uint8Array(dish.value[0].data); // cant store chars in a Uint8Array, so make it a normal one. const actual = Array.prototype.slice.call(dataArray).map(c => String.fromCharCode(c)).join(""); assert.strictEqual(actual, "abcdefghijk"); }), ]); ================================================ FILE: tests/node/tests/Utils.mjs ================================================ import TestRegister from "../../lib/TestRegister.mjs"; import Utils from "../../../src/core/Utils.mjs"; import it from "../assertionHandler.mjs"; import assert from "assert"; TestRegister.addApiTests([ it("Utils: should parse six backslashes correctly", () => { assert.equal(Utils.parseEscapedChars("\\\\\\\\\\\\"), "\\\\\\"); }), it("Utils: should parse escaped quotes correctly", () => { assert.equal(Utils.parseEscapedChars("\\'"), "'"); }), it("Utils: should parse escaped quotes and backslashes correctly", () => { assert.equal(Utils.parseEscapedChars("\\\\'"), "\\'"); }), it("Utils: should parse escaped quotes and escaped backslashes correctly", () => { assert.equal(Utils.parseEscapedChars("\\\\\\'"), "\\'"); }), it("Utils: should replace delete character", () => { assert.equal( Utils.printable("\x7e\x7f\x80\xa7", false, true), "\x7e...", ); }), ]); ================================================ FILE: tests/node/tests/lib/BigIntUtils.mjs ================================================ import TestRegister from "../../../lib/TestRegister.mjs"; import { parseBigInt, egcd, modPow } from "../../../../src/core/lib/BigIntUtils.mjs"; import it from "../../assertionHandler.mjs"; import assert from "assert"; TestRegister.addApiTests([ // ===== parseBigInt tests ===== it("BigIntUtils: parseBigInt - decimal number", () => { const value = parseBigInt("1", "test value"); assert.deepStrictEqual(value, BigInt("1")); }), it("BigIntUtils: parseBigInt - large decimal", () => { const value = parseBigInt("123456789012345678901234567890", "test value"); assert.deepStrictEqual(value, BigInt("123456789012345678901234567890")); }), it("BigIntUtils: parseBigInt - hexadecimal lowercase", () => { const value = parseBigInt("0xff", "test value"); assert.deepStrictEqual(value, BigInt("255")); }), it("BigIntUtils: parseBigInt - hexadecimal uppercase", () => { const value = parseBigInt("0xFF", "test value"); assert.deepStrictEqual(value, BigInt("255")); }), it("BigIntUtils: parseBigInt - large hexadecimal", () => { const value = parseBigInt("0x123456789ABCDEF", "test value"); assert.deepStrictEqual(value, BigInt("0x123456789ABCDEF")); }), it("BigIntUtils: parseBigInt - whitespace trimming", () => { const value = parseBigInt(" 42 ", "test value"); assert.deepStrictEqual(value, BigInt("42")); }), it("BigIntUtils: parseBigInt - invalid input (text)", () => { assert.throws(() => parseBigInt("test", "test value"), { name: "Error", message: "test value must be decimal or hex (0x...)" }); }), it("BigIntUtils: parseBigInt - invalid input (hex without prefix)", () => { assert.throws(() => parseBigInt("FF", "test value"), { name: "Error", message: "test value must be decimal or hex (0x...)" }); }), it("BigIntUtils: parseBigInt - invalid input (mixed)", () => { assert.throws(() => parseBigInt("12abc", "test value"), { name: "Error", message: "test value must be decimal or hex (0x...)" }); }), // ===== egcd tests ===== it("BigIntUtils: egcd - basic coprime", () => { const a = BigInt("36"); const b = BigInt("48"); const gcd = BigInt("12"); const bezout1 = BigInt("-1"); const bezout2 = BigInt("1"); assert.deepStrictEqual(egcd(a, b), [gcd, bezout1, bezout2]); }), it("BigIntUtils: egcd - coprime numbers", () => { const [g, x, y] = egcd(BigInt("3"), BigInt("11")); assert.strictEqual(g, BigInt("1")); // Verify Bézout identity: a*x + b*y = gcd assert.strictEqual(BigInt("3") * x + BigInt("11") * y, g); }), it("BigIntUtils: egcd - non-coprime numbers", () => { const [g, x, y] = egcd(BigInt("240"), BigInt("46")); assert.strictEqual(g, BigInt("2")); // Verify Bézout identity assert.strictEqual(BigInt("240") * x + BigInt("46") * y, g); }), it("BigIntUtils: egcd - with zero", () => { const [g, x, y] = egcd(BigInt("17"), BigInt("0")); assert.strictEqual(g, BigInt("17")); assert.strictEqual(x, BigInt("1")); assert.strictEqual(y, BigInt("0")); }), it("BigIntUtils: egcd - identical numbers", () => { const [g, x, y] = egcd(BigInt("42"), BigInt("42")); assert.strictEqual(g, BigInt("42")); // Verify Bézout identity assert.strictEqual(BigInt("42") * x + BigInt("42") * y, g); }), it("BigIntUtils: egcd - large numbers", () => { const a = BigInt("123456789012345678901234567890"); const b = BigInt("987654321098765432109876543210"); const [g, x, y] = egcd(a, b); // Verify Bézout identity assert.strictEqual(a * x + b * y, g); }), // ===== modPow tests ===== it("BigIntUtils: modPow - basic", () => { // 2^10 mod 1000 = 1024 mod 1000 = 24 const result = modPow(BigInt("2"), BigInt("10"), BigInt("1000")); assert.strictEqual(result, BigInt("24")); }), it("BigIntUtils: modPow - RSA-like example", () => { // Common RSA public exponent const base = BigInt("123456789"); const exp = BigInt("65537"); const mod = BigInt("999999999999"); const result = modPow(base, exp, mod); // Result should be less than modulus assert(result < mod); assert(result >= BigInt("0")); }), it("BigIntUtils: modPow - exponent zero", () => { // Any number^0 = 1 const result = modPow(BigInt("999"), BigInt("0"), BigInt("100")); assert.strictEqual(result, BigInt("1")); }), it("BigIntUtils: modPow - base zero", () => { // 0^n = 0 const result = modPow(BigInt("0"), BigInt("5"), BigInt("100")); assert.strictEqual(result, BigInt("0")); }), it("BigIntUtils: modPow - large exponent", () => { // Test with very large exponent (efficient algorithm should handle this) const result = modPow(BigInt("3"), BigInt("1000000"), BigInt("1000000007")); assert(result >= BigInt("0")); assert(result < BigInt("1000000007")); }), it("BigIntUtils: modPow - modular inverse verification", () => { // If a*x . 1 (mod m), then modPow(a, 1, m) * x . 1 (mod m) const a = BigInt("3"); const m = BigInt("11"); const x = BigInt("4"); // inverse of 3 mod 11 const result = modPow(a, BigInt("1"), m) * x % m; assert.strictEqual(result, BigInt("1")); }), ]); ================================================ FILE: tests/node/tests/nodeApi.mjs ================================================ /* eslint no-console: 0 */ /** * nodeApi.js * * Test node api utilities * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import assert from "assert"; import it from "../assertionHandler.mjs"; import chef from "../../../src/node/index.mjs"; import { OperationError, ExcludedOperationError } from "../../../src/core/errors/index.mjs"; import NodeDish from "../../../src/node/NodeDish.mjs"; import { toBase32, magic} from "../../../src/node/index.mjs"; import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addApiTests([ it("should have some operations", () => { assert(chef); assert(chef.toBase32); assert(chef.setUnion); assert(!chef.randomFunction); }), it("should export other functions at top level", () => { assert(toBase32); }), it("should be synchronous", () => { try { const result = chef.toBase32("input"); assert.notEqual("something", result); } catch (e) { // shouldnt reach here assert(false); } try { const fail = chef.setUnion("1"); // shouldnt get here assert(!fail || false); } catch (e) { assert(true); } }), it("should not catch Errors", () => { try { chef.setUnion("1"); assert(false); } catch (e) { assert(e instanceof OperationError); } }), it("should accept arguments in object format for operations", () => { const result = chef.setUnion("1 2 3 4:3 4 5 6", { itemDelimiter: " ", sampleDelimiter: ":" }); assert.equal(result.value, "1 2 3 4 5 6"); }), it("should accept just some of the optional arguments being overriden", () => { const result = chef.setIntersection("1 2 3 4 5\\n\\n3 4 5", { itemDelimiter: " ", }); assert.equal(result.value, "3 4 5"); }), it("should accept no override arguments and just use the default values", () => { const result = chef.powerSet("1,2,3"); assert.equal(result.value, "\n3\n2\n1\n2,3\n1,3\n1,2\n1,2,3\n"); }), it("should return an object with a .to method", () => { const result = chef.toBase32("input"); assert(result.to); assert.equal(result.to("string"), "NFXHA5LU"); }), it("should return an object with a .get method", () => { const result = chef.toBase32("input"); assert(result.get); assert.equal(result.get("string"), "NFXHA5LU"); }), it("should return a NodeDish", async () => { const result = chef.toBase32("input"); assert(result instanceof NodeDish); }), it("should coerce to a string as you expect", () => { const result = chef.fromBase32(chef.toBase32("something")); assert.equal(String(result), "something"); // This kind of coercion uses toValue assert.equal(""+result, "NaN"); }), it("should coerce to a number as you expect", () => { const result = chef.fromBase32(chef.toBase32("32")); assert.equal(3 + result, 35); }), it("chef.help: should exist", () => { assert(chef.help); }), it("chef.help: should describe a operation", () => { const result = chef.help("tripleDESDecrypt"); assert.strictEqual(result[0].name, "Triple DES Decrypt"); assert.strictEqual(result[0].module, "Ciphers"); assert.strictEqual(result[0].inputType, "string"); assert.strictEqual(result[0].outputType, "string"); assert.strictEqual(result[0].description, "Triple DES applies DES three times to each block to increase key size.

        Key: Triple DES uses a key length of 24 bytes (192 bits).

        IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

        Padding: In CBC and ECB mode, PKCS#7 padding will be used as a default."); assert.strictEqual(result[0].args.length, 5); }), it("chef.help: null for invalid operation", () => { const result = chef.help("some invalid function name"); assert.strictEqual(result, null); }), it("chef.help: takes a wrapped operation as input", () => { const result = chef.help(chef.toBase32); assert.strictEqual(result[0].name, "To Base32"); assert.strictEqual(result[0].module, "Default"); }), it("chef.help: returns multiple results", () => { const result = chef.help("base 64"); assert.strictEqual(result.length, 13); }), it("chef.help: looks in description for matches too", () => { // string only in one operation's description. const result = chef.help("Converts a unit of data to another format."); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].name, "Convert data units"); }), it("chef.help: lists name matches before desc matches", () => { const result = chef.help("Checksum"); assert.ok(result[0].name.includes("Checksum")); assert.ok(result[1].name.includes("Checksum")); assert.strictEqual(result[result.length - 1].name.includes("Checksum"), false); assert.ok(result[result.length - 1].description.includes("checksum")); }), it("chef.help: exact name match only returns one result", () => { const result = chef.help("MD5"); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].name, "MD5"); }), it("chef.help: exact match ignores whitespace", () => { const result = chef.help("tobase64"); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].name, "To Base64"); }), it("chef.bake: should exist", () => { assert(chef.bake); }), it("chef.bake: should return NodeDish", () => { const result = chef.bake("input", "to base 64"); assert(result instanceof NodeDish); }), it("chef.bake: should take an input and an op name and perform it", () => { const result = chef.bake("some input", "to base 32"); assert.strictEqual(result.toString(), "ONXW2ZJANFXHA5LU"); }), it("chef.bake: should complain if recipe isnt a valid object", () => { assert.throws(() => chef.bake("some input", 3264), { name: "TypeError", message: "Recipe can only contain function names or functions" }); }), it("chef.bake: Should complain if string op is invalid", () => { assert.throws(() => chef.bake("some input", "not a valid operation"), { name: "TypeError", message: "Couldn't find an operation with name 'not a valid operation'." }); }), it("chef.bake: Should take an input and an operation and perform it", () => { const result = chef.bake("https://google.com/search?q=help", chef.parseURI); assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = help\n"); }), it("chef.bake: Should complain if an invalid operation is inputted", () => { assert.throws(() => chef.bake("https://google.com/search?q=help", () => {}), { name: "TypeError", message: "Inputted function not a Chef operation." }); }), it("chef.bake: accepts an array of operation names and performs them all in order", () => { const result = chef.bake("https://google.com/search?q=that's a complicated question", ["URL encode", "URL decode", "Parse URI"]); assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n"); }), it("chef.bake: forgiving with operation names", () =>{ const result = chef.bake("https://google.com/search?q=that's a complicated question", ["urlencode", "url decode", "parseURI"]); assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n"); }), it("chef.bake: forgiving with operation names", () =>{ const result = chef.bake("hello", ["to base 64"]); assert.strictEqual(result.toString(), "aGVsbG8="); }), it("chef.bake: if recipe is empty array, return input as dish", () => { const result = chef.bake("some input", []); assert.strictEqual(result.toString(), "some input"); assert(result instanceof NodeDish, "Result is not instance of NodeDish"); }), it("chef.bake: accepts an array of operations as recipe", () => { const result = chef.bake("https://google.com/search?q=that's a complicated question", [chef.URLEncode, chef.URLDecode, chef.parseURI]); assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n"); }), it("should complain if an invalid operation is inputted as part of array", () => { assert.throws(() => chef.bake("something", [() => {}]), { name: "TypeError", message: "Inputted function not a Chef operation." }); }), it("chef.bake: should take single JSON object describing op and args OBJ", () => { const result = chef.bake("some input", { op: chef.toHex, args: { Delimiter: "Colon" } }); assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74"); }), it("chef.bake: should take single JSON object desribing op with optional args", () => { const result = chef.bake("some input", { op: chef.toHex, }); assert.strictEqual(result.toString(), "73 6f 6d 65 20 69 6e 70 75 74"); }), it("chef.bake: should take single JSON object describing op and args ARRAY", () => { const result = chef.bake("some input", { op: chef.toHex, args: ["Colon"] }); assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74"); }), it("chef.bake: should error if op in JSON is not chef op", () => { assert.throws(() => chef.bake("some input", { op: () => {}, args: ["Colon"], }), { name: "TypeError", message: "Inputted function not a Chef operation." }); }), it("chef.bake: should take multiple ops in JSON object form, some ops by string", () => { const result = chef.bake("some input", [ { op: chef.toHex, args: ["Colon"] }, { op: "to octal", args: { delimiter: "Semi-colon", } } ]); 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"); }), it("chef.bake: should take multiple ops in JSON object form, some without args", () => { const result = chef.bake("some input", [ { op: chef.toHex, }, { op: "to octal", args: { delimiter: "Semi-colon", } } ]); 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"); }), it("chef.bake: should handle op with multiple args", () => { const result = chef.bake("some input", { op: "to morse code", args: { formatOptions: "Dash/Dot", wordDelimiter: "Comma", letterDelimiter: "Backslash", } }); assert.strictEqual(result.toString(), "DotDotDot\\DashDashDash\\DashDash\\Dot,DotDot\\DashDot\\DotDashDashDot\\DotDotDash\\Dash"); }), it("chef.bake: should take compact JSON format from Chef Website as recipe", () => { 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]}]); assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something"); }), it("chef.bake: should accept Clean JSON format from Chef website as recipe", () => { 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] } ]); assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something"); }), it("chef.bake: should accept Clean JSON format from Chef website - args optional", () => { const result = chef.bake("some input", [ { "op": "To Morse Code" }, { "op": "Hex to PEM", "args": ["SOMETHING"] }, { "op": "To Snake case", "args": [false] } ]); assert.strictEqual(result.toString(), "begin_something_aaaaaaaaaaaaaa_end_something"); }), it("chef.bake: should accept operation names from Chef Website which contain forward slash", () => { const result = chef.bake("I'll have the test salmon", [ { "op": "Find / Replace", "args": [{ "option": "Regex", "string": "test" }, "good", true, false, true, false]} ]); assert.strictEqual(result.toString(), "I'll have the good salmon"); }), it("chef.bake: should accept operation names from Chef Website which contain a hyphen", () => { const result = chef.bake("I'll have the test salmon", [ { "op": "Adler-32 Checksum", "args": [] } ]); assert.strictEqual(result.toString(), "6e4208f8"); }), it("chef.bake: should accept operation names from Chef Website which contain a period", () => { 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", [ { "op": "Parse ASN.1 hex string", "args": [0, 32] } ]); assert.strictEqual(result.toString(), `SEQUENCE INTEGER 05 IA5String 'Anybody there?' `); }), it("Excluded operations: throw a sensible error when you try and call one", () => { try { chef.fork(); } catch (e) { assert.strictEqual(e.type, "ExcludedOperationError"); assert.strictEqual(e.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef."); } }), it("chef.bake: cannot accept flowControl operations in recipe", () => { assert.throws(() => chef.bake("some input", "magic"), { name: "TypeError", message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API" }); assert.throws(() => chef.bake("some input", magic), { name: "TypeError", message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API" }); assert.throws(() => chef.bake("some input", ["to base 64", "magic"]), { name: "TypeError", message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API" }); }), it("Excluded operations: throw a sensible error when you try and call one", () => { assert.throws(chef.fork, (err) => { assert(err instanceof ExcludedOperationError); assert.deepEqual(err.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef."); return true; }, "Unexpected error type" ); assert.throws(chef.javaScriptBeautify, (err) => { assert(err instanceof ExcludedOperationError); assert.deepEqual(err.message, "Sorry, the JavaScriptBeautify operation is not available in the Node.js version of CyberChef."); return true; }, "Unexpected error type" ); }), it("Operation arguments: should be accessible from operation object if op has array arg", () => { assert.ok(chef.toCharcode.args); assert.deepEqual(chef.unzip.args, { password: { type: "binaryString", value: "", }, verifyResult: { type: "boolean", value: false, } }); }), it("Operation arguments: should have key for each argument in operation", () => { assert.ok(chef.convertDistance.args.inputUnits); assert.ok(chef.convertDistance.args.outputUnits); assert.strictEqual(chef.bitShiftRight.args.amount.type, "number"); assert.strictEqual(chef.bitShiftRight.args.amount.value, 1); assert.strictEqual(chef.bitShiftRight.args.type.type, "option"); assert.ok(Array.isArray(chef.bitShiftRight.args.type.options)); }), it("Operation arguments: should list all options excluding subheadings", () => { // First element (subheading) removed assert.equal(chef.convertDistance.args.inputUnits.options[0], "Nanometres (nm)"); assert.equal(chef.defangURL.args.process.options[1], "Only full URLs"); }), ]); ================================================ FILE: tests/node/tests/operations.mjs ================================================ /* eslint no-console: 0 */ /** * nodeApi.js * * Test node api operations * * Aim of these tests is to ensure each arg type is * handled correctly by the wrapper. * * Generally just checking operations that use external dependencies to ensure * it behaves as expected in Node. * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import assert from "assert"; import it from "../assertionHandler.mjs"; import fs from "fs"; import { addLineNumbers, adler32Checksum, AESDecrypt, affineCipherDecode, affineCipherEncode, bifidCipherEncode, bitShiftRight, cartesianProduct, CSSMinify, toBase64, toHex } from "../../../src/node/index.mjs"; import chef from "../../../src/node/index.mjs"; import TestRegister from "../../lib/TestRegister.mjs"; import File from "../../../src/node/File.mjs"; global.File = File; TestRegister.addApiTests([ it("ADD: toggleString argument", () => { const result = chef.ADD("sample input", { key: { string: "some key", option: "utf8" } }); assert.equal(result.toString(), "\xe6\xd0\xda\xd5\x8c\xd0\x85\xe2\xe1\xdf\xe2\xd9"); }), it("ADD: default option toggleString argument", () => { const result = chef.ADD(3, { key: "4", }); assert.strictEqual(result.toString(), "7"); }), it("addLineNumbers: No arguments", () => { const result = addLineNumbers("sample input"); assert.equal(result.toString(), "1 sample input"); }), it("adler32Checksum: No args", () => { const result = adler32Checksum("sample input"); assert.equal(result.toString(), "1f2304d3"); }), it("AES decrypt: toggleString and option", () => { const result = AESDecrypt("4a123af235a507bbc9d5871721d61b98504d569a9a5a7847e2d78315fec7", { key: { string: "some longer key1", option: "utf8", }, iv: { string: "some iv some iv1", option: "utf8", }, mode: "OFB", }); assert.equal(result.toString(), "a slightly longer sampleinput?"); }), it("AffineCipherDecode: number input", () => { const result = affineCipherDecode("some input", { a: 7, b: 4 }); assert.strictEqual(result.toString(), "cuqa ifjgr"); }), it("affineCipherEncode: number input", () => { const result = affineCipherEncode("some input", { a: 11, b: 6 }); assert.strictEqual(result.toString(), "weiy qtpsh"); }), it("analyzeHash", () => { const result = chef.analyseHash(chef.MD5("some input")); const expected = `Hash length: 32 Byte length: 16 Bit length: 128 Based on the length, this hash could have been generated by one of the following hashing functions: MD5 MD4 MD2 HAVAL-128 RIPEMD-128 Snefru Tiger-128`; assert.strictEqual(result.toString(), expected); }), it("AND", () => { const result = chef.AND("Scot-free", { key: { string: "Raining Cats and Dogs", option: "utf8", } }); assert.strictEqual(result.toString(), "Raid)fb A"); }), it("atBash Cipher", () => { const result = chef.atbashCipher("Happy as a Clam"); assert.strictEqual(result.toString(), "Szkkb zh z Xozn"); }), it("Bcrypt", async () => { const result = await chef.bcrypt("Put a Sock In It"); const strResult = result.toString(); assert.equal(strResult.length, 60); assert.equal(strResult.slice(0, 7), "$2a$10$"); }), it("bcryptCompare", async() => { const result = await chef.bcryptCompare("Put a Sock In It", { hash: "$2a$10$2rT4a3XnIecBsd1H33dMTuyYE1HJ1n9F.V2rjQtAH73rh1qvOf/ae", }); assert.strictEqual(result.toString(), "Match: Put a Sock In It"); }), it("Bcrypt Parse", async () => { const result = await chef.bcryptParse("$2a$10$ODeP1.6fMsb.ENk2ngPUCO7qTGVPyHA9TqDVcyupyed8FjsiF65L6"); const expected = `Rounds: 10 Salt: $2a$10$ODeP1.6fMsb.ENk2ngPUCO Password hash: 7qTGVPyHA9TqDVcyupyed8FjsiF65L6 Full hash: $2a$10$ODeP1.6fMsb.ENk2ngPUCO7qTGVPyHA9TqDVcyupyed8FjsiF65L6`; assert.strictEqual(result.toString(), expected); }), it("bifid cipher decode", () => { const result = chef.bifidCipherDecode("Vhef Qnte Ke Xfhz Mxon Bmgf", { keyword: "Alpha", }); assert.strictEqual(result.toString(), "What Goes Up Must Come Down"); }), it("bifid cipher encode: string option", () => { const result = bifidCipherEncode("some input", { keyword: "mykeyword", }); assert.strictEqual(result.toString(), "nmhs zmsdo"); }), it("bit shift left", () => { const result = chef.bitShiftLeft("Keep Your Eyes Peeled"); assert.strictEqual(result.toString(), "–ÊÊà@²Þêä@ŠòÊæ@ ÊÊØÊÈ"); }), it("bitShiftRight: number and option", () => { const result = bitShiftRight("some bits to shift", { type: "Arithmetic shift", amount: 1, }); assert.strictEqual(result.toString(), "9762\u001014:9\u0010:7\u00109443:"); }), it("Blowfish encrypt", () => { const result = chef.blowfishEncrypt("Fool's Gold", { key: { string: "0011223344556677", option: "hex", }, iv: { string: "exparrot", option: "utf8" }, mode: "CBC" }); assert.strictEqual(result.toString(), "55a2838980078ffe1722b08d5fa1d481"); }), it("Blowfish decrypt", () => { const result = chef.blowfishDecrypt("55a2838980078ffe1722b08d5fa1d481", { key: { string: "0011223344556677", option: "hex", }, iv: { string: "exparrot", option: "utf8", }, mode: "CBC" }); assert.strictEqual(result.toString(), "Fool's Gold"); }), it("BSON Serialise / Deserialise", () => { const result = chef.BSONDeserialise(chef.BSONSerialise("{\"phrase\": \"Mouth-watering\"}")); assert.strictEqual(result.toString(), `{ "phrase": "Mouth-watering" }`); }), it("Bzip2 Decompress", async () => { const result = await chef.bzip2Decompress(chef.fromBase64("QlpoOTFBWSZTWUdQlt0AAAIVgEAAAQAmJAwAIAAxBkxA0A2pTL6U2CozxdyRThQkEdQlt0A=")); assert.strictEqual(result.toString(), "Fit as a Fiddle"); }), it("cartesianProduct: binary string", () => { const result = cartesianProduct("1:2\\n\\n3:4", { itemDelimiter: ":", }); assert.strictEqual(result.toString(), "(1,3):(1,4):(2,3):(2,4)"); }), it("Change IP format", () => { const result = chef.changeIPFormat("172.20.23.54", { inputFormat: "Dotted Decimal", outputFormat: "Hex", }); assert.strictEqual(result.toString(), "ac141736"); }), it("Chi square", () => { const result = chef.chiSquare("Burst Your Bubble"); assert.strictEqual(result.toString(), "433.55399816176475"); }), it("Compare CTPH Hashes", () => { const result = chef.compareCTPHHashes("1234\n3456"); assert.strictEqual(result.toString(), "0"); }), it("Compare SSDEEPHashes", () => { const result = chef.compareCTPHHashes("1234\n3456"); assert.strictEqual(result.toString(), "0"); }), it("Convert area", () => { const result = chef.convertArea("12345", { inputUnits: "Square metre (sq m)", outputUnits: "Isle of Wight" }); assert.strictEqual(result.toString(), "0.00003248684210526316"); }), it("Convert data units", () => { const result = chef.convertDataUnits("12345", { inputUnits: "Bits (b)", outputUnits: "Kilobytes (KB)", }); assert.strictEqual(result.toString(), "1.543125"); }), it("Convert distance", () => { const result = chef.convertDistance("1234567", { inputUnits: "Nanometres (nm)", outputUnits: "Furlongs (fur)", }); assert.strictEqual(result.toString(), "0.00000613699494949495"); }), it("Convert mass", () => { const result = chef.convertMass("123", { inputUnits: "Earth mass (M⊕)", outputUnits: "Great Pyramid of Giza (6,000,000 tonnes)", }); assert.strictEqual(result.toString(), "122429895000000000"); }), it("Convert speed", () => { const result = chef.convertSpeed("123", { inputUnits: "Lunar escape velocity", outputUnits: "Jet airliner cruising speed", }); assert.strictEqual(result.toString(), "1168.5"); }), it("Count occurrences", () => { const result = chef.countOccurrences("Talk the Talk", { searchString: { string: "Tal", option: "Simple string", } }); assert.strictEqual(result.toString(), "2"); }), it("CSS Beautify", () => { const result = chef.CSSBeautify("header {color:black;padding:3rem;}"); const expected = `header { \\tcolor:black; \\tpadding:3rem; } `; assert.strictEqual(result.toString(), expected); }), it("CSS minify: boolean", () => { const input = `header { // comment width: 100%; color: white; }`; const result = CSSMinify(input, { preserveComments: true, }); assert.strictEqual(result.toString(), "header {// comment width: 100%;color: white;}"); }), it("CSS Selector", () => { const result = chef.CSSSelector("

        Hello

        ", { cssSelector: "h1", }); assert.strictEqual(result.toString(), "

        Hello

        "); }), it("CTPH", () => { const result = chef.CTPH("If You Can't Stand the Heat, Get Out of the Kitchen"); assert.strictEqual(result.toString(), "A:+EgFgBKAA0V0UFfClEs6:+Qk0gUFse"); }), it("Decode NetBIOS Name", () => { assert.strictEqual(chef.decodeNetBIOSName("EBGMGMCAEHHCGFGFGLCAFEGPCAENGFCA").toString(), "All Greek To Me"); }), it("Decode text", () => { const encoded = chef.encodeText("Ugly Duckling", { encoding: "UTF-16LE (1200)", }); const result = chef.decodeText(encoded, { encoding: "UTF-16LE (1200)", }); assert.strictEqual(result.toString(), "Ugly Duckling"); }), it("Derive EVP Key", () => { const result = chef.deriveEVPKey("", { passphrase: { string: "46 6c 65 61 20 4d 61 72 6b 65 74", option: "Hex", }, salt: { string: "Market", option: "utf8", }, }); assert.strictEqual(result.toString(), "4930d5d200e80f18c96b5550d13c6af8"); }), it("Derive PBKDF2 Key", () => { const result = chef.derivePBKDF2Key("", { passphrase: { string: "Jack of All Trades Master of None", option: "utf8", }, keySize: 256, iterations: 2, hashingFunction: "md5", salt: { string: "fruit", option: "utf8" } }); assert.strictEqual(result.toString(), "728a885b209e8b19cbd7430ca32608ff09190f7ccb7ded204e1d4c50f87c47bf"); }), it("DES Decrypt", () => { const result = chef.DESDecrypt("713081c66db781c323965ba8f166fd8c230c3bb48504a913", { key: { string: "onetwoth", option: "utf8", }, iv: { string: "threetwo", option: "utf8", }, mode: "ECB", }); assert.strictEqual(result.toString(), "Put a Sock In It"); }), it("DES Encrypt", () => { const result = chef.DESEncrypt("Put a Sock In It", { key: { string: "onetwoth", option: "utf8", }, iv: { string: "threetwo", option: "utf8", }, mode: "ECB", }); assert.strictEqual(result.toString(), "713081c66db781c323965ba8f166fd8c230c3bb48504a913"); }), it("Diff", () => { const result = chef.diff("one two\\n\\none two three"); assert.strictEqual(result.toString(), "one two three"); }), it("Disassemble x86", () => { const result = chef.disassembleX86(chef.toBase64("one two three")); const expected = `0000000000000000 0000 ADD BYTE PTR [RAX],AL\r 0000000000000002 0B250000000B OR ESP,DWORD PTR [000000000B000008]\r `; assert.strictEqual(result.toString(), expected); }), it("Divide", () => { assert.strictEqual(chef.divide("4\n7").toString(), "0.57142857142857142857"); }), it("Drop bytes", () => { assert.strictEqual(chef.dropBytes("There's No I in Team").toString(), "'s No I in Team"); }), it("Entropy", () => { const result = chef.entropy("Ride Him, Cowboy!"); assert.strictEqual(result.toString(), "3.734521664779752"); }), it("Escape string", () => { const result = chef.escapeString("Know the Ropes", { escapeLevel: "Everything", JSONCompatible: false, ES6Compatible: true, uppercaseHex: true, }); assert.strictEqual(result.toString(), "\\x4B\\x6E\\x6F\\x77\\x20\\x74\\x68\\x65\\x20\\x52\\x6F\\x70\\x65\\x73"); }), it("Escape unicode characters", () => { assert.strictEqual(chef.escapeUnicodeCharacters("σου").toString(), "\\u03C3\\u03BF\\u03C5"); }), it("Expand alphabet range", () => { assert.strictEqual( chef.expandAlphabetRange("Fight Fire With Fire", {delimiter: "t"}).toString(), "Ftitgthttt tFtitrtet tWtitttht tFtitrte"); }), it("Extract dates", () => { assert.strictEqual(chef.extractDates("Don't Look a Gift Horse In The Mouth 01/02/1992").toString(), "01/02/1992"); }), it("Filter", () => { const result = chef.filter( `I Smell a Rat Every Cloud Has a Silver Lining Top Drawer`, { regex: "Every", }); const expected = "Every Cloud Has a Silver Lining"; assert.strictEqual(result.toString(), expected); }), it("Find / Replace", () => { assert.strictEqual( chef.findReplace( "Curiosity Killed The Cat", { find: { string: "l", option: "Regex", }, replace: "s", }).toString(), "Curiosity Kissed The Cat"); }), it("Fletcher8 Checksum", () => { assert.strictEqual(chef.fletcher8Checksum("Keep Your Eyes Peeled").toString(), "48"); }), it("Format MAC addresses", () => { const result = chef.formatMACAddresses("00-01-02-03-04-05"); const expected = `000102030405 000102030405 00-01-02-03-04-05 00-01-02-03-04-05 00:01:02:03:04:05 00:01:02:03:04:05 `; assert.strictEqual(result.toString(), expected); }), it("Frequency distribution", () => { const result = chef.frequencyDistribution("Don't Count Your Chickens Before They Hatch"); 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}"; // Whacky formatting, but the data is all there assert.strictEqual(result.toString().replace(/\r?\n|\r|\s/g, ""), expected); }), it("From base", () => { assert.strictEqual(chef.fromBase("11", {radix: 13}).toString(), "14"); }), it("From BCD", () => { assert.strictEqual(chef.fromBCD("1143", { inputFormat: "Raw", scheme: "7 4 2 1"}).toString(), "31313433"); }), it("From binary", () => { assert.strictEqual(chef.fromBinary("010101011100101101011010").toString(), "UËZ"); }), it("From Charcode", () => { 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"); }), it("From decimal", () => { assert.strictEqual(chef.fromDecimal("72 101 108 108 111").toString(), "Hello"); }), it("From hex", () => { assert.strictEqual(chef.fromHex("52 69 6e 67 20 41 6e 79 20 42 65 6c 6c 73 3f").toString(), "Ring Any Bells?"); }), it("From hex content", () => { assert.strictEqual(chef.fromHexContent("foo|3d|bar").toString(), "foo=bar"); }), it("To and From hex dump", () => { assert.strictEqual(chef.fromHexdump(chef.toHexdump("Elephant in the Room")).toString(), "Elephant in the Room"); }), it("From HTML entity", () => { assert.strictEqual(chef.fromHTMLEntity("&").toString(), "&"); }), it("To and From morse code", () => { assert.strictEqual(chef.fromMorseCode(chef.toMorseCode("Put a Sock In It")).toString(), "PUT A SOCK IN IT"); }), it("From octal", () => { assert.strictEqual(chef.fromOctal("113 156 157 167 40 164 150 145 40 122 157 160 145 163").toString(), "Know the Ropes"); }), it("To, From punycode", () => { assert.strictEqual(chef.fromPunycode(chef.toPunycode("münchen")).toString(), "münchen"); }), it("From unix timestamp", () => { assert.strictEqual(chef.fromUNIXTimestamp("978346800").toString(), "Mon 1 January 2001 11:00:00 UTC"); }), it("Generate HOTP", () => { const result = chef.generateHOTP("JBSWY3DPEHPK3PXP", { }); const expected = `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0 Password: 282760`; assert.strictEqual(result.toString(), expected); }), it("Generate PGP Key Pair", async () => { const result = await chef.generatePGPKeyPair("Back To the Drawing Board", { keyType: "ECC-256", }); assert.strictEqual(result.toString().substr(0, 37), "-----BEGIN PGP PRIVATE KEY BLOCK-----"); }), ...[1, 3, 4, 5, 6, 7].map(version => it(`Generate UUID v${version}`, () => { const result = chef.generateUUID("", { "version": `v${version}` }).toString(); assert.ok(result); assert.strictEqual(result.length, 36); })), ...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => { const uuid = chef.generateUUID("", { "version": `v${version}` }).toString(); const result = chef.analyseUUID(uuid).toString(); const expected = `UUID version: ${version}`; assert.strictEqual(result, expected); })), it("Generate UUID using defaults", () => { const uuid = chef.generateUUID(); assert.ok(uuid); const analysis = chef.analyseUUID(uuid).toString(); assert.strictEqual(analysis, "UUID version: 4"); }), it("Gzip, Gunzip", () => { assert.strictEqual(chef.gunzip(chef.gzip("Down To The Wire")).toString(), "Down To The Wire"); }), it("Hex to Object Identifier", () => { assert.strictEqual( chef.hexToObjectIdentifier(chef.toHex("You Can't Teach an Old Dog New Tricks")).toString(), "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"); }), it("Hex to PEM", () => { const result = chef.hexToPEM(chef.toHex("Yada Yada")); const expected = `-----BEGIN CERTIFICATE-----\r WWFkYSBZYWRh\r -----END CERTIFICATE-----\r\n`; assert.strictEqual(result.toString(), expected); }), it("HMAC", () => { assert.strictEqual(chef.HMAC("On Cloud Nine", {key: "idea"}).toString(), "e15c268b4ee755c9e52db094ed50add7"); }), it("JPathExpression", () => { assert.strictEqual(chef.JPathExpression("{\"key\" : \"value\"}", {query: "$.key"}).toString(), "\"value\""); }), it("JSON Beautify", () => { assert.strictEqual( chef.JSONBeautify("{\"key\" : \"value\"}").toString(), `{ "key": "value" }`); }), it("Keccak", () => { assert.strictEqual(chef.keccak("Flea Market").toString(), "c2a06880b19e453ee5440e8bd4c2024bedc15a6630096aa3f609acfd2b8f15f27cd293e1cc73933e81432269129ce954a6138889ce87831179d55dcff1cc7587"); }), it("LZNT1 Decompress", () => { assert.strictEqual(chef.LZNT1Decompress("\x1a\xb0\x00compress\x00edtestda\x04ta\x07\x88alot").toString(), "compressedtestdatacompressedalot"); }), it("MD6", () => { assert.strictEqual(chef.MD6("Head Over Heels", {key: "arty"}).toString(), "d8f7fe4931fbaa37316f76283d5f615f50ddd54afdc794b61da522556aee99ad"); }), it("Parse ASN.1 Hex string", () => { assert.strictEqual(chef.parseASN1HexString(chef.toHex("Mouth-watering")).toString(), "UNKNOWN(77) 7574682d7761746572696e67\n"); }), it("Parse DateTime", () => { const result = chef.parseDateTime("06/07/2001 01:59:30"); const expected = `Date: Friday 6th July 2001 Time: 01:59:30 Period: AM Timezone: UTC UTC offset: +0000 Daylight Saving Time: false Leap year: false Days in this month: 31 Day of year: 187 Week number: 27 Quarter: 3`; assert.strictEqual(result.toString(), expected); }), it("Parse IPV6 address", () => { const result = chef.parseIPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); const expected = `Longhand: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 Shorthand: 2001:db8:85a3::8a2e:370:7334 This 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. Documentation range: 2001:db8::/32`; assert.strictEqual(result.toString(), expected); }), it("Parse URI", () => { const result = chef.parseURI("https://www.google.co.uk/search?q=almonds"); const expected = `Protocol: https: Hostname: www.google.co.uk Path name: /search Arguments: \tq = almonds `; assert.strictEqual(result.toString(), expected); }), it("Parse user agent", () => { const result = chef.parseUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 "); const expected = `Browser Name: Firefox Version: 47.0 Device Model: unknown Type: unknown Vendor: unknown Engine Name: Gecko Version: 47.0 OS Name: Windows Version: 7 CPU Architecture: amd64`; assert.strictEqual(result.toString(), expected); }), it("PGP Encrypt and decrypt", async () => { const pbkey = `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: Keybase OpenPGP v2.1.3 Comment: https://keybase.io/crypto xo0EXZtlowEEAKUqTFownTmqgXWu2KDrtyNYtFck7a16WM5QD95bFoAFFdnlwZ45 6Vw8G8LCzHdyRXYp/JF1GknDrAd7nIRE+SuSz2yVK5nlOCfO1HFcg2Ov7e7/pBwd qawx9GUIsCKd/6NxwDuT4YqarLFsuwljRC/eQiibO+ejnhoiKcU69sTNABEBAAHN AMK0BBMBCgAeBQJdm2WjAhsvAwsJBwMVCggCHgECF4ADFgIBAhkBAAoJEGS79V2S 7D0owtMD/RT+o4BQJ8NSQBDgkYf42uOOu1Ud6GuN89nX6n20yAZbmqQ8CHnHY+Qc l6ft4HnbIaNrI3arp/C2C+cwFypmt1BKyFEJUXO7ft3i/IxnjpCorDyAMCDckDvq uma1LWtUHLb5s/ZuGMSHnhuji74IRWuIofNPdf7bCZW1GMbW9jNUzo0EXZtlowEE AL38zaNkPmUVQaowP696fayBo18Nxs0yOzC4+0TYv1B/k5aUb0Air2h+o/Xw4E42 Jh9gVdPSvhOAEqdV0UDe71wxa4cfAVMDY9v8ta81MWunChj3ISUk1oIQylTJNsY/ b4KWOrLaOtBD9dyFGCzss5vLVdqdMjVIW2Cz0hb6IYG7ABEBAAHCwIMEGAEKAA8F Al2bZaMFCQ8JnAACGy4AqAkQZLv1XZLsPSidIAQZAQoABgUCXZtlowAKCRA16MU2 u2hFTX+JBACZ27xk0Afny2jjSoRzqLMrhzE7DBGcg2QqecMdNre12hVompAWcS4l NFmPShKRi6UT8Zb38nD43vwfqwZImn60dOPqqAep3YF/Axm1u5HJb0aMEsb8O9jV sVmNJv9jVTzPdlTGFQjuaeJfk5lwxB+5/O9NcgDhPgRAk9xb4FrT+xzmA/4tD11C AdcITUkTZT4ZOo2418DGeaiaEqWcIkZeQG4Vh5TMj4QtZDwsYQhXPl5Zj1zKIN/1 gRrKC+ztaQoDG8pJXTTtc9inRU++dhMqnRGrPcz0VfVXFaiH7PUCy+4WpP6r5Bs5 YQ9ESHo+FsmIvDzU3e/PD0SfXfO4vqBrFYN8986NBF2bZaMBBADJafe0w9diaCNx 3A7e8MqjbNrhrLkD2cPxXspCATX3SuI19d2+hMiHZfKTyadBTIa+ICxvqoxwxyZD raHSY3CWVZd1V4KB5mqf+3Zj5riLeGU0dtXwi/5c0bdUhBUgHiAMhi75p05jYih5 KsNxPcK9hEwPu7B+QeHURMiIgojTGQARAQABwsCDBBgBCgAPBQJdm2WjBQkDwmcA AhsuAKgJEGS79V2S7D0onSAEGQEKAAYFAl2bZaMACgkQzdkMJSM5Bqg2rwP/Ue28 m3Fdfgh5JxouZ3Dm2KUDhZL95B+vdMk72acdoU7SRjlyDT8cApRqYx+MIXb8WrPN 1xCZnOM4zXeWIM0CAPQ1e/sCrK58L+P+eVngNmrW9epKtZ4L6hx+dqqja9vPZGQK CsFAhA6A1gWB++OLk9Y6H23tWIdKEXMeAX7492zDYgP+OSPS79EWAqXL8SvmDrbl WI5eiM6X5hAMrOjQqzXhatD7eP41N/FC3SfhyhX7hFbagO7MJG2AS5bmSvcuCdcN wDwXd94B+7bfYgJIRKbr272yDwkyzGn+zmxzvMUt6ak5PNzfmadvhMZvIfDftswp GYpXIUU0GObOgP2tpCGTErs= =m++F -----END PGP PUBLIC KEY BLOCK-----`; const privateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- Version: Keybase OpenPGP v2.1.3 Comment: https://keybase.io/crypto xcEYBF2bZaMBBAClKkxaMJ05qoF1rtig67cjWLRXJO2teljOUA/eWxaABRXZ5cGe OelcPBvCwsx3ckV2KfyRdRpJw6wHe5yERPkrks9slSuZ5TgnztRxXINjr+3u/6Qc HamsMfRlCLAinf+jccA7k+GKmqyxbLsJY0Qv3kIomzvno54aIinFOvbEzQARAQAB AAP7BXVS5aN3/AkNqIvOiUQ7nqrr9s9NHYUOvJllFNucxZP6x2MyQAjjlJKV9kdF cOhxXDjXVHVIGPT4UUeoAgUHg6K0K5WLmmNaO1w7ayf9737OrhrQFblQNqh4J9BV oP/cArJ5+j/4IGKGYuWy3kTpvtabedlWq99E9PYrDJHD8E8CANDjnboIRgmAwHwi ZKqc5rNXIBl7fJgFdf96cWiMF/7j2nJuarJGJRQUGxDaBi5zZSTZnwfVJZrDboyb JCahLTMCAMpqP0wTM4Qs95HhJUBmAdBhqxXjiAMtMDnn0ue8qAtv4JRjPkfxXUsC 4J4PExw6eMU7BCGInel5B6+jdpvURf8B/3koVTHTxyBR/OTpP8XiwOwreb/SleIS JMYiXx6akUoPtACfXyBYM0fqCNCq38ZYhNM89oJbu1Rm5LJHe0m0DY6d4c0AwrQE EwEKAB4FAl2bZaMCGy8DCwkHAxUKCAIeAQIXgAMWAgECGQEACgkQZLv1XZLsPSjC 0wP9FP6jgFAnw1JAEOCRh/ja4467VR3oa43z2dfqfbTIBluapDwIecdj5ByXp+3g edsho2sjdqun8LYL5zAXKma3UErIUQlRc7t+3eL8jGeOkKisPIAwINyQO+q6ZrUt a1Qctvmz9m4YxIeeG6OLvghFa4ih8091/tsJlbUYxtb2M1THwRgEXZtlowEEAL38 zaNkPmUVQaowP696fayBo18Nxs0yOzC4+0TYv1B/k5aUb0Air2h+o/Xw4E42Jh9g VdPSvhOAEqdV0UDe71wxa4cfAVMDY9v8ta81MWunChj3ISUk1oIQylTJNsY/b4KW OrLaOtBD9dyFGCzss5vLVdqdMjVIW2Cz0hb6IYG7ABEBAAEAA/4xkx7hrM2vOL26 t/5WPsM+WVGVAxZGAv549zvxuhEp4zBS0Ya6GJLm1GzaRzFwlyaZd1zN+ibJFdlI OtdwcvvIAqNBsJMcjl2eaVtWK/PYvwqS7mVfojK8zUsKKNFIL6z/JKv7gmXzGuKV S5aYUOUMQI3mliTuqQpfLewhYBtOeQIA42jDWJfxjWiejV6QSNmBYhLeOwi/CFrd YE6obpXqX0V3vVOqB1rw/VHfabkWBmdOu55muw9kCLYOR89HNF6NrwIA1d+cTU7p eFgSUm/u1esS1ucAoxdOPZ7pkLv9+NLQNvjLThmOHCFXyTZr4aoHtnqSG8PcUAWs hyQ35+WpKWA7tQH9GqDFogK+8GjzgVl+vCEnaTV7H/69tS93m9z06hFRs4iEZwWC 4oCUNqOFj6IFyiBf2cM0pmMX0ODLnIG5SDVfWaIFwsCDBBgBCgAPBQJdm2WjBQkP CZwAAhsuAKgJEGS79V2S7D0onSAEGQEKAAYFAl2bZaMACgkQNejFNrtoRU1/iQQA mdu8ZNAH58to40qEc6izK4cxOwwRnINkKnnDHTa3tdoVaJqQFnEuJTRZj0oSkYul E/GW9/Jw+N78H6sGSJp+tHTj6qgHqd2BfwMZtbuRyW9GjBLG/DvY1bFZjSb/Y1U8 z3ZUxhUI7mniX5OZcMQfufzvTXIA4T4EQJPcW+Ba0/sc5gP+LQ9dQgHXCE1JE2U+ GTqNuNfAxnmomhKlnCJGXkBuFYeUzI+ELWQ8LGEIVz5eWY9cyiDf9YEaygvs7WkK AxvKSV007XPYp0VPvnYTKp0Rqz3M9FX1VxWoh+z1AsvuFqT+q+QbOWEPREh6PhbJ iLw81N3vzw9En13zuL6gaxWDfPfHwRgEXZtlowEEAMlp97TD12JoI3HcDt7wyqNs 2uGsuQPZw/FeykIBNfdK4jX13b6EyIdl8pPJp0FMhr4gLG+qjHDHJkOtodJjcJZV l3VXgoHmap/7dmPmuIt4ZTR21fCL/lzRt1SEFSAeIAyGLvmnTmNiKHkqw3E9wr2E TA+7sH5B4dREyIiCiNMZABEBAAEAA/wJeGeSwtCaSm48OM4kMms8wu4JxW7PnQon C79z2g25CnbXda+O+TxajXMZ+tXX7qq5PtcICxteZCbK8NuWgmF1QqWWhS2ZLbAV 5edTc0vw8FSDwiAeiHyKa5Hs4B3uJaB54uADPyOYHPfX/NhEOfNAleDgVoa1Toqf R50lFsGOVwIA/cetzK3+NTZ5W+V8DGShxv4u5qAhhGZRb0GA3TPAoshVjHWY34i1 KivtI3/tLLNTaVSVblG2VVoydKelRhsjGwIAyy0E1KI5O2EhLsVsDwx9NtO4SmUG REZt/LRYp1p5+nsarfeCVKQ4qQ6eqdK71Z7tEICT0JXqgSjQsKYVdscR2wH9GiyR LuHX3Nnh+M8lUv36ZM5XrWEypRFQaNYssRzPpqU4f9oViSPxdADonxehDP4ICmFr vqT+etEmjr9dzp4ZSKLswsCDBBgBCgAPBQJdm2WjBQkDwmcAAhsuAKgJEGS79V2S 7D0onSAEGQEKAAYFAl2bZaMACgkQzdkMJSM5Bqg2rwP/Ue28m3Fdfgh5JxouZ3Dm 2KUDhZL95B+vdMk72acdoU7SRjlyDT8cApRqYx+MIXb8WrPN1xCZnOM4zXeWIM0C APQ1e/sCrK58L+P+eVngNmrW9epKtZ4L6hx+dqqja9vPZGQKCsFAhA6A1gWB++OL k9Y6H23tWIdKEXMeAX7492zDYgP+OSPS79EWAqXL8SvmDrblWI5eiM6X5hAMrOjQ qzXhatD7eP41N/FC3SfhyhX7hFbagO7MJG2AS5bmSvcuCdcNwDwXd94B+7bfYgJI RKbr272yDwkyzGn+zmxzvMUt6ak5PNzfmadvhMZvIfDftswpGYpXIUU0GObOgP2t pCGTErs= =Ya+/ -----END PGP PRIVATE KEY BLOCK-----`; const message = "A Fool and His Money are Soon Parted"; const encrypted = await chef.PGPEncrypt(message, { publicKeyOfRecipient: pbkey, }); const result = await chef.PGPDecrypt(encrypted, { privateKeyOfRecipient: privateKey, }); assert.strictEqual(result.toString(), message); }), it("Raw deflate", () => { assert.strictEqual(chef.rawInflate(chef.rawDeflate("Like Father Like Son", { compressionType: "Fixed Huffman Coding"})).toString(), "Like Father Like Son"); }), it("RC4", () => { assert.strictEqual( chef.RC4("Go Out On a Limb", {passphrase: {string: "Under Your Nose", option: "UTF8"}, inputFormat: "UTF8", outputFormat: "Hex"}).toString(), "7d17e60d9bc94b7f4095851c729e69a2"); }), it("RC4 Drop", () => { assert.strictEqual( chef.RC4Drop("Go Out On a Limb", {passphrase: {string: "Under Your Nose", option: "UTF8"}, inputFormat: "UTF8", outputFormat: "Hex"}).toString(), "b85cb1c4ed6bed8f260ab92829bba942"); }), it("Regular Expression", () => { assert.strictEqual(chef.regularExpression("Wouldn't Harm a Fly", {regex: "\\'[a-z]"}).toString(), "Wouldn't Harm a Fly"); }), it("Remove EXIF", () => { const result = chef.removeEXIF(fs.readFileSync("tests/node/sampleData/pic.jpg")); assert.strictEqual(result.toString().length, 4582); }), it("Scan for embedded files", () => { const result = chef.scanForEmbeddedFiles(fs.readFileSync("src/web/static/images/cook_male-32x32.png")); const expected = "Scanning data for 'magic bytes' which may indicate embedded files."; assert.ok(result.toString().indexOf(expected) === 0); }), it("Scrypt", () => { assert.strictEqual( chef.scrypt("Playing For Keeps", {salt: {string: "salty", option: "Hex"}}).toString(), "5446b6d86d88515894a163201765bceed0bc39610b1506cdc4d939ffc638bc46e051bce756e2865165d89d955a43a7eb5504502567dea8bfc9e7d49aaa894c07"); }), it("SHA3", () => { assert.strictEqual( chef.SHA3("benign gravel").toString(), "2b1e36e0dbe151a89887be08da3bad141908cce62327f678161bcf058627e87abe57e3c5fce6581678714e6705a207acbd5c1f37f7a812280bc2cc558f00bed9"); }), it("Shake", () => { assert.strictEqual( chef.shake("murderous bloodshed").toString(), "b79b3bb88099330bc6a15122f8dfaededf57a33b51c748d5a94e8122ff18d21e12f83412926b7e4a77a85ba6f36aa4841685e78296036337175e40096b5ac000"); }), it("Snefru", () => { assert.strictEqual( chef.snefru("demeaning milestone", {size: 256, rounds: 8}).toString(), "a671b48770fe073ce49e9259cc2f47d345a53712639f8ae23c5ad3fec19540a5"); }), it("SQL Beautify", () => { const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F FROM STATS;`); const expected = `SELECT MONTH, ID, RAIN_I, TEMP_F FROM STATS;`; assert.strictEqual(result.toString(), expected); }), it("SSDEEP", () => { assert.strictEqual( chef.SSDEEP("shotgun tyranny snugly").toString(), "3:DLIXzMQCJc:XERKc"); }), it("strings", () => { const result = chef.strings("smothering ampersand abreast", {displayTotal: true}); const expected = `Total found: 1 smothering ampersand abreast`; assert.strictEqual(result.toString(), expected); }), it("toBase64: editableOption", () => { const result = toBase64("some input", { alphabet: { value: "0-9A-W+/a-zXYZ=" }, }); assert.strictEqual(result.toString(), "StXkPI1gRe1sT0=="); }), it("toBase64: editableOptions key is value", () => { const result = toBase64("some input", { alphabet: "0-9A-W+/a-zXYZ=", }); assert.strictEqual(result.toString(), "StXkPI1gRe1sT0=="); }), it("toBase64: editableOptions default", () => { const result = toBase64("some input"); assert.strictEqual(result.toString(), "c29tZSBpbnB1dA=="); }), it("To BCD", () => { assert.strictEqual(chef.toBCD("443").toString(), "0100 0100 0011"); }), it("To CamelCase", () => { assert.strictEqual(chef.toCamelCase("Quickest Wheel").toString(), "quickestWheel"); }), it("toHex: accepts args", () => { const result = toHex("some input", { delimiter: "Colon", }); assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74"); }), it("To Kebab case", () => { assert.strictEqual(chef.toKebabCase("Elfin Gold").toString(), "elfin-gold"); }), it("To punycode", () => { assert.strictEqual(chef.toPunycode("♠ ♣ ♥ ♦ ← ↑ ‍ →").toString(), " -m06cw7klao368lfb3aq"); }), it("to snake case", () => { assert.strictEqual(chef.toSnakeCase("Abhorrent Grass").value, "abhorrent_grass"); }), it("to unix timestamp", () => { assert.strictEqual(chef.toUNIXTimestamp("2001-04-01").toString(), "986083200 (Sun 1 April 2001 00:00:00 UTC)"); }), it("Translate DateTime format", () => { assert.strictEqual(chef.translateDateTimeFormat("01/04/1999 22:33:01").toString(), "Thursday 1st April 1999 22:33:01 +00:00 UTC"); }), it("Triple DES encrypt / decrypt", () => { assert.strictEqual( chef.tripleDESDecrypt( chef.tripleDESEncrypt("Destroy Money", { 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"}, iv: {string: "00 00 00 00 00 00 00 00", option: "Hex"}}), { 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"}, iv: {string: "00 00 00 00 00 00 00 00", option: "Hex"} }).toString(), "Destroy Money"); }), it("UNIX Timestamp to Windows Filetime", () => { assert.strictEqual(chef.UNIXTimestampToWindowsFiletime("2020735").toString(), "116464943350000000"); }), it("XML Beautify", () => { assert.strictEqual( chef.XMLBeautify("abc").toString(), ` \\tabc `); }), it("XOR: toggleString with default option", () => { assert.strictEqual(chef.XOR("fe023da5", { key: "73 6f 6d 65" }).toString(), "\u0015\n]W@\u000b\fP"); }), it("XOR: toggleString with custom option", () => { assert.strictEqual(chef.XOR("fe023da5", { key: { string: "73 6f 6d 65", option: "utf8", } }).toString(), "QV\u0010\u0004UDWQ"); }), it("XPath expression", () => { assert.strictEqual( chef.XPathExpression("abc", {xPath: "contact-info/company"}).toString(), "abc"); }), it("Zlib deflate / inflate", () => { assert.strictEqual(chef.zlibInflate(chef.zlibDeflate("cut homer wile rooky grits dizen")).toString(), "cut homer wile rooky grits dizen"); }), it("extract EXIF", () => { assert.strictEqual( chef.extractEXIF(fs.readFileSync("tests/node/sampleData/pic.jpg")).toString(), `Found 7 tags. Orientation: 1 XResolution: 72 YResolution: 72 ResolutionUnit: 2 ColorSpace: 1 ExifImageWidth: 57 ExifImageHeight: 57`); }), it("Tar", () => { const tarred = chef.tar("some file content", { filename: "test.txt" }); assert.strictEqual(tarred.type, 7); assert.strictEqual(tarred.value.size, 2048); assert.strictEqual(tarred.value.data.toString().substr(0, 8), "test.txt"); }), it("Untar", () => { const tarred = chef.tar("some file content", { filename: "filename.txt", }); const untarred = chef.untar(tarred); assert.strictEqual(untarred.type, 8); assert.strictEqual(untarred.value.length, 1); assert.strictEqual(untarred.value[0].name, "filename.txt"); assert.strictEqual(untarred.value[0].data.toString(), "some file content"); }), it("Zip", () => { const zipped = chef.zip("some file content", { filename: "sample.zip", comment: "added", operatingSystem: "Unix", }); assert.strictEqual(zipped.type, 7); assert.ok(zipped.value.data.toString().includes("sample.zip")); assert.ok(zipped.value.data.toString().includes("added")); }), it("Unzip", () => { const zipped = chef.zip("some file content", { filename: "zipped.zip", comment: "zippy", }); const unzipped = chef.unzip(zipped); assert.equal(unzipped.type, 8); assert.equal(unzipped.value[0].data, "some file content"); assert.equal(unzipped.value[0].name, "zipped.zip"); }), it("Unzip with password", () => { const zipped = chef.zip("some content", { password: "abcd", }); const unzipped = chef.unzip(zipped, { password: "abcd", }); assert.equal(unzipped.value[0].data, "some content"); }), it("YARA Rule Matching", async () => { const input = "foobar foobar bar foo foobar"; 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"; const res = await chef.YARARules(input, { rules: "rule foo {strings: $re1 = /foo/ condition: $re1} rule bar {strings: $re1 = /bar/ condition: $re1}", showStrings: true, showStringLengths: true, showMetadata: true }); assert.equal(output, res.value); }), it("performs MAGIC", async () => { const input = "WUagwsiae6mP8gNtCCLUFpCpCB26RmBDoDD8PacdAmzAzBVjkK2QstFXaKhpC6iUS7RHqXrJtFisoRSgoJ4whjm1arm864qaNq4RcfUmLHrcsAaZc5TXCYifNdgS83gDeejGX46gaiMyuBV6EskHt1scgJ88x2tNSotQDwbGY1mmCob2ARGFvCKYNqiN9ipMq1ZU1mgkdbNuGcb76aRtYWhCGUc8g93UJudhb8htsheZnwTpgqhx83SVJSZXMXUjJT2zmpC7uXWtumqokbdSi88YtkWDAc1Toouh2oH4D4ddmNKJWUDpMwmngUmK14xwmomccPQE9hM172APnSqwxdKQ172RkcAsysnmj5gGtRmVNNh2s359wr6mS2QRP"; const depth = 1; const res = await chef.magic(input, { depth, }); // assert against the structure of the output, rather than the values. assert.strictEqual(res.value.length, depth + 1); res.value.forEach(row => { assert.ok(row.recipe); assert.ok(row.data); assert.ok(row.languageScores); assert.ok(Object.prototype.hasOwnProperty.call(row, "fileType")); // Can be null, so cannot just use ok assert.ok(row.entropy); assert.ok(row.matchingOps); assert.ok(Object.prototype.hasOwnProperty.call(row, "useful")); assert.ok(Object.prototype.hasOwnProperty.call(row, "matchesCrib")); row.recipe.forEach(item => { assert.ok(Object.prototype.hasOwnProperty.call(item, "op"), `No 'op' property in item ${item}`); assert.strictEqual(typeof item.op, "string"); assert.ok(Object.prototype.hasOwnProperty.call(item, "args"), `No 'args' property in item ${item}`); assert.ok(Array.isArray(item.args)); }); row.languageScores.forEach(score => { assert.ok(Object.prototype.hasOwnProperty.call(score, "lang"), `No 'lang' property in languageScore ${score}`); assert.strictEqual(typeof score.lang, "string"); assert.ok(Object.prototype.hasOwnProperty.call(score, "score"), `No 'score' property in languageScore ${score}`); assert.strictEqual(typeof score.score, "number"); assert.ok(Object.prototype.hasOwnProperty.call(score, "probability"), `No 'probability' property in languageScore ${score}`); assert.strictEqual(typeof score.probability, "number"); }); row.matchingOps.forEach(op => { assert.ok(Object.prototype.hasOwnProperty.call(op, "op"), `No 'op' property in matchingOp ${JSON.stringify(op)}`); assert.strictEqual(typeof op.op, "string"); assert.ok(Object.prototype.hasOwnProperty.call(op, "pattern"), `No 'pattern' property in matchingOp ${JSON.stringify(op)}`); assert.ok(op.pattern instanceof RegExp); assert.ok(Object.prototype.hasOwnProperty.call(op, "args"), `No 'args' property in matchingOp ${JSON.stringify(op)}`); assert.ok(Array.isArray(op.args)); assert.ok(Object.prototype.hasOwnProperty.call(op, "useful"), `No 'useful' property in matchingOp ${JSON.stringify(op)}`); assert.ifError(op.useful); // Expect this to be undefined assert.ok(Object.prototype.hasOwnProperty.call(op, "entropyRange"), `No 'entropyRange' property in matchingOp ${JSON.stringify(op)}`); assert.ifError(op.entropyRange); // Expect this to be undefined assert.ok(Object.prototype.hasOwnProperty.call(op, "output"), `No 'output' property in matchingOp ${JSON.stringify(op)}`); assert.ifError(op.output); // Expect this to be undefined }); }); }), ]); ================================================ FILE: tests/operations/index.mjs ================================================ /* eslint no-console: 0 */ /** * Test Runner * * For running the tests in the test register. * * @author tlwr [toby@toby.codes] * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import { setLongTestFailure, logTestReport } from "../lib/utils.mjs"; import TestRegister from "../lib/TestRegister.mjs"; import "./tests/A1Z26CipherDecode.mjs"; import "./tests/AESKeyWrap.mjs"; import "./tests/AlternatingCaps.mjs"; import "./tests/AvroToJSON.mjs"; import "./tests/BaconCipher.mjs"; import "./tests/Base32.mjs"; import "./tests/Base45.mjs"; import "./tests/Base58.mjs"; import "./tests/Base62.mjs"; import "./tests/Base64.mjs"; import "./tests/Base85.mjs"; import "./tests/Base92.mjs"; import "./tests/BCD.mjs"; import "./tests/Bech32.mjs"; import "./tests/BitwiseOp.mjs"; import "./tests/BLAKE2b.mjs"; import "./tests/BLAKE2s.mjs"; import "./tests/BLAKE3.mjs"; import "./tests/Bombe.mjs"; import "./tests/BSON.mjs"; import "./tests/ByteRepr.mjs"; import "./tests/CaesarBoxCipher.mjs"; import "./tests/CaretMdecode.mjs"; import "./tests/CartesianProduct.mjs"; import "./tests/CBORDecode.mjs"; import "./tests/CBOREncode.mjs"; import "./tests/CetaceanCipherDecode.mjs"; import "./tests/CetaceanCipherEncode.mjs"; import "./tests/ChaCha.mjs"; import "./tests/ChangeIPFormat.mjs"; import "./tests/CharEnc.mjs"; import "./tests/Charts.mjs"; import "./tests/Ciphers.mjs"; import "./tests/CipherSaber2.mjs"; import "./tests/CMAC.mjs"; import "./tests/Code.mjs"; import "./tests/Colossus.mjs"; import "./tests/Comment.mjs"; import "./tests/Compress.mjs"; import "./tests/ConditionalJump.mjs"; import "./tests/ConvertCoordinateFormat.mjs"; import "./tests/ConvertLeetSpeak.mjs"; import "./tests/ConvertToNATOAlphabet.mjs"; import "./tests/CRCChecksum.mjs"; import "./tests/Crypt.mjs"; import "./tests/CSV.mjs"; import "./tests/DateTime.mjs"; import "./tests/DefangIP.mjs"; import "./tests/DisassembleARM.mjs"; import "./tests/DropNthBytes.mjs"; import "./tests/ECDSA.mjs"; import "./tests/ELFInfo.mjs"; import "./tests/Enigma.mjs"; import "./tests/ExtractAudioMetadata.mjs"; import "./tests/ExtractEmailAddresses.mjs"; import "./tests/ExtractHashes.mjs"; import "./tests/ExtractIPAddresses.mjs"; import "./tests/Float.mjs"; import "./tests/FileTree.mjs"; import "./tests/FletcherChecksum.mjs"; import "./tests/Fork.mjs"; import "./tests/FromDecimal.mjs"; import "./tests/GenerateAllChecksums.mjs"; import "./tests/GenerateAllHashes.mjs"; import "./tests/GenerateDeBruijnSequence.mjs"; import "./tests/GenerateQRCode.mjs"; import "./tests/GetAllCasings.mjs"; import "./tests/GOST.mjs"; import "./tests/Gunzip.mjs"; import "./tests/Gzip.mjs"; import "./tests/Hash.mjs"; import "./tests/HASSH.mjs"; import "./tests/HaversineDistance.mjs"; import "./tests/Hex.mjs"; import "./tests/Hexdump.mjs"; import "./tests/HKDF.mjs"; import "./tests/Image.mjs"; import "./tests/IndexOfCoincidence.mjs"; import "./tests/JA3Fingerprint.mjs"; import "./tests/JA4.mjs"; import "./tests/JA3SFingerprint.mjs"; import "./tests/Jsonata.mjs"; import "./tests/JSONBeautify.mjs"; import "./tests/JSONMinify.mjs"; import "./tests/JSONtoCSV.mjs"; import "./tests/Jump.mjs"; import "./tests/JWK.mjs"; import "./tests/JWTDecode.mjs"; import "./tests/JWTSign.mjs"; import "./tests/JWTVerify.mjs"; import "./tests/LevenshteinDistance.mjs"; import "./tests/Lorenz.mjs"; import "./tests/LS47.mjs"; import "./tests/LuhnChecksum.mjs"; import "./tests/LZNT1Decompress.mjs"; import "./tests/LZString.mjs"; import "./tests/Magic.mjs"; import "./tests/Media.mjs"; import "./tests/MIMEDecoding.mjs"; import "./tests/Modhex.mjs"; import "./tests/MorseCode.mjs"; import "./tests/MS.mjs"; import "./tests/MultipleBombe.mjs"; import "./tests/MurmurHash3.mjs"; import "./tests/NetBIOS.mjs"; import "./tests/NormaliseUnicode.mjs"; import "./tests/NTLM.mjs"; import "./tests/OTP.mjs"; import "./tests/ParseIPRange.mjs"; import "./tests/ParseObjectIDTimestamp.mjs"; import "./tests/ParseQRCode.mjs"; import "./tests/ParseSSHHostKey.mjs"; import "./tests/ParseTCP.mjs"; import "./tests/ParseTLSRecord.mjs"; import "./tests/ParseTLV.mjs"; import "./tests/ParseUDP.mjs"; import "./tests/PEMtoHex.mjs"; import "./tests/PGP.mjs"; import "./tests/PHP.mjs"; import "./tests/PHPSerialize.mjs"; import "./tests/PowerSet.mjs"; import "./tests/Protobuf.mjs"; import "./tests/PubKeyFromCert.mjs"; import "./tests/PubKeyFromPrivKey.mjs"; import "./tests/Rabbit.mjs"; import "./tests/RAKE.mjs"; import "./tests/Regex.mjs"; import "./tests/Register.mjs"; import "./tests/RisonEncodeDecode.mjs"; import "./tests/Rotate.mjs"; import "./tests/RSA.mjs"; import "./tests/Salsa20.mjs"; import "./tests/XSalsa20.mjs"; import "./tests/SeqUtils.mjs"; import "./tests/SetDifference.mjs"; import "./tests/SetIntersection.mjs"; import "./tests/SetUnion.mjs"; import "./tests/Shuffle.mjs"; import "./tests/SIGABA.mjs"; import "./tests/SM2.mjs"; import "./tests/SM4.mjs"; import "./tests/RC6.mjs"; // import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet import "./tests/SQLBeautify.mjs"; import "./tests/StrUtils.mjs"; import "./tests/StripIPv4Header.mjs"; import "./tests/StripTCPHeader.mjs"; import "./tests/StripUDPHeader.mjs"; import "./tests/Subsection.mjs"; import "./tests/SwapCase.mjs"; import "./tests/SymmetricDifference.mjs"; import "./tests/TakeNthBytes.mjs"; import "./tests/Template.mjs"; import "./tests/TextEncodingBruteForce.mjs"; import "./tests/TextIntegerConverter.mjs"; import "./tests/ToFromInsensitiveRegex.mjs"; import "./tests/TranslateDateTimeFormat.mjs"; import "./tests/Typex.mjs"; import "./tests/UnescapeString.mjs"; import "./tests/Unicode.mjs"; import "./tests/URLEncodeDecode.mjs"; import "./tests/RSA.mjs"; import "./tests/CBOREncode.mjs"; import "./tests/CBORDecode.mjs"; import "./tests/JA3Fingerprint.mjs"; import "./tests/JA3SFingerprint.mjs"; import "./tests/HASSH.mjs"; import "./tests/JSONtoYAML.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; import "./tests/YARA.mjs"; import "./tests/ParseCSR.mjs"; import "./tests/XXTEA.mjs"; const testStatus = { allTestsPassing: true, counts: { total: 0, }, }; setLongTestFailure(); const logOpsTestReport = logTestReport.bind(null, testStatus); (async function () { const results = await TestRegister.runTests(); logOpsTestReport(results); })(); ================================================ FILE: tests/operations/tests/A1Z26CipherDecode.mjs ================================================ /** * A1Z26 Cipher Decode tests * * @author brick-pixel * @copyright Crown Copyright 2026 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "A1Z26 Cipher Decode: basic decode", "input": "8 5 12 12 15", "expectedOutput": "hello", "recipeConfig": [ { "op": "A1Z26 Cipher Decode", "args": ["Space"] } ] }, { "name": "A1Z26 Cipher Decode: empty input returns empty string", "input": "", "expectedOutput": "", "recipeConfig": [ { "op": "A1Z26 Cipher Decode", "args": ["Space"] } ] } ]); ================================================ FILE: tests/operations/tests/AESKeyWrap.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 128-bit KEK", "input": "00112233445566778899aabbccddeeff", "expectedOutput": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 192-bit KEK", "input": "00112233445566778899aabbccddeeff", "expectedOutput": "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 256-bit KEK", "input": "00112233445566778899aabbccddeeff", "expectedOutput": "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: RFC Test Vector, 192-bit data, 192-bit KEK", "input": "00112233445566778899aabbccddeeff0001020304050607", "expectedOutput": "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: RFC Test Vector, 192-bit data, 256-bit KEK", "input": "00112233445566778899aabbccddeeff0001020304050607", "expectedOutput": "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: RFC Test Vector, 256-bit data, 256-bit KEK", "input": "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f", "expectedOutput": "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 128-bit KEK", "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", "expectedOutput": "00112233445566778899aabbccddeeff", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 192-bit KEK", "input": "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d", "expectedOutput": "00112233445566778899aabbccddeeff", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 256-bit KEK", "input": "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7", "expectedOutput": "00112233445566778899aabbccddeeff", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: RFC Test Vector, 192-bit data, 192-bit KEK", "input": "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2", "expectedOutput": "00112233445566778899aabbccddeeff0001020304050607", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: RFC Test Vector, 192-bit data, 256-bit KEK", "input": "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1", "expectedOutput": "00112233445566778899aabbccddeeff0001020304050607", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: RFC Test Vector, 256-bit data, 256-bit KEK", "input": "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21", "expectedOutput": "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: invalid KEK length", "input": "00112233445566778899aabbccddeeff", "expectedOutput": "KEK must be either 16, 24, or 32 bytes (currently 10 bytes)", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "00010203040506070809"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: invalid IV length", "input": "00112233445566778899aabbccddeeff", "expectedOutput": "IV must be 8 bytes (currently 6 bytes)", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: input length not multiple of 8", "input": "00112233445566778899aabbccddeeff0102", "expectedOutput": "input must be 8n (n>=2) bytes (currently 18 bytes)", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Wrap: input too short", "input": "0011223344556677", "expectedOutput": "input must be 8n (n>=2) bytes (currently 8 bytes)", "recipeConfig": [ { "op": "AES Key Wrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: invalid KEK length", "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", "expectedOutput": "KEK must be either 16, 24, or 32 bytes (currently 10 bytes)", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "00010203040506070809"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: invalid IV length", "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", "expectedOutput": "IV must be 8 bytes (currently 6 bytes)", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: input length not multiple of 8", "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5e621", "expectedOutput": "input must be 8n (n>=3) bytes (currently 26 bytes)", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: input too short", "input": "1fa68b0a8112b447aef34bd8fb5a7b82", "expectedOutput": "input must be 8n (n>=3) bytes (currently 16 bytes)", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, { "name": "AES Key Unwrap: corrupted input", "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe6", "expectedOutput": "IV mismatch", "recipeConfig": [ { "op": "AES Key Unwrap", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, "Hex", "Hex" ], }, ], }, ]); ================================================ FILE: tests/operations/tests/AlternatingCaps.mjs ================================================ /* @author sw5678 * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "AlternatingCaps: Basic Example", "input": "Hello, world!", "expectedOutput": "hElLo, WoRlD!", "recipeConfig": [ { "op": "Alternating Caps", "args": [] }, ], } ]); ================================================ FILE: tests/operations/tests/AvroToJSON.mjs ================================================ /** * Avro to JSON tests. * * @author jarrodconnolly [jarrod@nestedquotes.ca] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Avro to JSON: no input (force JSON true)", input: "", expectedOutput: "Please provide an input.", recipeConfig: [ { op: "Avro to JSON", args: [true] } ], }, { name: "Avro to JSON: no input (force JSON false)", input: "", expectedOutput: "Please provide an input.", recipeConfig: [ { op: "Avro to JSON", args: [false] } ], }, { name: "Avro to JSON: small (force JSON true)", 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" + "\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" + "\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" + "\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" + "\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" + "\x15\x41", expectedOutput: "{\n \"name\": \"myname\"\n}", recipeConfig: [ { op: "Avro to JSON", args: [true] } ], }, { name: "Avro to JSON: small (force JSON false)", 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" + "\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" + "\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" + "\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" + "\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" + "\x15\x41", expectedOutput: "{\"name\":\"myname\"}\n", recipeConfig: [ { op: "Avro to JSON", args: [false] } ], } ]); ================================================ FILE: tests/operations/tests/BCD.mjs ================================================ /** * BCD tests * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "To BCD: default 0", input: "0", expectedOutput: "0000", recipeConfig: [ { "op": "To BCD", "args": ["8 4 2 1", true, false, "Nibbles"] } ] }, { name: "To BCD: unpacked nibbles", input: "1234567890", expectedOutput: "0000 0001 0000 0010 0000 0011 0000 0100 0000 0101 0000 0110 0000 0111 0000 1000 0000 1001 0000 0000", recipeConfig: [ { "op": "To BCD", "args": ["8 4 2 1", false, false, "Nibbles"] } ] }, { name: "To BCD: packed, signed bytes", input: "1234567890", expectedOutput: "00000001 00100011 01000101 01100111 10001001 00001100", recipeConfig: [ { "op": "To BCD", "args": ["8 4 2 1", true, true, "Bytes"] } ] }, { name: "To BCD: packed, signed nibbles, 8 4 -2 -1", input: "-1234567890", expectedOutput: "0000 0111 0110 0101 0100 1011 1010 1001 1000 1111 0000 1101", recipeConfig: [ { "op": "To BCD", "args": ["8 4 -2 -1", true, true, "Nibbles"] } ] }, { name: "From BCD: default 0", input: "0000", expectedOutput: "0", recipeConfig: [ { "op": "From BCD", "args": ["8 4 2 1", true, false, "Nibbles"] } ] }, { name: "From BCD: packed, signed bytes", input: "00000001 00100011 01000101 01100111 10001001 00001101", expectedOutput: "-1234567890", recipeConfig: [ { "op": "From BCD", "args": ["8 4 2 1", true, true, "Bytes"] } ] }, { name: "From BCD: Excess-3, unpacked, unsigned", input: "00000100 00000101 00000110 00000111 00001000 00001001 00001010 00001011 00001100 00000011", expectedOutput: "1234567890", recipeConfig: [ { "op": "From BCD", "args": ["Excess-3", false, false, "Nibbles"] } ] }, { name: "BCD: raw 4 2 2 1, packed, signed", input: "1234567890", expectedOutput: "1234567890", recipeConfig: [ { "op": "To BCD", "args": ["4 2 2 1", true, true, "Raw"] }, { "op": "From BCD", "args": ["4 2 2 1", true, true, "Raw"] } ] }, ]); ================================================ FILE: tests/operations/tests/BLAKE2b.mjs ================================================ /** * BitwiseOp tests * * @author h345983745 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "BLAKE2b: 512 - Hello World", input: "Hello World", expectedOutput: "4386a08a265111c9896f56456e2cb61a64239115c4784cf438e36cc851221972da3fb0115f73cd02486254001f878ab1fd126aac69844ef1c1ca152379d0a9bd", recipeConfig: [ { "op": "BLAKE2b", "args": ["512", "Hex", {string: "", option: "UTF8"}] } ] }, { name: "BLAKE2b: 384 - Hello World", input: "Hello World", expectedOutput: "4d388e82ca8f866e606b6f6f0be910abd62ad6e98c0adfc27cf35acf948986d5c5b9c18b6f47261e1e679eb98edf8e2d", recipeConfig: [ { "op": "BLAKE2b", "args": ["384", "Hex", {string: "", option: "UTF8"}] } ] }, { name: "BLAKE2b: 256 - Hello World", input: "Hello World", expectedOutput: "1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00", recipeConfig: [ { "op": "BLAKE2b", "args": ["256", "Hex", {string: "", option: "UTF8"}] } ] }, { name: "BLAKE2b: 160 - Hello World", input: "Hello World", expectedOutput: "6a8489e6fd6e51fae12ab271ec7fc8134dd5d737", recipeConfig: [ { "op": "BLAKE2b", "args": ["160", "Hex", {string: "", option: "UTF8"}] } ] }, { name: "BLAKE2b: Key Test", input: "message data", expectedOutput: "3d363ff7401e02026f4a4687d4863ced", recipeConfig: [ { "op": "BLAKE2b", "args": ["128", "Hex", {string: "pseudorandom key", option: "UTF8"}] } ] } ]); ================================================ FILE: tests/operations/tests/BLAKE2s.mjs ================================================ /** * BitwiseOp tests * * @author h345983745 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "BLAKE2s: 256 - Hello World", input: "Hello World", expectedOutput: "7706af019148849e516f95ba630307a2018bb7bf03803eca5ed7ed2c3c013513", recipeConfig: [ { "op": "BLAKE2s", "args": ["256", "Hex", {string: "", option: "UTF8"}] } ] }, { name: "BLAKE2s: 160 - Hello World", input: "Hello World", expectedOutput: "0e4fcfc2ee0097ac1d72d70b595a39e09a3c7c7e", recipeConfig: [ { "op": "BLAKE2s", "args": ["160", "Hex", {string: "", option: "UTF8"}] } ] }, { name: "BLAKE2s: 128 - Hello World", input: "Hello World", expectedOutput: "9964ee6f36126626bf864363edfa96f6", recipeConfig: [ { "op": "BLAKE2s", "args": ["128", "Hex", {string: "", option: "UTF8"}] } ] }, { name: "BLAKE2s: Key Test", input: "Hello World", expectedOutput: "9964ee6f36126626bf864363edfa96f6", recipeConfig: [ { "op": "BLAKE2s", "args": ["128", "Hex", {string: "", option: "UTF8"}] } ] } ]); ================================================ FILE: tests/operations/tests/BLAKE3.mjs ================================================ /** * BLAKE3 tests. * @author xumptex [xumptex@outlook.fr] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "BLAKE3: 8 - Hello world", input: "Hello world", expectedOutput: "e7e6fb7d2869d109", recipeConfig: [ { "op": "BLAKE3", "args": [8, ""] } ] }, { name: "BLAKE3: 16 - Hello world 2", input: "Hello world 2", expectedOutput: "2a3df5fe5f0d3fcdd995fc203c7f7c52", recipeConfig: [ { "op": "BLAKE3", "args": [16, ""] } ] }, { name: "BLAKE3: 32 - Hello world", input: "Hello world", expectedOutput: "e7e6fb7d2869d109b62cdb1227208d4016cdaa0af6603d95223c6a698137d945", recipeConfig: [ { "op": "BLAKE3", "args": [32, ""] } ] }, { name: "BLAKE3: Key Test", input: "Hello world", expectedOutput: "59dd23ac9d025690", recipeConfig: [ { "op": "BLAKE3", "args": [8, "ThiskeyisexactlythirtytwoBytesLo"] } ] }, { name: "BLAKE3: Key Test 2", input: "Hello world", expectedOutput: "c8302c9634c1da42", recipeConfig: [ { "op": "BLAKE3", "args": [8, "ThiskeyisexactlythirtytwoByteslo"] } ] } ]); ================================================ FILE: tests/operations/tests/BSON.mjs ================================================ /** * BSON tests. * * @author n1474335 [n1474335@gmail.com] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "BSON serialise: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "BSON serialise", args: [], }, ], }, { name: "BSON serialise: basic", input: "{\"hello\":\"world\"}", expectedOutput: "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00", recipeConfig: [ { op: "BSON serialise", args: [], }, ], }, { name: "BSON deserialise: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "BSON deserialise", args: [], }, ], }, { name: "BSON deserialise: basic", input: "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00", expectedOutput: "{\n \"hello\": \"world\"\n}", recipeConfig: [ { op: "BSON deserialise", args: [], }, ], }, ]); ================================================ FILE: tests/operations/tests/BaconCipher.mjs ================================================ /** * Bacon Cipher tests. * * @author Karsten Silkenbäumer [github.com/kassi] * @copyright Karsten Silkenbäumer 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; import { BACON_ALPHABETS, BACON_TRANSLATIONS } from "../../../src/core/lib/Bacon.mjs"; const alphabets = Object.keys(BACON_ALPHABETS); const translations = BACON_TRANSLATIONS; TestRegister.addTests([ { name: "Bacon Decode: no input", input: "", expectedOutput: "", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[0], false] } ], }, { name: "Bacon Decode: reduced alphabet 0/1", input: "00011 00100 00010 01101 00011 01000 01100 00110 00001 00000 00010 01101 01100 10100 01101 10000 01001 10001", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[0], false] } ], }, { name: "Bacon Decode: reduced alphabet 0/1 inverse", input: "11100 11011 11101 10010 11100 10111 10011 11001 11110 11111 11101 10010 10011 01011 10010 01111 10110 01110", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[0], true] } ], }, { name: "Bacon Decode: reduced alphabet A/B lower case", input: "aaabb aabaa aaaba abbab aaabb abaaa abbaa aabba aaaab aaaaa aaaba abbab abbaa babaa abbab baaaa abaab baaab", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[1], false] } ], }, { name: "Bacon Decode: reduced alphabet A/B lower case inverse", input: "bbbaa bbabb bbbab baaba bbbaa babbb baabb bbaab bbbba bbbbb bbbab baaba baabb ababb baaba abbbb babba abbba", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[1], true] } ], }, { name: "Bacon Decode: reduced alphabet A/B upper case", input: "AAABB AABAA AAABA ABBAB AAABB ABAAA ABBAA AABBA AAAAB AAAAA AAABA ABBAB ABBAA BABAA ABBAB BAAAA ABAAB BAAAB", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[1], false] } ], }, { name: "Bacon Decode: reduced alphabet A/B upper case inverse", input: "BBBAA BBABB BBBAB BAABA BBBAA BABBB BAABB BBAAB BBBBA BBBBB BBBAB BAABA BAABB ABABB BAABA ABBBB BABBA ABBBA", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[1], true] } ], }, { name: "Bacon Decode: reduced alphabet case code", input: "thiS IsaN exampLe oF ThE bacON cIpher WIth upPPercasE letters tRanSLaTiNG to OnEs anD LoWErcase To zERoes. KS", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[2], false] } ], }, { name: "Bacon Decode: reduced alphabet case code inverse", input: "THIs iS An EXAMPlE Of tHe BACon CiPHER wiTH UPppERCASe LETTERS TrANslAtIng TO oNeS ANd lOweRCASE tO ZerOES. ks", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[2], true] } ], }, { name: "Bacon Decode: reduced alphabet case code", input: "A little example of the Bacon Cipher to be decoded. It is a working example and shorter than my others, but it anyways works tremendously. And just that's important, correct?", expectedOutput: "DECODE", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[3], false] } ], }, { name: "Bacon Decode: reduced alphabet case code inverse", input: "Well, there's now another example which will be not only strange to read but sound weird for everyone not knowing what the thing is about. Nevertheless, works great out of the box.", expectedOutput: "DECODE", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[0], translations[3], true] } ], }, { name: "Bacon Decode: complete alphabet 0/1", input: "00011 00100 00010 01110 00011 01000 01101 00110 00001 00000 00010 01110 01101 10110 01110 10001 01010 10010", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[0], false] } ], }, { name: "Bacon Decode: complete alphabet 0/1 inverse", input: "11100 11011 11101 10001 11100 10111 10010 11001 11110 11111 11101 10001 10010 01001 10001 01110 10101 01101", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[0], true] } ], }, { name: "Bacon Decode: complete alphabet A/B lower case", input: "aaabb aabaa aaaba abbba aaabb abaaa abbab aabba aaaab aaaaa aaaba abbba abbab babba abbba baaab ababa baaba", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[1], false] } ], }, { name: "Bacon Decode: complete alphabet A/B lower case inverse", input: "bbbaa bbabb bbbab baaab bbbaa babbb baaba bbaab bbbba bbbbb bbbab baaab baaba abaab baaab abbba babab abbab", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[1], true] } ], }, { name: "Bacon Decode: complete alphabet A/B upper case", input: "AAABB AABAA AAABA ABBBA AAABB ABAAA ABBAB AABBA AAAAB AAAAA AAABA ABBBA ABBAB BABBA ABBBA BAAAB ABABA BAABA", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[1], false] } ], }, { name: "Bacon Decode: complete alphabet A/B upper case inverse", input: "BBBAA BBABB BBBAB BAAAB BBBAA BABBB BAABA BBAAB BBBBA BBBBB BBBAB BAAAB BAABA ABAAB BAAAB ABBBA BABAB ABBAB", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[1], true] } ], }, { name: "Bacon Decode: complete alphabet case code", input: "thiS IsaN exampLe oF THe bacON cIpher WItH upPPercasE letters tRanSLAtiNG tO OnES anD LOwErcaSe To ZeRoeS. kS", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[2], false] } ], }, { name: "Bacon Decode: complete alphabet case code inverse", input: "THIs iSAn EXAMPlE Of thE BACon CiPHER wiTh UPppERCASe LETTERS TrANslaTIng To zEroES and LoWERcAsE tO oNEs. Ks", expectedOutput: "DECODINGBACONWORKS", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[2], true] } ], }, { name: "Bacon Decode: complete alphabet case code", input: "A little example of the Bacon Cipher to be decoded. It is a working example and shorter than the first, but it anyways works tremendously. And just that's important, correct?", expectedOutput: "DECODE", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[3], false] } ], }, { name: "Bacon Decode: complete alphabet case code inverse", input: "Well, there's now another example which will be not only strange to read but sound weird for everyone knowing nothing what the thing is about. Nevertheless, works great out of the box. ", expectedOutput: "DECODE", recipeConfig: [ { op: "Bacon Cipher Decode", args: [alphabets[1], translations[3], true] } ], }, { name: "Bacon Encode: no input", input: "", expectedOutput: "", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[0], false, false] } ], }, { name: "Bacon Encode: reduced alphabet 0/1", input: "There's a fox, and it jumps over the fence.", 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", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[0], false, false] } ], }, { name: "Bacon Encode: reduced alphabet 0/1 inverse", input: "There's a fox, and it jumps over the fence.", 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", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[0], false, true] } ], }, { name: "Bacon Encode: reduced alphabet 0/1, keeping extra characters", input: "There's a fox, and it jumps over the fence.", expectedOutput: "1001000111001001000000100'10001 00000 001010110110101, 000000110000011 0100010010 0100010011010110111010001 01101100110010010000 100100011100100 0010100100011000001000100.", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[0], true, false] } ], }, { name: "Bacon Encode: reduced alphabet 0/1 inverse, keeping extra characters", input: "There's a fox, and it jumps over the fence.", expectedOutput: "0110111000110110111111011'01110 11111 110101001001010, 111111001111100 1011101101 1011101100101001000101110 10010011001101101111 011011100011011 1101011011100111110111011.", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[0], true, true] } ], }, { name: "Bacon Encode: reduced alphabet A/B", input: "There's a fox, and it jumps over the fence.", 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", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[1], false, false] } ], }, { name: "Bacon Encode: reduced alphabet A/B inverse", input: "There's a fox, and it jumps over the fence.", 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", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[1], false, true] } ], }, { name: "Bacon Encode: reduced alphabet A/B, keeping extra characters", input: "There's a fox, and it jumps over the fence.", expectedOutput: "BAABAAABBBAABAABAAAAAABAA'BAAAB AAAAA AABABABBABBABAB, AAAAAABBAAAAABB ABAAABAABA ABAAABAABBABABBABBBABAAAB ABBABBAABBAABAABAAAA BAABAAABBBAABAA AABABAABAAABBAAAAABAAABAA.", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[1], true, false] } ], }, { name: "Bacon Encode: reduced alphabet A/B inverse, keeping extra characters", input: "There's a fox, and it jumps over the fence.", expectedOutput: "ABBABBBAAABBABBABBBBBBABB'ABBBA BBBBB BBABABAABAABABA, BBBBBBAABBBBBAA BABBBABBAB BABBBABBAABABAABAAABABBBA BAABAABBAABBABBABBBB ABBABBBAAABBABB BBABABBABBBAABBBBBABBBABB.", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[0], translations[1], true, true] } ], }, { name: "Bacon Encode: complete alphabet 0/1", input: "There's a fox, and it jumps over the fence.", 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", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[1], translations[0], false, false] } ], }, { name: "Bacon Encode: complete alphabet 0/1 inverse", input: "There's a fox, and it jumps over the fence.", 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", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[1], translations[0], false, true] } ], }, { name: "Bacon Encode: complete alphabet 0/1, keeping extra characters", input: "There's a fox, and it jumps over the fence.", expectedOutput: "1001100111001001000100100'10010 00000 001010111010111, 000000110100011 0100010011 0100110100011000111110010 01110101010010010001 100110011100100 0010100100011010001000100.", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[1], translations[0], true, false] } ], }, { name: "Bacon Encode: complete alphabet 0/1 inverse, keeping extra characters", input: "There's a fox, and it jumps over the fence.", expectedOutput: "0110011000110110111011011'01101 11111 110101000101000, 111111001011100 1011101100 1011001011100111000001101 10001010101101101110 011001100011011 1101011011100101110111011.", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[1], translations[0], true, true] } ], }, { name: "Bacon Encode: complete alphabet A/B", input: "There's a fox, and it jumps over the fence.", 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", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[1], translations[1], false, false] } ], }, { name: "Bacon Encode: complete alphabet A/B inverse", input: "There's a fox, and it jumps over the fence.", 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", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[1], translations[1], false, true] } ], }, { name: "Bacon Encode: complete alphabet A/B, keeping extra characters", input: "There's a fox, and it jumps over the fence.", expectedOutput: "BAABBAABBBAABAABAAABAABAA'BAABA AAAAA AABABABBBABABBB, AAAAAABBABAAABB ABAAABAABB ABAABBABAAABBAAABBBBBAABA ABBBABABABAABAABAAAB BAABBAABBBAABAA AABABAABAAABBABAAABAAABAA.", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[1], translations[1], true, false] } ], }, { name: "Bacon Encode: complete alphabet A/B inverse, keeping extra characters", input: "There's a fox, and it jumps over the fence.", expectedOutput: "ABBAABBAAABBABBABBBABBABB'ABBAB BBBBB BBABABAAABABAAA, BBBBBBAABABBBAA BABBBABBAA BABBAABABBBAABBBAAAAABBAB BAAABABABABBABBABBBA ABBAABBAAABBABB BBABABBABBBAABABBBABBBABB.", recipeConfig: [ { op: "Bacon Cipher Encode", args: [alphabets[1], translations[1], true, true] } ], }, ]); ================================================ FILE: tests/operations/tests/Base32.mjs ================================================ /** * Base32 Tests * * @author Peter C-S [petercs@purelymail.com] * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; import {ALPHABET_OPTIONS} from "../../../src/core/lib/Base32.mjs"; // Example Standard Base32 Tests const STANDARD_INP = "HELLO BASE32"; const STANDARD_OUT = "JBCUYTCPEBBECU2FGMZA===="; // Example Hex Extended Base32 Tests const EXTENDED_INP = "HELLO BASE32 EXTENDED"; const EXTENDED_OUT = "912KOJ2F41142KQ56CP20HAOAH2KSH258G======"; // All Bytes const ALL_BYTES = [ "\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", ].join(""); const ALL_BYTES_EXTENDED_OUT = "000G40O40K30E209185GO38E1S8124GJ2GAHC5OO34D1M70T3OFI08924CI2A9H750KIKAPC5KN2UC1H68PJ8D9M6SS3IEHR7GUJSFQ085146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB9DLONAUBTG62OJ3CHIMCPR8D5L6MR3DDPNN0SBIEDQ7ATJNF1SNKURSFLV7V041GA1O91C6GU48J2KBHI6OT3SGI699754LIQBPH6CQJEE9R7KVK2GQ58T4KMJAFA59LALQPBDELUOB3CLJMIQRDDTON6TBNF5TNQVS1GE2OF2CBHM7P34SLIUCPN7CVK6HQB9T9LEMQVCDJMMRRJETTNV0S7HE7P75SRJUHQFATFMERRNFU3OV5SVKUNRFFU7PVBTVPVFUVS======"; const ALL_BYTES_STANDARD_OUT = "AAAQEAYEAUDAOCAJBIFQYDIOB4IBCEQTCQKRMFYYDENBWHA5DYPSAIJCEMSCKJRHFAUSUKZMFUXC6MBRGIZTINJWG44DSOR3HQ6T4P2AIFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLJNVYXK6L5QGCYTDMRSWMZ3INFVGW3DNNZXXA4LSON2HK5TXPB4XU634PV7H7AEBQKBYJBMGQ6EITCULRSGY5D4QSGJJHFEVS2LZRGM2TOOJ3HU7UCQ2FI5EUWTKPKFJVKV2ZLNOV6YLDMVTWS23NN5YXG5LXPF5X274BQOCYPCMLRWHZDE4VS6MZXHM7UGR2LJ5JVOW27MNTWW33TO55X7A4HROHZHF43T6R2PK5PWO33XP6DY7F47U6X3PP6HZ7L57Z7P674======"; TestRegister.addTests([ { name: "To Base32 Standard: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Base32", args: [ALPHABET_OPTIONS[0].value], }, ], }, { name: "To Base32 Hex Extended: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Base32", args: [ALPHABET_OPTIONS[1].value], }, ], }, { name: "From Base32 Standard: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Base32", args: [ALPHABET_OPTIONS[0].value, false], }, ], }, { name: "From Base32 Hex Extended: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Base32", args: [ALPHABET_OPTIONS[1].value, false], }, ], }, { name: "To Base32 Standard: " + STANDARD_INP, input: STANDARD_INP, expectedOutput: STANDARD_OUT, recipeConfig: [ { op: "To Base32", args: [ALPHABET_OPTIONS[0].value], }, ], }, { name: "To Base32 Hex Extended: " + EXTENDED_INP, input: EXTENDED_INP, expectedOutput: EXTENDED_OUT, recipeConfig: [ { op: "To Base32", args: [ALPHABET_OPTIONS[1].value], }, ], }, { name: "From Base32 Standard: " + STANDARD_OUT, input: STANDARD_OUT, expectedOutput: STANDARD_INP, recipeConfig: [ { op: "From Base32", args: [ALPHABET_OPTIONS[0].value, false], }, ], }, { name: "From Base32 Hex Extended: " + EXTENDED_OUT, input: EXTENDED_OUT, expectedOutput: EXTENDED_INP, recipeConfig: [ { op: "From Base32", args: [ALPHABET_OPTIONS[1].value, false], }, ], }, { name: "To Base32 Hex Standard: All Bytes", input: ALL_BYTES, expectedOutput: ALL_BYTES_STANDARD_OUT, recipeConfig: [ { op: "To Base32", args: [ALPHABET_OPTIONS[0].value], }, ], }, { name: "To Base32 Hex Extended: All Bytes", input: ALL_BYTES, expectedOutput: ALL_BYTES_EXTENDED_OUT, recipeConfig: [ { op: "To Base32", args: [ALPHABET_OPTIONS[1].value], }, ], }, { name: "From Base32 Hex Standard: All Bytes", input: ALL_BYTES_STANDARD_OUT, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "From Base32", args: [ALPHABET_OPTIONS[0].value, false], }, ], }, { name: "From Base32 Hex Extended: All Bytes", input: ALL_BYTES_EXTENDED_OUT, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "From Base32", args: [ALPHABET_OPTIONS[1].value, false], }, ], }, ]); ================================================ FILE: tests/operations/tests/Base45.mjs ================================================ /** * Base45 tests. * * @author Thomas Weißschuh [thomas@t-8ch.de] * * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const defaultB45Alph = "0-9A-Z $%*+\\-./:"; TestRegister.addTests([ { name: "To Base45: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Base45", args: [defaultB45Alph], }, ], }, { name: "To Base45: Spec encoding example 1", input: "AB", expectedOutput: "BB8", recipeConfig: [ { op: "To Base45", args: [defaultB45Alph], }, ], }, { name: "To Base45: Spec encoding example 2", input: "Hello!!", expectedOutput: "%69 VD92EX0", recipeConfig: [ { op: "To Base45", args: [defaultB45Alph], }, ], }, { name: "To Base45: Spec encoding example 3", input: "base-45", expectedOutput: "UJCLQE7W581", recipeConfig: [ { op: "To Base45", args: [defaultB45Alph], }, ], }, { name: "From Base45: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Base45", args: [defaultB45Alph], }, ], }, { name: "From Base45: Spec decoding example 1", input: "QED8WEX0", expectedOutput: "ietf!", recipeConfig: [ { op: "From Base45", args: [defaultB45Alph], }, ], }, { name: "From Base45: Invalid character", input: "!", expectedOutput: "Character not in alphabet: '!'", recipeConfig: [ { op: "From Base45", args: [defaultB45Alph], }, ], }, { name: "From Base45: Invalid triplet value", input: "ZZZ", expectedOutput: "Triplet too large: 'ZZZ'", recipeConfig: [ { op: "From Base45", args: [defaultB45Alph], }, ], }, ]); ================================================ FILE: tests/operations/tests/Base58.mjs ================================================ /** * Base58 tests. * * @author tlwr [toby@toby.codes] * * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "To Base58 (Bitcoin): nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Base58", args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], }, ], }, { name: "To Base58 (Ripple): nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Base58", args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"], }, ], }, { name: "To Base58 (Bitcoin): 'hello world'", input: "hello world", expectedOutput: "StV1DL6CwTryKyV", recipeConfig: [ { op: "To Base58", args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], }, ], }, { name: "To Base58 (Ripple): 'hello world'", input: "hello world", expectedOutput: "StVrDLaUATiyKyV", recipeConfig: [ { op: "To Base58", args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"], }, ], }, { name: "To Base58 all null", input: "\0\0\0\0\0\0", expectedOutput: "111111", recipeConfig: [ { op: "To Base58", args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], }, ], }, { name: "From Base58 all null", input: "111111", expectedOutput: "\0\0\0\0\0\0", recipeConfig: [ { op: "From Base58", args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], }, ], }, { name: "To Base58 with null prefix and suffix", input: "\0\0\0Hello\0\0\0", expectedOutput: "111D7LMXYjHjTu", recipeConfig: [ { op: "To Base58", args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], }, ], }, { name: "From Base58 with null prefix and suffix", input: "111D7LMXYjHjTu", expectedOutput: "\0\0\0Hello\0\0\0", recipeConfig: [ { op: "From Base58", args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], }, ], }, { name: "From Base58 (Bitcoin): 'StV1DL6CwTryKyV'", input: "StV1DL6CwTryKyV", expectedOutput: "hello world", recipeConfig: [ { op: "From Base58", args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], }, ], }, { name: "From Base58 (Ripple): 'StVrDLaUATiyKyV'", input: "StVrDLaUATiyKyV", expectedOutput: "hello world", recipeConfig: [ { op: "From Base58", args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"], }, ], }, ]); ================================================ FILE: tests/operations/tests/Base62.mjs ================================================ /** * Base62 tests. * * @author tcode2k16 [tcode2k16@gmail.com] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "To Base62: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Base62", args: ["0-9A-Za-z"], }, ], }, { name: "To Base62: Hello, World!", input: "Hello, World!", expectedOutput: "1wJfrzvdbtXUOlUjUf", recipeConfig: [ { op: "To Base62", args: ["0-9A-Za-z"], }, ], }, { name: "To Base62: UTF-8", input: "ნუ პანიკას", expectedOutput: "BPDNbjoGvDCDzHbKT77eWg0vGQrJuWRXltuRVZ", recipeConfig: [ { op: "To Base62", args: ["0-9A-Za-z"], }, ], }, { name: "From Base62: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Base62", args: ["0-9A-Za-z"], }, ], }, { name: "From Base62: Hello, World!", input: "1wJfrzvdbtXUOlUjUf", expectedOutput: "Hello, World!", recipeConfig: [ { op: "From Base62", args: ["0-9A-Za-z"], }, ], }, { name: "From Base62: UTF-8", input: "BPDNbjoGvDCDzHbKT77eWg0vGQrJuWRXltuRVZ", expectedOutput: "ნუ პანიკას", recipeConfig: [ { op: "From Base62", args: ["0-9A-Za-z"], }, ], } ]); ================================================ FILE: tests/operations/tests/Base64.mjs ================================================ /** * Base64 tests. * * @author n1474335 [n1474335@gmail.com] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const ALL_BYTES = [ "\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", ].join(""); TestRegister.addTests([ { name: "To Base64: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Base64", args: ["A-Za-z0-9+/="], }, ], }, { name: "To Base64: Hello, World!", input: "Hello, World!", expectedOutput: "SGVsbG8sIFdvcmxkIQ==", recipeConfig: [ { op: "To Base64", args: ["A-Za-z0-9+/="], }, ], }, { name: "To Base64: UTF-8", input: "ნუ პანიკას", expectedOutput: "4YOc4YOjIOGDnuGDkOGDnOGDmOGDmeGDkOGDoQ==", recipeConfig: [ { op: "To Base64", args: ["A-Za-z0-9+/="], }, ], }, { name: "To Base64: All bytes", input: ALL_BYTES, expectedOutput: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==", recipeConfig: [ { op: "To Base64", args: ["A-Za-z0-9+/="], }, ], }, { name: "From Base64: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Base64", args: ["A-Za-z0-9+/=", true], }, ], }, { name: "From Base64: Hello, World!", input: "SGVsbG8sIFdvcmxkIQ==", expectedOutput: "Hello, World!", recipeConfig: [ { op: "From Base64", args: ["A-Za-z0-9+/=", true], }, ], }, { name: "From Base64: UTF-8", input: "4YOc4YOjIOGDnuGDkOGDnOGDmOGDmeGDkOGDoQ==", expectedOutput: "ნუ პანიკას", recipeConfig: [ { op: "From Base64", args: ["A-Za-z0-9+/=", true], }, ], }, { name: "From Base64: All bytes", input: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==", expectedOutput: ALL_BYTES, recipeConfig: [ { op: "From Base64", args: ["A-Za-z0-9+/=", true], }, ], }, ]); ================================================ FILE: tests/operations/tests/Base85.mjs ================================================ /** * Base85 tests * * @author john19696 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; // Example from Wikipedia const 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."; // Escape newline, quote & backslash const wpOutput = "9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKFCj@.4Gp$d7F!,L7@<6@)/0JDEF@3BB/F*&OCAfu2/AKYi(\ DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF-FD5W8ARlolDIal(\ DIdu\ D.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c"; const 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"; const 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"; TestRegister.addTests([ { name: "To Base85", input: wpExample, expectedOutput: wpOutput, recipeConfig: [ { "op": "To Base85", "args": ["!-u"] } ] }, { name: "From Base85", input: wpOutput + "\n", expectedOutput: wpExample, recipeConfig: [ { "op": "From Base85", "args": ["!-u", true] } ] }, { name: "From Base85", input: wpOutput + "v", expectedError: true, expectedOutput: "From Base85 - Invalid character 'v' at index 337", recipeConfig: [ { "op": "From Base85", "args": ["!-u", false] } ] }, { name: "To Base85", input: allZeroExample, expectedOutput: allZeroOutput, recipeConfig: [ { "op": "To Base85", "args": ["!-u"] } ] }, { name: "From Base85", input: allZeroOutput, expectedOutput: allZeroExample, recipeConfig: [ { "op": "From Base85", "args": ["!-u", true, "z"] } ] }, ]); ================================================ FILE: tests/operations/tests/Base92.mjs ================================================ /** * Base92 tests. * * @author sg5506844 [sg5506844@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "To Base92: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Base92", args: [], }, ], }, { name: "To Base92: Spec encoding example 1", input: "AB", expectedOutput: "8y2", recipeConfig: [ { op: "To Base92", args: [], }, ], }, { name: "To Base92: Spec encoding example 2", input: "Hello!!", expectedOutput: ";K_$aOTo&", recipeConfig: [ { op: "To Base92", args: [], }, ], }, { name: "To Base92: Spec encoding example 3", input: "base-92", expectedOutput: "DX2?VLGA<\/td> {2}SS<\/td> {2}VFISUSGTKSTMPSUNAK<\/td>/, recipeConfig: [ { "op": "Bombe", "args": [ "3-rotor", "", "EKMFLGDQVZNTOWYHXUSPAIBRCJLGA<\/td> {2}AG<\/td> {2}QFIMUMAFKMQSKMYNGW<\/td>/, recipeConfig: [ { "op": "Bombe", "args": [ "3-rotor", "", "EKMFLGDQVZNTOWYHXUSPAIBRCJLGA<\/td> {2}SS<\/td> {2}VFISUSGTKSTMPSUNAK<\/td>/, recipeConfig: [ { "op": "Bombe", "args": [ "3-rotor", "", "EKMFLGDQVZNTOWYHXUSPAIBRCJLGA<\/td> {2}TT<\/td> {2}VFISUSGTKSTMPSUNAK<\/td>/, recipeConfig: [ { "op": "Bombe", "args": [ "3-rotor", "", "EKMFLGDQVZNTOWYHXUSPAIBRCJLGA<\/td> {2}TT AG BO CL EK FF HH II JJ SS YY<\/td> {2}THISISATESTMESSAGE<\/td>/, recipeConfig: [ { "op": "Bombe", "args": [ "3-rotor", "", "EKMFLGDQVZNTOWYHXUSPAIBRCJLHSC<\/td> {2}SS<\/td> {2}HHHSSSGQUUQPKSEKWK<\/td>/, // recipeConfig: [ // { // "op": "Bombe", // "args": [ // "4-rotor", // "LEYJVCNIXWPBQMDRTAKZGFUHOS", // Beta // "EKMFLGDQVZNTOWYHXUSPAIBRCJ?@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-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-^?", 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", recipeConfig: [ { op: "Caret/M-decode", args: [], }, ], }, ]); ================================================ FILE: tests/operations/tests/CartesianProduct.mjs ================================================ /** * Cartesian Product tests. * * @author d98762625 * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Cartesian Product", input: "1 2 3 4 5\n\na b c d e", 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)", recipeConfig: [ { op: "Cartesian Product", args: ["\n\n", " "], }, ], }, { name: "Cartesian Product: too many on left", input: "1 2 3 4 5 6\n\na b c d e", 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)", recipeConfig: [ { op: "Cartesian Product", args: ["\n\n", " "], }, ], }, { name: "Cartesian Product: too many on right", input: "1 2 3 4 5\n\na b c d e f", 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)", recipeConfig: [ { op: "Cartesian Product", args: ["\n\n", " "], }, ], }, { name: "Cartesian Product: item delimiter", input: "1-2-3-4-5\n\na-b-c-d-e", 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)", recipeConfig: [ { op: "Cartesian Product", args: ["\n\n", "-"], }, ], }, { name: "Cartesian Product: sample delimiter", input: "1 2 3 4 5_a b c d e", 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)", recipeConfig: [ { op: "Cartesian Product", args: ["_", " "], }, ], }, ]); ================================================ FILE: tests/operations/tests/CetaceanCipherDecode.mjs ================================================ /** * CetaceanCipher Encode tests * * @author dolphinOnKeys * @copyright Crown Copyright 2022 * @licence Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Cetacean Cipher Decode", input: "EEEEEEEEEeeEEEEe EEEEEEEEEeeEEEeE EEEEEEEEEeeEEEee EEeeEEEEEeeEEeee", expectedOutput: "a b c で", recipeConfig: [ { op: "Cetacean Cipher Decode", args: [] }, ], } ]); ================================================ FILE: tests/operations/tests/CetaceanCipherEncode.mjs ================================================ /** * CetaceanCipher Encode tests * * @author dolphinOnKeys * @copyright Crown Copyright 2022 * @licence Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Cetacean Cipher Encode", input: "a b c で", expectedOutput: "EEEEEEEEEeeEEEEe EEEEEEEEEeeEEEeE EEEEEEEEEeeEEEee EEeeEEEEEeeEEeee", recipeConfig: [ { op: "Cetacean Cipher Encode", args: [] }, ], } ]); ================================================ FILE: tests/operations/tests/ChaCha.mjs ================================================ /** * ChaCha tests. * * @author joostrijneveld [joost@joostrijneveld.nl] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "ChaCha: no key", input: "", expectedOutput: `Invalid key length: 0 bytes. ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`, recipeConfig: [ { "op": "ChaCha", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, 0, "20", "Hex", "Hex", ] } ], }, { name: "ChaCha: no nonce", input: "", expectedOutput: `Invalid nonce length: 0 bytes. ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`, recipeConfig: [ { "op": "ChaCha", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": ""}, 0, "20", "Hex", "Hex", ] } ], }, { name: "ChaCha: RFC8439", input: "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.", 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", recipeConfig: [ { "op": "ChaCha", "args": [ {"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"}, {"option": "Hex", "string": "00:00:00:00:00:00:00:4a:00:00:00:00"}, 1, "20", "Raw", "Hex", ] } ], }, { name: "ChaCha: RFC8439 Raw output", input: "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.", 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", recipeConfig: [ { "op": "ChaCha", "args": [ {"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"}, {"option": "Hex", "string": "00:00:00:00:00:00:00:4a:00:00:00:00"}, 1, "20", "Raw", "Raw", ] }, { "op": "To Hex", "args": [] }, ], }, { name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.1", 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", 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", recipeConfig: [ { "op": "ChaCha", "args": [ {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, 0, "8", "Hex", "Hex", ] } ], }, { name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.2", 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", 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", recipeConfig: [ { "op": "ChaCha", "args": [ {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, 0, "12", "Hex", "Hex", ] } ], }, { name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.3", 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", 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", recipeConfig: [ { "op": "ChaCha", "args": [ {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, 0, "20", "Hex", "Hex", ] } ], }, { name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.4", 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", 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", recipeConfig: [ { "op": "ChaCha", "args": [ {"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"}, {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, 0, "8", "Hex", "Hex", ] } ], }, { name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.5", 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", 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", recipeConfig: [ { "op": "ChaCha", "args": [ {"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"}, {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, 0, "12", "Hex", "Hex", ] } ], }, { name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.6", 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", 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", recipeConfig: [ { "op": "ChaCha", "args": [ {"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"}, {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, 0, "20", "Hex", "Hex", ] } ], }, ]); ================================================ FILE: tests/operations/tests/ChangeIPFormat.mjs ================================================ /** * Change IP format tests. * * @author Chris Smith * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Change IP format: Dotted Decimal to Hex", input: "192.168.1.1", expectedOutput: "c0a80101", recipeConfig: [ { op: "Change IP format", args: ["Dotted Decimal", "Hex"], }, ], }, { name: "Change IP format: Decimal to Dotted Decimal", input: "3232235777", expectedOutput: "192.168.1.1", recipeConfig: [ { op: "Change IP format", args: ["Decimal", "Dotted Decimal"], }, ], }, { name: "Change IP format: Hex to Octal", input: "c0a80101", expectedOutput: "030052000401", recipeConfig: [ { op: "Change IP format", args: ["Hex", "Octal"], }, ], }, { name: "Change IP format: Octal to Decimal", input: "030052000401", expectedOutput: "3232235777", recipeConfig: [ { op: "Change IP format", args: ["Octal", "Decimal"], }, ], }, ]); ================================================ FILE: tests/operations/tests/CharEnc.mjs ================================================ /** * CharEnc tests. * * @author tlwr [toby@toby.codes] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Encode text, Decode text: nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "Encode text", "args": ["UTF-8 (65001)"] }, { "op": "Decode text", "args": ["UTF-8 (65001)"] }, ], }, { name: "Encode text, Decode text: hello", input: "hello", expectedOutput: "hello", recipeConfig: [ { "op": "Encode text", "args": ["UTF-8 (65001)"] }, { "op": "Decode text", "args": ["UTF-8 (65001)"] }, ], }, { name: "Encode text (EBCDIC): hello", input: "hello", expectedOutput: "88 85 93 93 96", recipeConfig: [ { "op": "Encode text", "args": ["IBM EBCDIC International (500)"] }, { "op": "To Hex", "args": ["Space"] }, ], }, { name: "Decode text (EBCDIC): 88 85 93 93 96", input: "88 85 93 93 96", expectedOutput: "hello", recipeConfig: [ { "op": "From Hex", "args": ["Space"] }, { "op": "Decode text", "args": ["IBM EBCDIC International (500)"] }, ], }, { name: "Generate Base64 Windows PowerShell", input: "ZABpAHIAIAAiAGMAOgBcAHAAcgBvAGcAcgBhAG0AIABmAGkAbABlAHMAIgAgAA==", expectedOutput: "dir \"c:\\program files\" ", recipeConfig: [ { "op": "From Base64", "args": ["A-Za-z0-9+/=", true] }, { "op": "Decode text", "args": ["UTF-16LE (1200)"] }, { "op": "Encode text", "args": ["UTF-8 (65001)"] }, ], }, ]); ================================================ FILE: tests/operations/tests/Charts.mjs ================================================ /** * Chart tests. * * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Scatter chart", input: "100 100\n200 200\n300 300\n400 400\n500 500", expectedMatch: /^',type: 'html'}});\")();)]", "\n" ] } ], expectedMatch: /^Invalid JPath expression: Unexpected "{" at character 1/ }, { name: "JPath Expression: Script-based RCE", input: "[{}]", recipeConfig: [ { "op": "JPath expression", "args": [ "$..[?(p=\"console.log(this.process.mainModule.require('child_process').execSync('id').toString())\";a=''[['constructor']][['constructor']](p);a())]", "\n" ] } ], expectedMatch: /^Invalid JPath expression: jsonPath: Cannot read properties of {2}\(reading 'constructor'\): / }, { name: "CSS selector", input: '
        \n

        hello

        \n

        world

        \n

        again

        \n
        ', expectedOutput: '

        hello

        \n

        again

        ', recipeConfig: [ { "op": "CSS selector", "args": ["#test p.a", "\\n"] } ] }, { name: "XPath expression", input: '
        \n

        hello

        \n

        world

        \n

        again

        \n
        ', expectedOutput: '

        hello

        \n

        again

        ', recipeConfig: [ { "op": "XPath expression", "args": ["/div/p[@class=\"a\"]", "\\n"] } ] }, { name: "To MessagePack: no content", input: "", expectedMatch: /Unexpected end of JSON input/, recipeConfig: [ { "op": "To MessagePack", "args": [] } ] }, { name: "From MessagePack: no content", input: "", expectedOutput: "Could not decode MessagePack to JSON: Error: Could not parse", recipeConfig: [ { "op": "From Hex", "args": ["Space"] }, { "op": "From MessagePack", "args": [] } ] }, { name: "To MessagePack: valid json", input: JSON.stringify(JSON_TEST_DATA), 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", recipeConfig: [ { "op": "To MessagePack", "args": [] }, { "op": "To Hex", "args": ["Space"] } ] }, { name: "From MessagePack: valid msgpack", 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", expectedOutput: JSON.stringify(JSON_TEST_DATA, null, 4), recipeConfig: [ { "op": "From Hex", "args": ["Space"] }, { "op": "From MessagePack", "args": [] } ] } ]); ================================================ FILE: tests/operations/tests/Colossus.mjs ================================================ /** * Colossus tests. * @author VirtualColossus [martin@virtualcolossus.co.uk] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Colossus Letter Count", input: "CTBKJUVXHZ-H3L4QV+YEZUK+SXOZ/N", expectedMatch: /00 00 : a30/, recipeConfig: [ { "op": "Colossus", "args": [ "", "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" ] } ] }, // Takes a while to run, so disabling for general purpose testing. Re-enable if modifying this operation. // { // name: "Colossus 1+2=.", // 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", // expectedMatch: /31 05 : a3108/, // recipeConfig: [ // { // "op": "Colossus", // "args": [ // "", // "KH Pattern", // "ΔZ", "ΔΧ", "", // "None", "Select Program", "1+2=. (1+2 Break In, Find X1,X2)", // "", // "", "", "", "", "", false, "", // "", "", "", "", "", false, "", // "", "", "", "", "", false, "", // false, // "", // false, false, false, false, false, // "", false, false, "", // "", // 3100, "X1", "X2", // "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" // ] // } // ] // }, // { // name: "Colossus 4=5=/1=2", // 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", // expectedMatch: /15 08 : a969/, // recipeConfig: [ // { // "op": "Colossus", // "args": [ // "", // "KH Pattern", // "ΔZ", "ΔΧ", "", // "None", "Select Program", "4=5=/1=2 (Given X1,X2 find X4,X5)", // "", // "", "", "", "", "", false, "", // "", "", "", "", "", false, "", // "", "", "", "", "", false, "", // false, // "", // false, false, false, false, false, // "", false, false, "", // "", // 960, "X4", "X5", // "31", "5", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" // ] // } // ] // }, ]); ================================================ FILE: tests/operations/tests/Comment.mjs ================================================ /** * Flow Control tests. * * @author tlwr [toby@toby.codes] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const ALL_BYTES = [ "\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", ].join(""); TestRegister.addTests([ { name: "Comment: nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "Comment", "args": [""] } ] }, { name: "Fork, Comment, Base64", input: "cat\nsat\nmat", expectedOutput: "Y2F0\nc2F0\nbWF0", recipeConfig: [ { "op": "Fork", "args": ["\\n", "\\n", false] }, { "op": "Comment", "args": ["Testing 123"] }, { "op": "To Base64", "args": ["A-Za-z0-9+/="] } ] }, { name: "Label, Comment: Complex content", input: ALL_BYTES, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "Label", args: [""] }, { op: "Comment", args: [""] } ] } ]); ================================================ FILE: tests/operations/tests/Compress.mjs ================================================ /** * Compress tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Bzip2 decompress", input: "425a6839314159265359b218ed630000031380400104002a438c00200021a9ea601a10003202185d5ed68ca6442f1e177245385090b218ed63", expectedOutput: "The cat sat on the mat.", recipeConfig: [ { "op": "From Hex", "args": ["Space"] }, { "op": "Bzip2 Decompress", "args": [] } ], }, { name: "LZMA compress & decompress", input: "The cat sat on the mat.", // Generated using command `echo -n "The cat sat on the mat." | lzma -z -6 | xxd -p` expectedOutput: "The cat sat on the mat.", recipeConfig: [ { "op": "LZMA Compress", "args": ["6"] }, { "op": "LZMA Decompress", "args": [] }, ], }, { name: "LZMA decompress: binary", // 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` input: "5d00008000ffffffffffffffff00000052500a84f99bb28021a969d627e03e8a922effffbd160000", expectedOutput: "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10", recipeConfig: [ { "op": "From Hex", "args": ["Space"] }, { "op": "LZMA Decompress", "args": [] }, { "op": "To Hex", "args": ["Space", 0] } ], }, { name: "LZMA decompress: string", // Generated using command `echo -n "The cat sat on the mat." | lzma -z -6 | xxd -p` input: "5d00008000ffffffffffffffff002a1a08a202b1a4b814b912c94c4152e1641907d3fd8cd903ffff4fec0000", expectedOutput: "The cat sat on the mat.", recipeConfig: [ { "op": "From Hex", "args": ["Space"] }, { "op": "LZMA Decompress", "args": [] } ], }, { name: "LZ4 Compress", input: "The cat sat on the mat.", expectedOutput: "04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000", recipeConfig: [ { "op": "LZ4 Compress", "args": [] }, { "op": "To Hex", "args": ["None", 0] } ], }, { name: "LZ4 Decompress", input: "04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000", expectedOutput: "The cat sat on the mat.", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "LZ4 Decompress", "args": [] } ], }, ]); ================================================ FILE: tests/operations/tests/ConditionalJump.mjs ================================================ /** * Conditional Jump tests * * @author tlwr [toby@toby.codes] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Conditional Jump: Skips 0", input: [ "should be changed", ].join("\n"), expectedOutput: [ "YzJodmRXeGtJR0psSUdOb1lXNW5aV1E9" ].join("\n"), recipeConfig: [ { op: "Conditional Jump", args: ["match", false, "", 0], }, { op: "To Base64", args: ["A-Za-z0-9+/="], }, { op: "To Base64", args: ["A-Za-z0-9+/="], }, ], }, { name: "Conditional Jump: Skips 1", input: [ "should be changed", ].join("\n"), // Expecting base32, not base64 output expectedOutput: [ "ONUG65LMMQQGEZJAMNUGC3THMVSA====", ].join("\n"), recipeConfig: [ { op: "Conditional Jump", args: ["should", false, "skip match", 10], }, { op: "To Base64", args: ["A-Za-z0-9+/="], }, { op: "Label", args: ["skip match"], }, { op: "To Base32", args: ["A-Z2-7="], }, ], }, { name: "Conditional Jump: Skips backwards", input: [ "match", ].join("\n"), expectedOutput: [ "f7cf556f7f4fc6635db8c314f7a81f2a", ].join("\n"), recipeConfig: [ { op: "Label", args: ["back to the beginning"], }, { op: "Jump", args: ["skip replace"], }, { op: "MD2", args: [], }, { op: "Label", args: ["skip replace"], }, { op: "Conditional Jump", args: ["match", false, "back to the beginning", 10], }, ], } ]); ================================================ FILE: tests/operations/tests/ConvertCoordinateFormat.mjs ================================================ /** * Convert co-ordinate format tests * * @author j433866 * * @copyright Crown Copyright 2019 * @license Apache-2.0 */ /** * TEST CO-ORDINATES * DD: 51.504°,-0.126°, * DDM: 51° 30.24',-0° 7.56', * DMS: 51° 30' 14.4",-0° 7' 33.6", * Geohash: gcpvj0h0x, * MGRS: 30U XC 99455 09790, * OSNG: TQ 30163 80005, * UTM: 30N 699456 5709791, */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Co-ordinates: From Decimal Degrees to Degrees Minutes Seconds", input: "51.504°,-0.126°,", expectedOutput: "51° 30' 14.4\",-0° 7' 33.6\",", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Degrees Minutes Seconds", "Comma", "None", 1] }, ], }, { name: "Co-ordinates: From Degrees Minutes Seconds to Decimal Degrees", input: "51° 30' 14.4\",-0° 7' 33.6\",", expectedOutput: "51.504°,-0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Degrees Minutes Seconds", "Comma", "Decimal Degrees", "Comma", "None", 3] }, ], }, { name: "Co-ordinates: From Decimal Degrees to Degrees Decimal Minutes", input: "51.504°,-0.126°,", expectedOutput: "51° 30.24',-0° 7.56',", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Degrees Decimal Minutes", "Comma", "None", 2] } ] }, { name: "Co-ordinates: From Degrees Decimal Minutes to Decimal Degrees", input: "51° 30.24',-0° 7.56',", expectedOutput: "51.504°,-0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Degrees Decimal Minutes", "Comma", "Decimal Degrees", "Comma", "None", 3] } ] }, { name: "Co-ordinates: From Decimal Degrees to Decimal Degrees", input: "51.504°,-0.126°,", expectedOutput: "51.504°,-0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "None", 3] } ] }, { name: "Co-ordinates: From Decimal Degrees to Geohash", input: "51.504°,-0.126°,", expectedOutput: "gcpvj0h0x,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Geohash", "Comma", "None", 9] }, ], }, { name: "Co-ordinates: From Geohash to Decimal Degrees", input: "gcpvj0h0x,", expectedOutput: "51.504°,-0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Geohash", "Comma", "Decimal Degrees", "Comma", "None", 3] }, ], }, { name: "Co-ordinates: From Decimal Degrees to MGRS", input: "51.504°,-0.126°,", expectedOutput: "30U XC 99455 09790,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Military Grid Reference System", "Comma", "None", 10] }, ], }, { name: "Co-ordinates: From MGRS to Decimal Degrees", input: "30U XC 99455 09790,", expectedOutput: "51.504°,-0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Military Grid Reference System", "Comma", "Decimal Degrees", "Comma", "None", 3] } ] }, { name: "Co-ordinates: From Decimal Degrees to OSNG", input: "51.504°,-0.126°,", expectedOutput: "TQ 30163 80005,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Ordnance Survey National Grid", "Comma", "None", 10] }, ], }, { name: "Co-ordinates: From OSNG to Decimal Degrees", input: "TQ 30163 80005,", expectedOutput: "51.504°,-0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Ordnance Survey National Grid", "Comma", "Decimal Degrees", "Comma", "None", 3] }, ], }, { name: "Co-ordinates: From Decimal Degrees to UTM", input: "51.504°,-0.126°,", expectedOutput: "30 N 699456 5709791,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Universal Transverse Mercator", "Comma", "None", 0] }, ], }, { name: "Co-ordinates: From UTM to Decimal Degrees", input: "30 N 699456 5709791,", expectedOutput: "51.504°,-0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Universal Transverse Mercator", "Comma", "Decimal Degrees", "Comma", "None", 3] }, ], }, { name: "Co-ordinates: Directions in input, not output", input: "N51.504°,W0.126°,", expectedOutput: "51.504°,-0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "None", 3] }, ], }, { name: "Co-ordinates: Directions in input and output", input: "N51.504°,W0.126°,", expectedOutput: "N 51.504°,W 0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "Before", 3] }, ], }, { name: "Co-ordinates: Directions not in input, in output", input: "51.504°,-0.126°,", expectedOutput: "N 51.504°,W 0.126°,", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "Before", 3] }, ], }, { name: "Co-ordinates: Directions not in input, in converted output", input: "51.504°,-0.126°,", expectedOutput: "N 51° 30' 14.4\",W 0° 7' 33.6\",", recipeConfig: [ { op: "Convert co-ordinate format", args: ["Decimal Degrees", "Comma", "Degrees Minutes Seconds", "Comma", "Before", 3] }, ], } ]); ================================================ FILE: tests/operations/tests/ConvertLeetSpeak.mjs ================================================ /** * @author bartblaze [] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Convert to Leet Speak: basic text", input: "leet", expectedOutput: "l337", recipeConfig: [ { op: "Convert Leet Speak", args: ["To Leet Speak"] } ] }, { name: "Convert from Leet Speak: basic leet", input: "l337", expectedOutput: "leet", recipeConfig: [ { op: "Convert Leet Speak", args: ["From Leet Speak"] } ] }, { name: "Convert to Leet Speak: basic text, keep case", input: "HELLO", expectedOutput: "H3LL0", recipeConfig: [ { op: "Convert Leet Speak", args: ["To Leet Speak"] } ] }, { name: "Convert from Leet Speak: basic leet, keep case", input: "H3LL0", expectedOutput: "HeLLo", recipeConfig: [ { op: "Convert Leet Speak", args: ["From Leet Speak"] } ] } ]); ================================================ FILE: tests/operations/tests/ConvertToNATOAlphabet.mjs ================================================ /** * @author MarvinJWendt [git@marvinjwendt.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Convert to NATO alphabet: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "Convert to NATO alphabet", args: [] } ] }, { name: "Convert to NATO alphabet: full alphabet with numbers", input: "abcdefghijklmnopqrstuvwxyz0123456789,/.", 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 ", recipeConfig: [ { op: "Convert to NATO alphabet", args: [] } ] } ]); ================================================ FILE: tests/operations/tests/Crypt.mjs ================================================ /** * Crypt tests. * * @author n1474335 [n1474335@gmail.com] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ /** * Ciphers * * The following expectedOutputs were generated using the following command format: * > openssl enc -aes-128-cbc -in test.txt -out test.enc -K "00112233445566778899aabbccddeeff" -iv "00112233445566778899aabbccddeeff" * > xxd -p test.enc | tr -d '\n' | xclip -selection clipboard * * All random data blocks (binary input, keys and IVs) were generated from /dev/urandom using dd: * > dd if=/dev/urandom of=key.txt bs=16 count=1 * * * The following is a Python script used to generate the AES-GCM tests. * It uses PyCryptodome (https://www.pycryptodome.org) to handle the AES encryption and decryption. * * from Crypto.Cipher import AES * import binascii * * input_data = "0123456789ABCDEF" * key = binascii.unhexlify("00112233445566778899aabbccddeeff") * iv = binascii.unhexlify("ffeeddccbbaa99887766554433221100") * aad = b'additional data' * * cipher = AES.new(key, AES.MODE_GCM, nonce=iv) * cipher.update(aad) * cipher_text, tag = cipher.encrypt_and_digest(binascii.unhexlify(input_data)) * * cipher = AES.new(key, AES.MODE_GCM, nonce=iv) * cipher.update(aad) * decrypted = cipher.decrypt_and_verify(cipher_text, tag) * * key = binascii.hexlify(key).decode("UTF-8") * iv = binascii.hexlify(iv).decode("UTF-8") * cipher_text = binascii.hexlify(cipher_text).decode("UTF-8") * tag = binascii.hexlify(tag).decode("UTF-8") * decrypted = binascii.hexlify(decrypted).decode("UTF-8") * * print("Key: {}\nIV : {}\nInput data: {}\nAAD: {}\n\nEncrypted ciphertext: {}\nGCM tag: {}\n\nDecrypted plaintext : {}".format(key, iv, input_data, aad, cipher_text, tag, decrypted)) * * * Outputs: * Key: 00112233445566778899aabbccddeeff * IV : ffeeddccbbaa99887766554433221100 * Input data: 0123456789ABCDEF * * Encrypted ciphertext: 8feeafedfdb2f6f9 * GCM tag: 654ef4957c6e2b0cc6501d8f9bcde032 * * Decrypted plaintext : 0123456789abcdef */ { name: "AES Encrypt: no key", input: "", expectedOutput: `Invalid key length: 0 bytes The following algorithms will be used based on the size of the key: 16 bytes = AES-128 24 bytes = AES-192 32 bytes = AES-256`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "CBC", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-CBC with IV0, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "2ef6c3fdb1314b5c2c326a2087fe1a82d5e73bf605ec8431d73e847187fc1c8fbbe969c177df1ecdf8c13f2f505f9498", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CBC", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-CTR with IV0, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "a98c9e8e3b7c894384d740e4f0f4ed0be2bbb1e0e13a255812c3c6b0a629e4ad759c075b2469c6f4fb2c0cf9", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CTR", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-CBC with IV1, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "4fa077d50cc71a57393e7b542c4e3aea0fb75383b97083f2f568ffc13c0e7a47502ec6d9f25744a061a3a5e55fe95e8d", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, "CBC", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-CFB, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "369e1c9e5a85b0520f3e61eecc37759246ad0a02cae7a99a3d250ae39cad4743385375cf63720d52ae8cdfb9", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, "CFB", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-OFB, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "369e1c9e5a85b0520f3e61eecc37759288cb378c5fa9c675bd6c4ede0ae6a925eaebc8e0a6162d2a000ddc0f", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, "OFB", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-CTR, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "369e1c9e5a85b0520f3e61eecc37759206f6f1ba63527af96fae3b15a921844df2e542902a4f0525dbb4146b", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, "CTR", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-ECB, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "2ef6c3fdb1314b5c2c326a2087fe1a8238c5a5db7dff38f6f4eb75b2e55cab3d8d6113eb8d3517223b4545fcdb4c5a48", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": ""}, "ECB", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-GCM, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: `d0bcace0fa3a214b0ac3cbb4ac2caaf97b965f172f66d2a4ec6304a15a4072f1b28a6f9b80473f86bfa47b2c Tag: 16a3e732a605cc9ca29108f742ca0743`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": ""}, "GCM", "Raw", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-GCM, ASCII, AAD", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: `daa58faa056c52756aa488aeafbd265b6effcf4eca58220a97b0005b1a9b1e1c9e7a6725d35f5f79b9493de7 Tag: 3b5378917f67b0aade9891fc6c291646`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "ffeeddccbbaa99887766554433221100"}, "GCM", "Raw", "Hex", {"option": "UTF8", "string": "additional data"} ] } ], }, { name: "AES Encrypt: AES-128-CBC, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "bf2ccb148e5df181a46f39764047e24fc94cc46bbe6c8d160fc25a977e4b630883e9e04d3eeae3ccbb2d57a4c22e61909f2b6d7b24940abe95d356ce986294270d0513e0ffe7a9928fa6669e1aaae4379310281dc27c0bb9e254684b2ecd7f5f944c8218f3bc680570399a508dfe4b65", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CBC", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-CFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "17211941bb2fa43d54d9fa59072436422a55be7a2be164cf5ec4e50e7a0035094ab684dab8d45a4515ae95c4136ded98898f74d4ecc4ac57ae682a985031ecb7518ddea6c8d816349801aa22ff0b6ac1784d169060efcd9fb77d564477038eb09bb4e1ce", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CFB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-OFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "17211941bb2fa43d54d9fa5907243642bfd805201c130c8600566720cf87562011f0872598f1e69cfe541bb864de7ed68201e0a34284157b581984dab3fe2cb0f20cb80d0046740df3e149ec4c92c0e81f2dc439a6f3a05c5ef505eae6308b301c673cfa", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "OFB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-CTR, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "17211941bb2fa43d54d9fa5907243642baf08c837003bf24d7b81a911ce41bd31de8a92f6dc6d11135b70c73ea167c3fc4ea78234f58652d25e23245dbcb895bf4165092d0515ae8f14230f8a34b06957f24ba4b24db741490e7edcd6e5310945cc159fc", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CTR", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-GCM, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: `5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e Tag: 70fad2ca19412c20f40fd06918736e56`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-128-GCM, Binary, AAD", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: `5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e Tag: 61cc4b70809452b0b3e38f913fa0a109`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "UTF8", "string": "additional data"} ] } ], }, { name: "AES Encrypt: AES-128-ECB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "869c057637a58cc3363bcc4bcfa62702abf85dff44300eb9fdcfb9d845772c8acb557c8d540baae2489c6758abef83d81b74239bef87c6c944c1b00ca160882bc15be9a6a3de4e6a50a2eab8b635c634027ed7eae4c1d2f08477c38b7dc24f6915da235bc3051f3a50736b14db8863e4", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "ECB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-192-CBC, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "1aec90cd7f629ef68243881f3e2b793a548cbcdad69631995a6bd0c8aea1e948d8a5f3f2b7e7f9b77da77434c92a6257a9f57e937b883f4400511b990888a0b1d27c0a4b7f298e6f50b563135edc9fa7d8eceb6bc8163e6153a20cf07aa1e705bc5cb3a37b0452b4019cef8000d7c1b7", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CBC", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-192-CFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "fc370a6c013b3c05430fbce810cb97d39cb0a587320a4c1b57d0c0d08e93cb0d1221abba9df09b4b1332ce923b289f92000e6b4f7fbc55dfdab9179081d8c36ef4a0e3d3a49f1564715c5d3e88f8bf6d3dd77944f22f99a03b5535a3cd47bc44d4a9665c", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CFB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-192-OFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "fc370a6c013b3c05430fbce810cb97d33605d11b2531c8833bc3e818003bbd7dd58b2a38d10d44d25d11bd96228b264a4d2aad1d0a7af2cfad0e70c1ade305433e95cb0ee693447f6877a59a4be5c070d19afba23ff10caf5ecfa7a9c2877b8df23d61f2", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "OFB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-192-CTR, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "fc370a6c013b3c05430fbce810cb97d340525303ae59c5e9b73ad5ff3e65ce3abf00431e0a292d990f732a397de589420827beb1c28623c56972eb2ddf0cf3f82e3c30e155df7f64a530419c28fc51a9091c73df78e73958bee1d1acd8676c9c0f1915ca", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CTR", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-192-GCM, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: `318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b Tag: 86db597d5302595223cadbd990f1309b`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-192-GCM, Binary, AAD", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: `318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b Tag: aeedf3e6ca4201577c0cf3e9ce58159d`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "UTF8", "string": "additional data"} ] } ], }, { name: "AES Encrypt: AES-192-ECB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "56ef533db50a3b33951a76acede52b7d54fbae7fb07da20daa3e2731e5721ee4c13ab15ac80748c14dece982310530ad65480512a4cf70201473fb7bc3480446bc86b1ff9b4517c4c1f656bc236fab1aca276ae5af25f5871b671823f3cb3e426da059dd83a13f125bd6cfe600c331b0", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "ECB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-256-CBC, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "bc60a7613559e23e8a7be8e98a1459003fdb036f33368d8a30156c51464b49472705a4ddae05da96956ce058bb180dd301c5fd58bf6a2ded0d7dd4da85fd5ba43a4297691532bf7f4cd92bfcfd3704faf2f9bd5425049b34433ba90fb85c80646e6cb09ee4e4059e7cd753a2fef8bbad", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CBC", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-256-CFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "5dc73709da5cb0ac914ae4bcb621fd75169eac5ff13a2dde573f6380ff812e8ddb58f0e9afaec1ff0d6d2af0659e10c05b714ec97481a15f4a7aeb4c6ea84112ce897459b54ed9e77a794f023f2bef1901f013cf435432fca5fb59e2be781916247d2334", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CFB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-256-OFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "5dc73709da5cb0ac914ae4bcb621fd75b6e1f909b88733f784b1df8a52dc200440a1076415d009a7c12cac1e8ab76bdc290e6634cd5bf8a416fda8dcfd7910e55fe9d1148cd85d7a59adad39ab089e111d8f8da246e2e874cf5d9ab7552af6308320a5ab", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "OFB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-256-CTR, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "5dc73709da5cb0ac914ae4bcb621fd7591356d4169898c986a90b193f4d1f0d5cba1d10b2bfc5aee8a48dce9dba174cecf56f92dddf7eb306d78360000eea7bcb50f696d84a3757a822800ed68f9edf118dc61406bacf64f022717d8cb6010049bf75d7e", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CTR", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-256-GCM, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: `1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f Tag: 821b1e5f32dad052e502775a523d957a`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "AES Encrypt: AES-256-GCM, Binary, AAD", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: `1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f Tag: a8f04c4d93bbef82bef61a103371aef9`, recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "UTF8", "string": "additional data"} ] } ], }, { name: "AES Encrypt: AES-256-ECB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "7e8521ba3f356ef692a51841807e141464aadc07bbc0ef2b628b8745bae356d245682a220688afca7be987b60cb120681ed42680ee93a67065619a3beaac11111a6cd88a6afa9e367722cb57df343f8548f2d691b295184da4ed5f3b763aaa8558502cb348ab58e81986337096e90caa", recipeConfig: [ { "op": "AES Encrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "ECB", "Hex", "Hex", {"option": "Hex", "string": ""} ] } ], }, { name: "DES Encrypt: no key", input: "", expectedOutput: `Invalid key length: 0 bytes DES uses a key length of 8 bytes (64 bits).`, recipeConfig: [ { "op": "DES Encrypt", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "CBC", "Hex", "Hex" ] } ], }, { name: "DES Encrypt: DES-CBC, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "6500defb824b0eb8ccbf1fa9689c6f5bcc65247d93ecb0e573232824bca82dd41e2361f8fd82ef187de9f3b74f7ba3ca2b4e735f3ca6304fb8dd1675933c576424b1ea72b3219bdab62fce56d49c820d5ac02a4702a6d688e90b0933de97da21e4829e5cf85caae8", recipeConfig: [ { "op": "DES Encrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "CBC", "Hex", "Hex" ] } ], }, { name: "DES Encrypt: DES-CFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "09015087e15b09374bc9edba80ce41e6809e332fc1e988858749fb2f4ebbd6483a6fce01a43271280c07c90e13d517729acac45beef7d088339eb7e084bbbb7459fc8bb592d2ca76b90066dc79b1fbc5e016208e1d02c6e48ab675530f8040e53e1a138b", recipeConfig: [ { "op": "DES Encrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "CFB", "Hex", "Hex" ] } ], }, { name: "DES Encrypt: DES-OFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "09015087e15b09374d8879bac14dbad851dd08fb131353a8c510acc4570e97720dd159465f1c7da3cac4a50521e1c1ab87e8cf5b0aa0c1d2eaa8a1ed914a26c13b2b0a76a368f08812fc7fa4b7c047f27df0c35e5f53b8a20e2ffc10e55d388cae8070db", recipeConfig: [ { "op": "DES Encrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "OFB", "Hex", "Hex" ] } ], }, { // play.golang.org/p/4Qm2hfLGsqc name: "DES Encrypt: DES-CTR, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "09015087e15b0937c462fd5974af0c4b5880de136a5680453c99f4500628cbeca769623515d836985110b93eacfea7fa4a7b2b3cb4f67acbb5f7e8ddb5a5d445da74bf6572b0a874befa3888c81110776388e400afd8dc908dcc0c018c7753355f8a1c9f", recipeConfig: [ { "op": "DES Encrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "CTR", "Hex", "Hex" ] } ], }, { name: "DES Encrypt: DES-ECB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "8dea4c6a35d5f6a419232159a0b039798d0a0b20fd1e559b1d04f8eb1120e8bca6ed5b3a4bc2b23d3b62312e6085d9e837677569fe79a65eba7cb4a2969e099fc1bd649e9c8aeb2c4c519e085db6974819257c20fde70acabc976308cc41635038c91acf5eefff1e", recipeConfig: [ { "op": "DES Encrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "ECB", "Hex", "Hex" ] } ], }, { name: "Triple DES Encrypt: no key", input: "", expectedOutput: `Invalid key length: 0 bytes Triple DES uses a key length of 24 bytes (192 bits).`, recipeConfig: [ { "op": "Triple DES Encrypt", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "CBC", "Hex", "Hex" ] } ], }, { name: "Triple DES Encrypt: DES-EDE3-CBC, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "f826c9116ea932eb7027a810b5ce21109c4ef2563c9f3ba5e2518f72484e88f8d3f6ff3f334f64bb6bb9ff91b70f6f29c037b10dee5fe16d7f0f41c9a7ecdd83f113a1dd66ab70783ee458c2366bf5fbc016f7c168c43c11d607692a3280e3750a6154a86b62c48d", recipeConfig: [ { "op": "Triple DES Encrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "CBC", "Hex", "Hex" ] } ], }, { name: "Triple DES Encrypt: DES-EDE3-CFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "874d32cd7bdae52c3690875e265a2fac7ced685e5ec4436a6bb5a5c18be185f4526683a5bc7ae86f00523034fb725ab4c8285a6967ccca1b76f6331718c26e12ea67fc924071f81ce0035a9dd31705bcd6467991cae5504d70424e6339459db5b33cbc8a", recipeConfig: [ { "op": "Triple DES Encrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "CFB", "Hex", "Hex" ] } ], }, { name: "Triple DES Encrypt: DES-EDE3-OFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "874d32cd7bdae52c8f61672860f715d14819c0270320a8ad71083b38bd8954bbada3c77af641590b00a678524d748668fe3dfa83f71835c411cdbdd8e73a70656324b7faaba16e1d8dba260d8f965fe7a91110134c19076f1eeb46393038c22c559fe490", recipeConfig: [ { "op": "Triple DES Encrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "OFB", "Hex", "Hex" ] } ], }, { // play.golang.org/p/RElT6pVeNz2 name: "Triple DES Encrypt: DES-EDE3-CTR, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "874d32cd7bdae52cd8630d3ab2bf373e7110e13713caa6a8bfed9d9dd802d0ebe93128ac0d0f05abcc56237b75fb69207dba11e68ddc4b0118a4c75e7248bbd80aaba4dd4436642546ec6ca7fa7526f3b0018ed5194c409dc2c1484530b968af554984f3", recipeConfig: [ { "op": "Triple DES Encrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "CTR", "Hex", "Hex" ] } ], }, { name: "Triple DES Encrypt: DES-EDE3-ECB Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "aa81f23d1b3abebd68ac560e051a711c2923843beecddb0f7fe4113bd1874e73cccf3a2a494bb011e154ca2737b4d0eb5978a10316361074ed368d85d5aff5c8555ea101b0a468e58780a74c7830c561674c183c972a2b48931adf789cb16df304e169500f8c95ad", recipeConfig: [ { "op": "Triple DES Encrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "ECB", "Hex", "Hex" ] } ], }, { name: "AES Decrypt: no key", input: "", expectedOutput: `Invalid key length: 0 bytes The following algorithms will be used based on the size of the key: 16 bytes = AES-128 24 bytes = AES-192 32 bytes = AES-256`, recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "CBC", "Hex", "Raw", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-CBC with IV0, ASCII", input: "2ef6c3fdb1314b5c2c326a2087fe1a82d5e73bf605ec8431d73e847187fc1c8fbbe969c177df1ecdf8c13f2f505f9498", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CBC", "Hex", "Raw", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-CTR with IV0, ASCII", input: "a98c9e8e3b7c894384d740e4f0f4ed0be2bbb1e0e13a255812c3c6b0a629e4ad759c075b2469c6f4fb2c0cf9", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CTR", "Hex", "Raw", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-CBC with IV, ASCII", input: "4fa077d50cc71a57393e7b542c4e3aea0fb75383b97083f2f568ffc13c0e7a47502ec6d9f25744a061a3a5e55fe95e8d", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, "CBC", "Hex", "Raw", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-CFB, ASCII", input: "369e1c9e5a85b0520f3e61eecc37759246ad0a02cae7a99a3d250ae39cad4743385375cf63720d52ae8cdfb9", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, "CFB", "Hex", "Raw", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-OFB, ASCII", input: "369e1c9e5a85b0520f3e61eecc37759288cb378c5fa9c675bd6c4ede0ae6a925eaebc8e0a6162d2a000ddc0f", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, "OFB", "Hex", "Raw", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-CTR, ASCII", input: "369e1c9e5a85b0520f3e61eecc37759206f6f1ba63527af96fae3b15a921844df2e542902a4f0525dbb4146b", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, "CTR", "Hex", "Raw", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-ECB, ASCII", input: "2ef6c3fdb1314b5c2c326a2087fe1a8238c5a5db7dff38f6f4eb75b2e55cab3d8d6113eb8d3517223b4545fcdb4c5a48", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": ""}, "ECB", "Hex", "Raw", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-GCM, ASCII", input: "d0bcace0fa3a214b0ac3cbb4ac2caaf97b965f172f66d2a4ec6304a15a4072f1b28a6f9b80473f86bfa47b2c", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": ""}, "GCM", "Hex", "Raw", {"option": "Hex", "string": "16a3e732a605cc9ca29108f742ca0743"}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-GCM, ASCII, AAD", input: "daa58faa056c52756aa488aeafbd265b6effcf4eca58220a97b0005b1a9b1e1c9e7a6725d35f5f79b9493de7", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "ffeeddccbbaa99887766554433221100"}, "GCM", "Hex", "Raw", {"option": "Hex", "string": "3b5378917f67b0aade9891fc6c291646"}, {"option": "UTF8", "string": "additional data"} ] } ], }, { name: "AES Decrypt: AES-128-CBC, Binary", input: "bf2ccb148e5df181a46f39764047e24fc94cc46bbe6c8d160fc25a977e4b630883e9e04d3eeae3ccbb2d57a4c22e61909f2b6d7b24940abe95d356ce986294270d0513e0ffe7a9928fa6669e1aaae4379310281dc27c0bb9e254684b2ecd7f5f944c8218f3bc680570399a508dfe4b65", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CBC", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-CFB, Binary", input: "17211941bb2fa43d54d9fa59072436422a55be7a2be164cf5ec4e50e7a0035094ab684dab8d45a4515ae95c4136ded98898f74d4ecc4ac57ae682a985031ecb7518ddea6c8d816349801aa22ff0b6ac1784d169060efcd9fb77d564477038eb09bb4e1ce", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CFB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-OFB, Binary", input: "17211941bb2fa43d54d9fa5907243642bfd805201c130c8600566720cf87562011f0872598f1e69cfe541bb864de7ed68201e0a34284157b581984dab3fe2cb0f20cb80d0046740df3e149ec4c92c0e81f2dc439a6f3a05c5ef505eae6308b301c673cfa", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "OFB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-CTR, Binary", input: "17211941bb2fa43d54d9fa5907243642baf08c837003bf24d7b81a911ce41bd31de8a92f6dc6d11135b70c73ea167c3fc4ea78234f58652d25e23245dbcb895bf4165092d0515ae8f14230f8a34b06957f24ba4b24db741490e7edcd6e5310945cc159fc", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CTR", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-GCM, Binary", input: "5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": "70fad2ca19412c20f40fd06918736e56"}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-128-GCM, Binary, AAD", input: "5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": "61cc4b70809452b0b3e38f913fa0a109"}, {"option": "UTF8", "string": "additional data"} ] } ], }, { name: "AES Decrypt: AES-128-ECB, Binary", input: "869c057637a58cc3363bcc4bcfa62702abf85dff44300eb9fdcfb9d845772c8acb557c8d540baae2489c6758abef83d81b74239bef87c6c944c1b00ca160882bc15be9a6a3de4e6a50a2eab8b635c634027ed7eae4c1d2f08477c38b7dc24f6915da235bc3051f3a50736b14db8863e4", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "51e201d463698ef5f717f71f5b4712af"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "ECB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-192-CBC, Binary", input: "1aec90cd7f629ef68243881f3e2b793a548cbcdad69631995a6bd0c8aea1e948d8a5f3f2b7e7f9b77da77434c92a6257a9f57e937b883f4400511b990888a0b1d27c0a4b7f298e6f50b563135edc9fa7d8eceb6bc8163e6153a20cf07aa1e705bc5cb3a37b0452b4019cef8000d7c1b7", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CBC", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-192-CFB, Binary", input: "fc370a6c013b3c05430fbce810cb97d39cb0a587320a4c1b57d0c0d08e93cb0d1221abba9df09b4b1332ce923b289f92000e6b4f7fbc55dfdab9179081d8c36ef4a0e3d3a49f1564715c5d3e88f8bf6d3dd77944f22f99a03b5535a3cd47bc44d4a9665c", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CFB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-192-OFB, Binary", input: "fc370a6c013b3c05430fbce810cb97d33605d11b2531c8833bc3e818003bbd7dd58b2a38d10d44d25d11bd96228b264a4d2aad1d0a7af2cfad0e70c1ade305433e95cb0ee693447f6877a59a4be5c070d19afba23ff10caf5ecfa7a9c2877b8df23d61f2", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "OFB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-192-CTR, Binary", input: "fc370a6c013b3c05430fbce810cb97d340525303ae59c5e9b73ad5ff3e65ce3abf00431e0a292d990f732a397de589420827beb1c28623c56972eb2ddf0cf3f82e3c30e155df7f64a530419c28fc51a9091c73df78e73958bee1d1acd8676c9c0f1915ca", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CTR", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-192-GCM, Binary", input: "318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": "86db597d5302595223cadbd990f1309b"}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-192-GCM, Binary, AAD", input: "318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": "aeedf3e6ca4201577c0cf3e9ce58159d"}, {"option": "UTF8", "string": "additional data"} ] } ], }, { name: "AES Decrypt: AES-192-ECB, Binary", input: "56ef533db50a3b33951a76acede52b7d54fbae7fb07da20daa3e2731e5721ee4c13ab15ac80748c14dece982310530ad65480512a4cf70201473fb7bc3480446bc86b1ff9b4517c4c1f656bc236fab1aca276ae5af25f5871b671823f3cb3e426da059dd83a13f125bd6cfe600c331b0", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "ECB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-256-CBC, Binary", input: "bc60a7613559e23e8a7be8e98a1459003fdb036f33368d8a30156c51464b49472705a4ddae05da96956ce058bb180dd301c5fd58bf6a2ded0d7dd4da85fd5ba43a4297691532bf7f4cd92bfcfd3704faf2f9bd5425049b34433ba90fb85c80646e6cb09ee4e4059e7cd753a2fef8bbad", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CBC", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-256-CFB, Binary", input: "5dc73709da5cb0ac914ae4bcb621fd75169eac5ff13a2dde573f6380ff812e8ddb58f0e9afaec1ff0d6d2af0659e10c05b714ec97481a15f4a7aeb4c6ea84112ce897459b54ed9e77a794f023f2bef1901f013cf435432fca5fb59e2be781916247d2334", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CFB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-256-OFB, Binary", input: "5dc73709da5cb0ac914ae4bcb621fd75b6e1f909b88733f784b1df8a52dc200440a1076415d009a7c12cac1e8ab76bdc290e6634cd5bf8a416fda8dcfd7910e55fe9d1148cd85d7a59adad39ab089e111d8f8da246e2e874cf5d9ab7552af6308320a5ab", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "OFB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-256-CTR, Binary", input: "5dc73709da5cb0ac914ae4bcb621fd7591356d4169898c986a90b193f4d1f0d5cba1d10b2bfc5aee8a48dce9dba174cecf56f92dddf7eb306d78360000eea7bcb50f696d84a3757a822800ed68f9edf118dc61406bacf64f022717d8cb6010049bf75d7e", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "CTR", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-256-GCM, Binary", input: "1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": "821b1e5f32dad052e502775a523d957a"}, {"option": "Hex", "string": ""} ] } ], }, { name: "AES Decrypt: AES-256-GCM, Binary, AAD", input: "1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "GCM", "Hex", "Hex", {"option": "Hex", "string": "a8f04c4d93bbef82bef61a103371aef9"}, {"option": "UTF8", "string": "additional data"} ] } ], }, { name: "AES Decrypt: AES-256-ECB, Binary", input: "7e8521ba3f356ef692a51841807e141464aadc07bbc0ef2b628b8745bae356d245682a220688afca7be987b60cb120681ed42680ee93a67065619a3beaac11111a6cd88a6afa9e367722cb57df343f8548f2d691b295184da4ed5f3b763aaa8558502cb348ab58e81986337096e90caa", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "AES Decrypt", "args": [ {"option": "Hex", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, {"option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, "ECB", "Hex", "Hex", {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""} ] } ], }, { name: "DES Decrypt: no key", input: "", expectedOutput: `Invalid key length: 0 bytes DES uses a key length of 8 bytes (64 bits).`, recipeConfig: [ { "op": "DES Decrypt", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "CBC", "Hex", "Hex" ] } ], }, { name: "DES Decrypt: DES-CBC, Binary", input: "6500defb824b0eb8ccbf1fa9689c6f5bcc65247d93ecb0e573232824bca82dd41e2361f8fd82ef187de9f3b74f7ba3ca2b4e735f3ca6304fb8dd1675933c576424b1ea72b3219bdab62fce56d49c820d5ac02a4702a6d688e90b0933de97da21e4829e5cf85caae8", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "DES Decrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "CBC", "Hex", "Hex" ] } ], }, { name: "DES Decrypt: DES-CFB, Binary", input: "09015087e15b09374bc9edba80ce41e6809e332fc1e988858749fb2f4ebbd6483a6fce01a43271280c07c90e13d517729acac45beef7d088339eb7e084bbbb7459fc8bb592d2ca76b90066dc79b1fbc5e016208e1d02c6e48ab675530f8040e53e1a138b", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "DES Decrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "CFB", "Hex", "Hex" ] } ], }, { name: "DES Decrypt: DES-OFB, Binary", input: "09015087e15b09374d8879bac14dbad851dd08fb131353a8c510acc4570e97720dd159465f1c7da3cac4a50521e1c1ab87e8cf5b0aa0c1d2eaa8a1ed914a26c13b2b0a76a368f08812fc7fa4b7c047f27df0c35e5f53b8a20e2ffc10e55d388cae8070db", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "DES Decrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "OFB", "Hex", "Hex" ] } ], }, { // play.golang.org/p/FpvqncmPk7R name: "DES Decrypt: DES-CTR, Binary", input: "09015087e15b0937ab0ae5a84d66e520893690a6ea066382bf1330e8876cb3aa82ccc634f8f0d458bbe0257df6f4637cdac89f311168ba91208a21ba4bdd13c4b1a92cb93b33364b5b94a5d3d7fba68f6eed5807d9f5afeb7fbffcd94792131d264004ae", expectedOutput: "7a0e643132750e96b76dc9efa7810bea2b8feaa5b97887e44f96c0e6d506cc4dd4665683c6f63139221f8d887fd0a05b39741f8a67d87d6ac6f8dc6b668bd3e4a97b8bd3a19eafd5cdf50c3e1b3f17d61087d0b67cf6db31fec338b75f5954942c852829", recipeConfig: [ { "op": "DES Decrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "CTR", "Hex", "Hex" ] } ], }, { name: "DES Decrypt: DES-ECB, Binary", input: "8dea4c6a35d5f6a419232159a0b039798d0a0b20fd1e559b1d04f8eb1120e8bca6ed5b3a4bc2b23d3b62312e6085d9e837677569fe79a65eba7cb4a2969e099fc1bd649e9c8aeb2c4c519e085db6974819257c20fde70acabc976308cc41635038c91acf5eefff1e", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "DES Decrypt", "args": [ {"option": "Hex", "string": "58345efb0a64e87e"}, {"option": "Hex", "string": "533ed1378bfd929e"}, "ECB", "Hex", "Hex" ] } ], }, { name: "Triple DES Decrypt: no key", input: "", expectedOutput: `Invalid key length: 0 bytes Triple DES uses a key length of 24 bytes (192 bits).`, recipeConfig: [ { "op": "Triple DES Decrypt", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "CBC", "Hex", "Hex" ] } ], }, { name: "Triple DES Decrypt: DES-EDE3-CBC, Binary", input: "f826c9116ea932eb7027a810b5ce21109c4ef2563c9f3ba5e2518f72484e88f8d3f6ff3f334f64bb6bb9ff91b70f6f29c037b10dee5fe16d7f0f41c9a7ecdd83f113a1dd66ab70783ee458c2366bf5fbc016f7c168c43c11d607692a3280e3750a6154a86b62c48d", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "Triple DES Decrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "CBC", "Hex", "Hex" ] } ], }, { name: "Triple DES Decrypt: DES-EDE3-CFB, Binary", input: "874d32cd7bdae52c3690875e265a2fac7ced685e5ec4436a6bb5a5c18be185f4526683a5bc7ae86f00523034fb725ab4c8285a6967ccca1b76f6331718c26e12ea67fc924071f81ce0035a9dd31705bcd6467991cae5504d70424e6339459db5b33cbc8a", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "Triple DES Decrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "CFB", "Hex", "Hex" ] } ], }, { name: "Triple DES Decrypt: DES-EDE3-OFB, Binary", input: "874d32cd7bdae52c8f61672860f715d14819c0270320a8ad71083b38bd8954bbada3c77af641590b00a678524d748668fe3dfa83f71835c411cdbdd8e73a70656324b7faaba16e1d8dba260d8f965fe7a91110134c19076f1eeb46393038c22c559fe490", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "Triple DES Decrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "OFB", "Hex", "Hex" ] } ], }, { // play.golang.org/p/iBacN9kX_RO name: "Triple DES Decrypt: DES-EDE3-CTR, Binary", input: "874d32cd7bdae52c254687e2d7e7093b077af2ec70878f99315f52a21ded5fb10c80a47e6271384335ac47376c758f675484fd7b8be9568aaec643f0d15cffdf3fe54ef3a1b2da50d5d8c7994d7a4a94e0a13a4d437443f0f1f39e93dd13ff06a80c66e4", expectedOutput: "7a0e643132750e9625205bc6fb10dc848c53b7cb5a654d1242aecb6191ad3b5114727e5044a0ee11311575873c54829a80f9471ac473a0bbe5e791a23be75062f7e8f2210d998f9fbbaf3a5bb3dacd494d42d82950e3ab273f821eb979168315a80ad20f", recipeConfig: [ { "op": "Triple DES Decrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "CTR", "Hex", "Hex" ] } ], }, { name: "Triple DES Decrypt: DES-EDE3-ECB, Binary", input: "aa81f23d1b3abebd68ac560e051a711c2923843beecddb0f7fe4113bd1874e73cccf3a2a494bb011e154ca2737b4d0eb5978a10316361074ed368d85d5aff5c8555ea101b0a468e58780a74c7830c561674c183c972a2b48931adf789cb16df304e169500f8c95ad", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "Triple DES Decrypt", "args": [ {"option": "Hex", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, {"option": "Hex", "string": "14f67ac044a84da6"}, "ECB", "Hex", "Hex" ] } ], }, { name: "RC2 Encrypt: no key", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "d3644d898b51a544f690b506c3fd0caeb7a1e6097f7ea28f69b909a4d8805c9a05f4cade8b281d3f044fa069374efb90e94723622c86afc17caee394ffbee0abe627de299208460eb981c9d56f9df885091c6c89e2ee173264b2820b8e67675214e6545a05dc0d3f", recipeConfig: [ { "op": "RC2 Encrypt", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "Hex", "Hex" ] } ], }, { name: "RC2 Encrypt: RC2-CBC, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "d25e5bc6c9311ef196d6f21cc4b0274b29fcca366aba5256406e02bf4ae628398f84e7d72ad92025ede76df4752d1510fe9c3492efb1dcf0be2cd41d619e10b9dd5a2304c2efbd3598d3b87f1a21f326d45e65537563436cfb6e4a41ec3733182ddc058f96f74a6c", recipeConfig: [ { "op": "RC2 Encrypt", "args": [ {"option": "Hex", "string": "eb970554bb213430f4bb4e5988a6a218"}, {"option": "Hex", "string": "ae817c784a097e0c"}, "Hex", "Hex" ] } ], }, { name: "RC2 Encrypt: RC2-ECB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "a160bf23b2a85eaa43d26753e51aaa899f162ec0da7280fffd41b705c5309c7fef2bbb56bf261cab4eadd3a5c69e0a67d45e426d1097187cc9a959b4d979a9d40df26f3dc8d030453fe27701438b78d3ce044330b4b5dca7832537ecf40b914f1b1dc16d4e6d7229", recipeConfig: [ { "op": "RC2 Encrypt", "args": [ {"option": "Hex", "string": "eb970554bb213430f4bb4e5988a6a218"}, {"option": "Hex", "string": ""}, "Hex", "Hex" ] } ], }, { name: "RC2 Decrypt: no key", input: "d3644d898b51a544f690b506c3fd0caeb7a1e6097f7ea28f69b909a4d8805c9a05f4cade8b281d3f044fa069374efb90e94723622c86afc17caee394ffbee0abe627de299208460eb981c9d56f9df885091c6c89e2ee173264b2820b8e67675214e6545a05dc0d3f", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "RC2 Decrypt", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "Hex", "Hex" ] } ], }, { name: "RC2 Decrypt: RC2-CBC, Binary", input: "d25e5bc6c9311ef196d6f21cc4b0274b29fcca366aba5256406e02bf4ae628398f84e7d72ad92025ede76df4752d1510fe9c3492efb1dcf0be2cd41d619e10b9dd5a2304c2efbd3598d3b87f1a21f326d45e65537563436cfb6e4a41ec3733182ddc058f96f74a6c", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "RC2 Decrypt", "args": [ {"option": "Hex", "string": "eb970554bb213430f4bb4e5988a6a218"}, {"option": "Hex", "string": "ae817c784a097e0c"}, "Hex", "Hex" ] } ], }, { name: "RC2 Decrypt: RC2-ECB, Binary", input: "a160bf23b2a85eaa43d26753e51aaa899f162ec0da7280fffd41b705c5309c7fef2bbb56bf261cab4eadd3a5c69e0a67d45e426d1097187cc9a959b4d979a9d40df26f3dc8d030453fe27701438b78d3ce044330b4b5dca7832537ecf40b914f1b1dc16d4e6d7229", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "RC2 Decrypt", "args": [ {"option": "Hex", "string": "eb970554bb213430f4bb4e5988a6a218"}, {"option": "Hex", "string": ""}, "Hex", "Hex" ] } ], }, /* The following expectedOutputs are generated with this Python script with pyCryptoDome from Crypto.Cipher import Blowfish import binascii # Blowfish cipher parameters - key, mode, iv, segment_size, nonce key = binascii.unhexlify("0011223344556677") mode = Blowfish.MODE_CBC kwargs = {} iv = binascii.unhexlify("ffeeddccbbaa9988") if mode in [Blowfish.MODE_CBC, Blowfish.MODE_CFB, Blowfish.MODE_OFB]: kwargs = {"iv": iv} if mode == Blowfish.MODE_CFB: kwargs["segment_size"] = 64 if mode == Blowfish.MODE_CTR: nonce = binascii.unhexlify("0000000000000000") nonce = nonce[:7] kwargs["nonce"] = nonce cipher = Blowfish.new(key, mode, **kwargs) # Input data and padding input_data = b"The quick brown fox jumps over the lazy dog." if mode == Blowfish.MODE_ECB or mode == Blowfish.MODE_CBC: padding_len = 8-(len(input_data) & 7) for i in range(padding_len): input_data += bytes([padding_len]) # Encrypted text cipher_text = cipher.encrypt(input_data) cipher_text = binascii.hexlify(cipher_text).decode("UTF-8") print("Encrypted: {}".format(cipher_text)) */ { name: "Blowfish Encrypt: ECB, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "f7784137ab1bf51546c0b120bdb7fed4509116e49283b35fab0e4292ac86251a9bf908330e3393815e3356bb26524027", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "0000000000000000"}, // IV "ECB", // Mode "Raw", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: ECB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "3d1bf0e87d83782d435a0ca58179ca290184867f52295af5c0fb4dcac7c6c68942906bb421d05925cc7d9cd21532376a0f6ae4c3f008b250381ffa9624f5eb697dbd44de48cf5593ea7dbf5842238474b546ceeb29f6cf327a7d13698786b8d14451f52fb0f5760a", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "0000000000000000"}, // IV "ECB", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Decrypt: ECB, ASCII", input: "f7784137ab1bf51546c0b120bdb7fed4509116e49283b35fab0e4292ac86251a9bf908330e3393815e3356bb26524027", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "0000000000000000"}, // IV "ECB", // Mode "Hex", // Input "Raw" // Output ] } ], }, { name: "Blowfish Decrypt: ECB, Binary", input: "3d1bf0e87d83782d435a0ca58179ca290184867f52295af5c0fb4dcac7c6c68942906bb421d05925cc7d9cd21532376a0f6ae4c3f008b250381ffa9624f5eb697dbd44de48cf5593ea7dbf5842238474b546ceeb29f6cf327a7d13698786b8d14451f52fb0f5760a", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "0000000000000000"}, // IV "ECB", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: CBC, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "398433f39e938286a35fc240521435b6972f3fe96846b54ab9351aa5fa9e10a6a94074e883d1cb36cb9657c817274b60", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "CBC", // Mode "Raw", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: CBC, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "3b42c51465896524e66c2fd2404c8c2b4eb26c760671f131c3372d374f48283ca9a5404d3d8aabd2a886c6551393ca41c682580f1c81f16046e3bec7b59247bdfca1d40bf2ad8ede9de99cb44b36658f775999d37776b3b1a085b9530e54ece69e1875e1bdc8cdcf", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "CBC", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Decrypt: CBC, ASCII", input: "398433f39e938286a35fc240521435b6972f3fe96846b54ab9351aa5fa9e10a6a94074e883d1cb36cb9657c817274b60", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "CBC", // Mode "Hex", // Input "Raw" // Output ] } ], }, { name: "Blowfish Decrypt: CBC, Binary", input: "3b42c51465896524e66c2fd2404c8c2b4eb26c760671f131c3372d374f48283ca9a5404d3d8aabd2a886c6551393ca41c682580f1c81f16046e3bec7b59247bdfca1d40bf2ad8ede9de99cb44b36658f775999d37776b3b1a085b9530e54ece69e1875e1bdc8cdcf", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "CBC", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: CFB, ASCII", input: "The quick brown fox jumps over the lazy dog.", // pyCryptoDome produces a different value with default settings. This is due to segment_size having // a default value of 8 bits. Setting it to 64 (one full block) will yield the same result. expectedOutput: "c8ca123592570c1fcb138d4ec08f7af14ad49363245be1ac25029c8ffc508b3217e75faaa5566426180fec8f", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "CFB", // Mode "Raw", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: CFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", // see above. pyCryptoDome produces a different value with its default settings expectedOutput: "e6ac1324d1576beab00e855de3f4ac1f5e3cbf89f4c2a743a5737895067ac5012e5bdb92477e256cc07bf691b58e721179b550e694abb0be7cbdc42586db755bf795f4338f47d356c57453afa6277e46aaeb3405f9744654a477f06c2ad92ede90555759", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "CFB", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Decrypt: CFB, ASCII", input: "c8ca123592570c1fcb138d4ec08f7af14ad49363245be1ac25029c8ffc508b3217e75faaa5566426180fec8f", expectedOutput: "The quick brown fox jumps over the lazy dog.", // see above. pyCryptoDome produces a different value with its default settings recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "CFB", // Mode "Hex", // Input "Raw" // Output ] } ], }, { name: "Blowfish Decrypt: CFB, Binary", input: "e6ac1324d1576beab00e855de3f4ac1f5e3cbf89f4c2a743a5737895067ac5012e5bdb92477e256cc07bf691b58e721179b550e694abb0be7cbdc42586db755bf795f4338f47d356c57453afa6277e46aaeb3405f9744654a477f06c2ad92ede90555759", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", // see above. pyCryptoDome produces a different value with its default settings recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "CFB", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: OFB, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "c8ca123592570c1fffcee88b9823b9450dc9c48e559123c1df1984214212bae7e44114d29dba79683d10cce5", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "OFB", // Mode "Raw", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: OFB, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "e6ac1324d1576bea4ceb5be7691c35e4919f18be06cc2a926025ef0973222e987de7c63cd71ed3b19190ba006931d9cbdf412f5b1ac7155904ca591f693fe11aa996e17866e0de4b2eb7ff5effabf94b0f49ed159202caf72745ac2f024d86f942d83767", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "OFB", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Decrypt: OFB, ASCII", input: "c8ca123592570c1fffcee88b9823b9450dc9c48e559123c1df1984214212bae7e44114d29dba79683d10cce5", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "OFB", // Mode "Hex", // Input "Raw" // Output ] } ], }, { name: "Blowfish Decrypt: OFB, Binary", input: "e6ac1324d1576bea4ceb5be7691c35e4919f18be06cc2a926025ef0973222e987de7c63cd71ed3b19190ba006931d9cbdf412f5b1ac7155904ca591f693fe11aa996e17866e0de4b2eb7ff5effabf94b0f49ed159202caf72745ac2f024d86f942d83767", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key {"option": "Hex", "string": "ffeeddccbbaa9988"}, // IV "OFB", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: CTR, ASCII", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "e2a5e0f03ad4877101c7cf83861ad93477adb57acac4bebc315a7bae34b4e6a54e5532db457a3131dcd9dda6", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key // pyCryptoDome only allows the size of the nonce to be [0,7] bytes. // Internally, it right-pads the nonce to 7 bytes long if it wasn't already 7 bytes, // and the last (8th) byte is used for counter. // Therefore a pyCryptoDome nonce of "aabbccdd" is equivalent to an IV of "aabbccdd00000000" here. {"option": "Hex", "string": "0000000000000000"}, // IV (nonce) "CTR", // Mode "Raw", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt: CTR, Binary", input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", expectedOutput: "ccc3e1e179d4e084b2e27cef77255595ebfb694a9999b7ef8e661086058472dad7f3e0350fde9be87059ab43d5b800aa08be4c00f3f2e99402fe2702c39e8663dbcbb146700d63432227f1045f116bfd4b65022ca20b70427ddcfd7441cb3c75f4d3fff0", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key // See notes above {"option": "Hex", "string": "0000000000000000"}, // IV (nonce) "CTR", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Decrypt: CTR, ASCII", input: "e2a5e0f03ad4877101c7cf83861ad93477adb57acac4bebc315a7bae34b4e6a54e5532db457a3131dcd9dda6", expectedOutput: "The quick brown fox jumps over the lazy dog.", recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key // See notes above {"option": "Hex", "string": "0000000000000000"}, // IV (nonce) "CTR", // Mode "Hex", // Input "Raw" // Output ] } ], }, { name: "Blowfish Decrypt: CTR, Binary", input: "ccc3e1e179d4e084b2e27cef77255595ebfb694a9999b7ef8e661086058472dad7f3e0350fde9be87059ab43d5b800aa08be4c00f3f2e99402fe2702c39e8663dbcbb146700d63432227f1045f116bfd4b65022ca20b70427ddcfd7441cb3c75f4d3fff0", expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", recipeConfig: [ { "op": "Blowfish Decrypt", "args": [ {"option": "Hex", "string": "0011223344556677"}, // Key // See notes above {"option": "Hex", "string": "0000000000000000"}, // IV (nonce) "CTR", // Mode "Hex", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt with variable key length: CBC, ASCII, 4 bytes", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "823f337a53ecf121aa9ec1b111bd5064d1d7586abbdaaa0c8fd0c6cc43c831c88bf088ee3e07287e3f36cf2e45f9c7e6", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "00112233"}, // Key {"option": "Hex", "string": "0000000000000000"}, // IV "CBC", // Mode "Raw", // Input "Hex" // Output ] } ], }, { name: "Blowfish Encrypt with variable key length: CBC, ASCII, 42 bytes", input: "The quick brown fox jumps over the lazy dog.", expectedOutput: "19f5a68145b34321cfba72226b0f33922ce44dd6e7869fe328db64faae156471216f12ed2a37fd0bdd7cebf867b3cff0", recipeConfig: [ { "op": "Blowfish Encrypt", "args": [ {"option": "Hex", "string": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead"}, // Key {"option": "Hex", "string": "0000000000000000"}, // IV "CBC", // Mode "Raw", // Input "Hex" // Output ] } ], } ]); ================================================ FILE: tests/operations/tests/DateTime.mjs ================================================ /** * DateTime tests. * * @author bwhitn [brian.m.whitney@outlook.com] * * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Filetime to Unix", input: "129207366395297693", expectedOutput: "1276263039529769300", recipeConfig: [ { op: "Windows Filetime to UNIX Timestamp", args: ["Nanoseconds (ns)", "Decimal"], }, ], }, { name: "Unix to Filetime", input: "1276263039529769300", expectedOutput: "129207366395297693", recipeConfig: [ { op: "UNIX Timestamp to Windows Filetime", args: ["Nanoseconds (ns)", "Decimal"], }, ], }, { name: "DateTime Delta Positive", input: "20/02/2024 13:36:00", expectedOutput: "20/02/2024 13:37:00", recipeConfig: [ { op: "DateTime Delta", args: ["Standard date and time", "DD/MM/YYYY HH:mm:ss", "Add", 0, 0, 1, 0], }, ], }, { name: "DateTime Delta Negative", input: "20/02/2024 14:37:00", expectedOutput: "20/02/2024 13:37:00", recipeConfig: [ { op: "DateTime Delta", args: ["Standard date and time", "DD/MM/YYYY HH:mm:ss", "Subtract", 0, 1, 0, 0], }, ], }, ]); ================================================ FILE: tests/operations/tests/DefangIP.mjs ================================================ /** * DefangIP tests. * * @author h345983745 * * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Defang IP: Valid IPV4", input: "192.168.1.1", expectedOutput: "192[.]168[.]1[.]1", recipeConfig: [ { op: "Defang IP Addresses", args: [], }, ], }, { name: "Defang IP: Valid IPV6", input: "2001:0db8:85a3:0000:0000:8a2e:0370:7343", expectedOutput: "2001[:]0db8[:]85a3[:]0000[:]0000[:]8a2e[:]0370[:]7343", recipeConfig: [ { op: "Defang IP Addresses", args: [], }, ], }, { name: "Defang IP: Valid IPV6 Shorthand", input: "2001:db8:3c4d:15::1a2f:1a2b", expectedOutput: "2001[:]db8[:]3c4d[:]15[:][:]1a2f[:]1a2b", recipeConfig: [ { op: "Defang IP Addresses", args: [], }, ], }, ]); ================================================ FILE: tests/operations/tests/DisassembleARM.mjs ================================================ /** * Disassemble ARM tests. * * @author MedjedThomasXM * * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ // ==================== ARM32 TESTS ==================== { name: "Disassemble ARM: ARM32 NOP (mov r0, r0)", input: "00 00 a0 e1", expectedMatch: /mov\s+r0,\s*r0/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM32 bx lr", input: "1e ff 2f e1", expectedMatch: /bx\s+lr/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM32 push {fp, lr}", input: "00 48 2d e9", expectedMatch: /push\s+\{fp,\s*lr\}/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM32 add fp, sp, #4", input: "04 b0 8d e2", expectedMatch: /add\s+fp,\s*sp/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM32 ldr r0, [r1]", input: "00 00 91 e5", expectedMatch: /ldr\s+r0,\s*\[r1\]/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM32 str r0, [r1]", input: "00 00 81 e5", expectedMatch: /str\s+r0,\s*\[r1\]/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM32 bl (branch link)", input: "00 00 00 eb", expectedMatch: /bl\s+/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM32 mul r0, r1, r2", input: "91 02 00 e0", expectedMatch: /mul\s+r0,\s*r1,\s*r2/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, // ==================== ARM32 THUMB TESTS ==================== { name: "Disassemble ARM: Thumb mov r0, r0", input: "00 46", expectedMatch: /mov\s+r0,\s*r0/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "Thumb", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: Thumb bx lr", input: "70 47", expectedMatch: /bx\s+lr/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "Thumb", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: Thumb push {r4, lr}", input: "10 b5", expectedMatch: /push\s+\{r4,\s*lr\}/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "Thumb", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: Thumb pop {r4, pc}", input: "10 bd", expectedMatch: /pop\s+\{r4,\s*pc\}/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "Thumb", "Little Endian", 0, true, true], }, ], }, // ==================== ARM64 TESTS ==================== { name: "Disassemble ARM: ARM64 ret", input: "c0 03 5f d6", expectedMatch: /ret/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 mov x0, #0", input: "00 00 80 d2", expectedMatch: /mov[z]?\s+x0,\s*#0/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 stp x29, x30, [sp, #-16]!", input: "fd 7b bf a9", expectedMatch: /stp\s+x29,\s*x30,\s*\[sp/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 ldp x29, x30, [sp], #16", input: "fd 7b c1 a8", expectedMatch: /ldp\s+x29,\s*x30,\s*\[sp\]/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 add x0, x1, x2", input: "20 00 02 8b", expectedMatch: /add\s+x0,\s*x1,\s*x2/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 sub x0, x1, x2", input: "20 00 02 cb", expectedMatch: /sub\s+x0,\s*x1,\s*x2/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 mul x0, x1, x2", input: "20 7c 02 9b", expectedMatch: /mul\s+x0,\s*x1,\s*x2/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 ldr x0, [x1]", input: "20 00 40 f9", expectedMatch: /ldr\s+x0,\s*\[x1\]/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 str x0, [x1]", input: "20 00 00 f9", expectedMatch: /str\s+x0,\s*\[x1\]/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 bl (branch link)", input: "00 00 00 94", expectedMatch: /bl\s+/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 cbz x0", input: "00 00 00 b4", expectedMatch: /cbz\s+x0/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 cbnz x0", input: "00 00 00 b5", expectedMatch: /cbnz\s+x0/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 sub sp, sp, #0x20", input: "ff 83 00 d1", expectedMatch: /sub\s+sp,\s*sp/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 add sp, sp, #0x20", input: "ff 83 00 91", expectedMatch: /add\s+sp,\s*sp/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, // ==================== MULTI-INSTRUCTION TESTS ==================== { name: "Disassemble ARM: ARM32 multiple instructions", input: "00 48 2d e9 04 b0 8d e2 00 00 a0 e1 00 88 bd e8", expectedMatch: /push.*\n.*add.*\n.*mov.*\n.*pop/s, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 0, true, true], }, ], }, { name: "Disassemble ARM: ARM64 function prologue/epilogue", input: "fd 7b bf a9 fd 03 00 91 00 00 80 52 fd 7b c1 a8 c0 03 5f d6", expectedMatch: /stp.*\n.*mov.*\n.*mov.*\n.*ldp.*\n.*ret/s, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, // ==================== ADDRESS TESTS ==================== { name: "Disassemble ARM: ARM64 with start address 0x1000", input: "c0 03 5f d6", expectedMatch: /0x00001000/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 4096, true, true], }, ], }, { name: "Disassemble ARM: ARM32 with start address 0x8000", input: "00 00 a0 e1", expectedMatch: /0x00008000/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Little Endian", 32768, true, true], }, ], }, // ==================== ENDIANNESS TESTS ==================== { name: "Disassemble ARM: ARM32 Big Endian", input: "e1 a0 00 00", expectedMatch: /mov\s+r0,\s*r0/, recipeConfig: [ { op: "Disassemble ARM", args: ["ARM (32-bit)", "ARM", "Big Endian", 0, true, true], }, ], }, // ==================== EDGE CASES ==================== { name: "Disassemble ARM: Empty input", input: "", expectedOutput: "", recipeConfig: [ { op: "Disassemble ARM", args: ["ARM64 (AArch64)", "ARM", "Little Endian", 0, true, true], }, ], }, ]); ================================================ FILE: tests/operations/tests/DropNthBytes.mjs ================================================ /** * @author Oshawk [oshawk@protonmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; /** * Drop nth bytes tests */ TestRegister.addTests([ { name: "Drop nth bytes: Nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "Drop nth bytes", args: [4, 0, false], }, ], }, { name: "Drop nth bytes: Nothing (apply to each line)", input: "", expectedOutput: "", recipeConfig: [ { op: "Drop nth bytes", args: [4, 0, true], }, ], }, { name: "Drop nth bytes: Basic single line", input: "0123456789", expectedOutput: "1235679", recipeConfig: [ { op: "Drop nth bytes", args: [4, 0, false], }, ], }, { name: "Drop nth bytes: Basic single line (apply to each line)", input: "0123456789", expectedOutput: "1235679", recipeConfig: [ { op: "Drop nth bytes", args: [4, 0, true], }, ], }, { name: "Drop nth bytes: Complex single line", input: "0123456789", expectedOutput: "01234678", recipeConfig: [ { op: "Drop nth bytes", args: [4, 5, false], }, ], }, { name: "Drop nth bytes: Complex single line (apply to each line)", input: "0123456789", expectedOutput: "01234678", recipeConfig: [ { op: "Drop nth bytes", args: [4, 5, true], }, ], }, { name: "Drop nth bytes: Basic multi line", input: "01234\n56789", expectedOutput: "123\n5689", recipeConfig: [ { op: "Drop nth bytes", args: [4, 0, false], }, ], }, { name: "Drop nth bytes: Basic multi line (apply to each line)", input: "01234\n56789", expectedOutput: "123\n678", recipeConfig: [ { op: "Drop nth bytes", args: [4, 0, true], }, ], }, { name: "Drop nth bytes: Complex multi line", input: "01234\n56789", expectedOutput: "012345679", recipeConfig: [ { op: "Drop nth bytes", args: [4, 5, false], }, ], }, { name: "Drop nth bytes: Complex multi line (apply to each line)", input: "012345\n6789ab", expectedOutput: "01234\n6789a", recipeConfig: [ { op: "Drop nth bytes", args: [4, 5, true], }, ], } ]); ================================================ FILE: tests/operations/tests/ECDSA.mjs ================================================ /** * ECDSA tests. * * @author cplussharp * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; import {ALL_BYTES, ASCII_TEXT, UTF8_TEXT} from "../../samples/Ciphers.mjs"; const SOME_HEX_BYTES = "cdb23f958e018418621d9e489b7bba0f0c481f604eba2eb1ea35e38f99490cc0"; const SOME_BASE64_BYTES = "zbI/lY4BhBhiHZ5Im3u6DwxIH2BOui6x6jXjj5lJDMA="; const P256 = { // openssl ecparam -name prime256v1 -genkey -noout -out p256.priv.key privateKeyPkcs1: `-----BEGIN EC PRIVATE KEY----- MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49 AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== -----END EC PRIVATE KEY-----`, privateKeyPkcs8: `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC 6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p -----END PRIVATE KEY-----`, // openssl ec -in p256.priv.key -pubout -out p256.pub.key publicKey: `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== -----END PUBLIC KEY-----`, signature: { sha256: { asn1: "3046022100e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127022100b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d", p1363: "e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d", jws: "4GkFYIovp9vanihMKnlZ37aPtSel8AOy15df8TUUUSe2uqJTeTM0-Lk-od1iK8YAEk2AkLq9gH7-P3e4syQ4jQ", json: `{"r":"00e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127","s":"00b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d"}` } } }; // openssl pkcs8 -topk8 -in p256.priv.key -out p256.enc-priv.key -v2 des3 -v2prf hmacWithSHA1 -passout pass:Test1234 /* const PEM_PRIV_P256_ENCRYPTED_PASS = "Test1234"; const PEM_PRIV_P256_ENCRYPTED = `-----BEGIN ENCRYPTED PRIVATE KEY----- MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAg+4ckqI9Q9ZAICCAAw DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEOnMUW15Hn/ub0OcCCj9lksEgZCk kxaK4d430lZHovcA4ZeKTt94QcfjnIHRk65aZt93l17l52pv6n/srs3aRo/n5RV+ wZ5sTLF0925ZQWJB5cIhzc8KQIvguGCX1znLQJJaRHyYOUXIN77AKEfALKAinBit 25paDnbXAqGn1CR3UwFWUZZW+c3UEhWhmpghQpS1tIl0KI6IAvnrGIdw2kKIouo= -----END ENCRYPTED PRIVATE KEY-----`;*/ const P384 = { privateKeyPkcs8: `-----BEGIN PRIVATE KEY----- MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAYo22xn2kZjN8MInom NDsgD/zhpUwnCYch634jUgO59fN9m2lR5ekaI1XABHz39rihZANiAAQwXoCsPOLv Nn2STUs/hpL41CQveSL3WUmJ4QdtD7UFCl1mBO6ME0xSUgIQTUNkHt5k9CpOq3x9 r+LG5+GcisoLn7R54R+bRoGp/p1ZBeuBXoCgthvs+RFoT3OewUmA8oQ= -----END PRIVATE KEY-----`, publicKey: `-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMF6ArDzi7zZ9kk1LP4aS+NQkL3ki91lJ ieEHbQ+1BQpdZgTujBNMUlICEE1DZB7eZPQqTqt8fa/ixufhnIrKC5+0eeEfm0aB qf6dWQXrgV6AoLYb7PkRaE9znsFJgPKE -----END PUBLIC KEY-----` }; const P521 = { privateKeyPkcs8: `-----BEGIN PRIVATE KEY----- MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAifBaJDqNwOtKgThc FU34GzPQ73ubOQg9dnighpVGwA3b/KwCifimCNKDmKnXJaE04mEcxg8yzcFKausF 5I8o206hgYkDgYYABAGwpkwrBBlZOdx4u9mxqYxJvtzAHaFFAzl21WQVbAjyrqXe nFPMkhbFpEEWr1ualPYKQkHe14AX33iU3fQ9MlBkgAAripsPbiKggAaog74cUERo qbrUFZwMbptGgovpE6pU93h7A1wb3Vtw9DZQCgiNbwzMbdsft+p2RJ8iSxWEC6Gd mw== -----END PRIVATE KEY-----`, publicKey: `-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBsKZMKwQZWTnceLvZsamMSb7cwB2h RQM5dtVkFWwI8q6l3pxTzJIWxaRBFq9bmpT2CkJB3teAF994lN30PTJQZIAAK4qb D24ioIAGqIO+HFBEaKm61BWcDG6bRoKL6ROqVPd4ewNcG91bcPQ2UAoIjW8MzG3b H7fqdkSfIksVhAuhnZs= -----END PUBLIC KEY-----` }; const PEM_PPRIV_RSA512 = `-----BEGIN RSA PRIVATE KEY----- MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p 6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o= -----END RSA PRIVATE KEY-----`; const PEM_PUB_RSA512 = `-----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ== -----END PUBLIC KEY-----`; TestRegister.addTests([ { name: "ECDSA Sign/Verify: P-256 with MD5", input: ASCII_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "MD5", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "MD5", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Sign/Verify: P-256 with SHA1", input: ASCII_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "SHA-1", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-1", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Sign/Verify: P-256 with SHA256", input: ASCII_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-256", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Sign/Verify: P-256 with SHA384", input: ASCII_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "SHA-384", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-384", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Sign/Verify: P-256 with SHA512", input: ASCII_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "SHA-512", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-512", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Sign/Verify:: Using a private key in PKCS#8 format works", input: ASCII_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs8, "SHA-256", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-256", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Sign/Verify: P-384 with SHA384", input: ASCII_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P384.privateKeyPkcs8, "SHA-384", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-384", P384.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Sign/Verify: P-521 with SHA512", input: ASCII_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P521.privateKeyPkcs8, "SHA-512", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-512", P521.publicKey, ASCII_TEXT, "Raw"] } ] }, // ECDSA Sign { name: "ECDSA Sign: Using public key fails", input: ASCII_TEXT, expectedOutput: "Provided key is not a private key.", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.publicKey, "SHA-256", "ASN.1 HEX"] } ] }, { name: "ECDSA Sign: Using an RSA key fails", input: ASCII_TEXT, expectedOutput: "Provided key is not an EC key.", recipeConfig: [ { "op": "ECDSA Sign", "args": [PEM_PPRIV_RSA512, "SHA-256", "ASN.1 HEX"] } ] }, // ECDSA Verify { name: "ECDSA Verify: P-256 with SHA256 (ASN.1 signature)", input: P256.signature.sha256.asn1, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Verify", "args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Verify: P-256 with SHA256 (P1363 signature)", input: P256.signature.sha256.p1363, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Verify", "args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Verify: P-256 with SHA256 (JWS signature)", input: P256.signature.sha256.jws, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Verify", "args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Verify: P-256 with SHA256 (JSON signature)", input: P256.signature.sha256.json, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Verify", "args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Verify: JSON signature missing r", input: JSON.stringify({s: JSON.parse(P256.signature.sha256.json).s}), expectedOutput: 'No "r" value in the signature JSON', recipeConfig: [ { "op": "ECDSA Verify", "args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Verify: JSON signature missing s", input: JSON.stringify({r: JSON.parse(P256.signature.sha256.json).r}), expectedOutput: 'No "s" value in the signature JSON', recipeConfig: [ { "op": "ECDSA Verify", "args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Verify: Using private key fails", input: P256.signature.sha256.asn1, expectedOutput: "Provided key is not a public key.", recipeConfig: [ { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-256", P256.privateKeyPkcs1, ASCII_TEXT, "Raw"] } ] }, { name: "ECDSA Verify: Using an RSA key fails", input: P256.signature.sha256.asn1, expectedOutput: "Provided key is not an EC key.", recipeConfig: [ { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-256", PEM_PUB_RSA512, ASCII_TEXT, "Raw"] } ] }, // ECDSA Signatur Conversion { name: "ECDSA Signature Conversion: ASN.1 To ASN.1", input: P256.signature.sha256.asn1, expectedOutput: P256.signature.sha256.asn1, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "ASN.1 HEX"] } ] }, { name: "ECDSA Signature Conversion: ASN.1 To P1363", input: P256.signature.sha256.asn1, expectedOutput: P256.signature.sha256.p1363, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "P1363 HEX"] } ] }, { name: "ECDSA Signature Conversion: ASN.1 To JWS", input: P256.signature.sha256.asn1, expectedOutput: P256.signature.sha256.jws, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "JSON Web Signature"] } ] }, { name: "ECDSA Signature Conversion: ASN.1 To JSON", input: P256.signature.sha256.asn1, expectedOutput: P256.signature.sha256.json, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "Raw JSON"] } ] }, { name: "ECDSA Signature Conversion: P1363 To ASN.1", input: P256.signature.sha256.p1363, expectedOutput: P256.signature.sha256.asn1, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "ASN.1 HEX"] } ] }, { name: "ECDSA Signature Conversion: P1363 To P1363", input: P256.signature.sha256.p1363, expectedOutput: P256.signature.sha256.p1363, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "P1363 HEX"] } ] }, { name: "ECDSA Signature Conversion: P1363 To JWS", input: P256.signature.sha256.p1363, expectedOutput: P256.signature.sha256.jws, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "JSON Web Signature"] } ] }, { name: "ECDSA Signature Conversion: P1363 To JSON", input: P256.signature.sha256.p1363, expectedOutput: P256.signature.sha256.json, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "Raw JSON"] } ] }, { name: "ECDSA Signature Conversion: JSON To ASN.1", input: P256.signature.sha256.json, expectedOutput: P256.signature.sha256.asn1, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "ASN.1 HEX"] } ] }, { name: "ECDSA Signature Conversion: JSON To P1363", input: P256.signature.sha256.json, expectedOutput: P256.signature.sha256.p1363, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "P1363 HEX"] } ] }, { name: "ECDSA Signature Conversion: JSON To JWS", input: P256.signature.sha256.json, expectedOutput: P256.signature.sha256.jws, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "JSON Web Signature"] } ] }, { name: "ECDSA Signature Conversion: JSON To JSON", input: P256.signature.sha256.json, expectedOutput: P256.signature.sha256.json, recipeConfig: [ { "op": "ECDSA Signature Conversion", "args": ["Auto", "Raw JSON"] } ] }, { name: "ECDSA Sign/Verify: P-256 with SHA256 UTF8", input: UTF8_TEXT, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-256", P256.publicKey, UTF8_TEXT, "Raw"] } ] }, { name: "ECDSA Sign/Verify: P-256 with SHA256 bytes raw", input: ALL_BYTES, expectedOutput: "Verified OK", recipeConfig: [ { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-256", P256.publicKey, ALL_BYTES, "Raw"] } ] }, { name: "ECDSA Sign/Verify: P-256 with SHA256 bytes hex", input: SOME_HEX_BYTES, expectedOutput: "Verified OK", recipeConfig: [ { "op": "From Hex", "args": ["Auto"] }, { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-256", P256.publicKey, SOME_HEX_BYTES, "Hex"] } ] }, { name: "ECDSA Sign/Verify: P-256 with SHA256 bytes Base64", input: SOME_BASE64_BYTES, expectedOutput: "Verified OK", recipeConfig: [ { "op": "From Base64", "args": ["A-Za-z0-9+/=", true] }, { "op": "ECDSA Sign", "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1 HEX"] }, { "op": "ECDSA Verify", "args": ["ASN.1 HEX", "SHA-256", P256.publicKey, SOME_BASE64_BYTES, "Base64"] } ] } ]); ================================================ FILE: tests/operations/tests/ELFInfo.mjs ================================================ /** * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; import {ELF32_LE, ELF32_BE, ELF64_LE, ELF64_BE} from "../../samples/Executables.mjs"; const 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"; const 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"; const 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"; const 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"; TestRegister.addTests([ { name: "ELF Info invalid ELF.", input: "\x7f\x00\x00\x00", expectedOutput: "Invalid ELF", recipeConfig: [ { op: "ELF Info", args: [], }, ], }, { name: "ELF Info 32-bit ELF Little Endian.", input: ELF32_LE, expectedOutput: ELF32_LE_OUTPUT, recipeConfig: [ { op: "From Hex", args: ["None"], }, { op: "ELF Info", args: [], }, ], }, { name: "ELF Info 32-bit ELF Big Endian.", input: ELF32_BE, expectedOutput: ELF32_BE_OUTPUT, recipeConfig: [ { op: "From Hex", args: ["None"], }, { op: "ELF Info", args: [], }, ], }, { name: "ELF Info 64-bit ELF Little Endian.", input: ELF64_LE, expectedOutput: ELF64_LE_OUTPUT, recipeConfig: [ { op: "From Hex", args: ["None"], }, { op: "ELF Info", args: [], }, ], }, { name: "ELF Info 64-bit ELF Big Endian.", input: ELF64_BE, expectedOutput: ELF64_BE_OUTPUT, recipeConfig: [ { op: "From Hex", args: ["None"], }, { op: "ELF Info", args: [], }, ], }, ]); ================================================ FILE: tests/operations/tests/Enigma.mjs ================================================ /** * Enigma machine tests. * @author s2224834 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { // Simplest test: A single keypress in the default position on a basic // Enigma. name: "Enigma: basic wiring", input: "G", expectedOutput: "P", recipeConfig: [ { "op": "Enigma", "args": [ "3-rotor", "", "A", "A", // Note: start on Z because it steps when the key is pressed "EKMFLGDQVZNTOWYHXUSPAIBRCJmp3<\/td>.*MIME<\/td>audio\/mpeg<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.mp3", 524288] } ] }, { name: "Extract Audio Metadata: MP3 common tags (title, artist)", input: MP3_HEX, expectedMatch: /Title<\/td>Galway<\/td>.*Artist<\/td>Kevin MacLeod<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.mp3", 524288] } ] }, { name: "Extract Audio Metadata: MP3 ID3v2 frames (TIT2, TPE1, TSSE)", input: MP3_HEX, expectedMatch: /ID3v2 Frames.*TIT2.*Galway.*TPE1.*Kevin MacLeod.*TSSE.*Lavf56\.40\.101/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.mp3", 524288] } ] }, { name: "Extract Audio Metadata: MP3 detections (id3v2)", input: MP3_HEX, expectedMatch: /Metadata systems<\/td>id3v2<\/td>/, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.mp3", 524288] } ] }, // ---- WAV ---- { name: "Extract Audio Metadata: WAV container and MIME", input: WAV_HEX, expectedMatch: /Container<\/td>wav<\/td>.*MIME<\/td>audio\/wav<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.wav", 524288] } ] }, { name: "Extract Audio Metadata: WAV RIFF chunks (fmt)", input: WAV_HEX, expectedMatch: /RIFF Chunks.*fmt .*16 bytes @ offset 20/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.wav", 524288] } ] }, // ---- FLAC ---- { name: "Extract Audio Metadata: FLAC container and common tags", input: FLAC_HEX, expectedMatch: /Container<\/td>flac<\/td>.*Title<\/td>Galway<\/td>.*Artist<\/td>Kevin MacLeod<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.flac", 524288] } ] }, { name: "Extract Audio Metadata: FLAC metadata blocks (STREAMINFO, VORBIS_COMMENT)", input: FLAC_HEX, expectedMatch: /FLAC Metadata Blocks.*STREAMINFO<\/td>34 bytes<\/td>.*VORBIS_COMMENT<\/td>86 bytes<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.flac", 524288] } ] }, { name: "Extract Audio Metadata: FLAC Vorbis comments (vendor, tags)", input: FLAC_HEX, expectedMatch: /Vorbis Comments.*Vendor<\/td>Lavf56\.40\.101<\/td>.*TITLE<\/td>Galway<\/td>.*ARTIST<\/td>Kevin MacLeod<\/td>.*ENCODER<\/td>Lavf56\.40\.101<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.flac", 524288] } ] }, { name: "Extract Audio Metadata: FLAC detections", input: FLAC_HEX, expectedMatch: /Metadata systems<\/td>flac_metablocks, vorbis_comments<\/td>/, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.flac", 524288] } ] }, // ---- AAC ---- { name: "Extract Audio Metadata: AAC container and MIME", input: AAC_HEX, expectedMatch: /Container<\/td>aac<\/td>.*MIME<\/td>audio\/aac<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.aac", 524288] } ] }, { name: "Extract Audio Metadata: AAC ADTS technical fields", input: AAC_HEX, expectedMatch: /AAC ADTS.*mpeg_version<\/td>MPEG-4<\/td>.*profile<\/td>LC<\/td>.*sample_rate<\/td>44100<\/td>.*channel_description<\/td>stereo<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.aac", 524288] } ] }, // ---- AC3 ---- { name: "Extract Audio Metadata: AC3 container and MIME", input: AC3_HEX, expectedMatch: /Container<\/td>ac3<\/td>.*MIME<\/td>audio\/ac3<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.ac3", 524288] } ] }, { name: "Extract Audio Metadata: AC3 technical fields (sample rate, bitrate, channels)", input: AC3_HEX, expectedMatch: /AC3 \(Dolby Digital\).*sample_rate<\/td>44100<\/td>.*bitrate_kbps<\/td>192<\/td>.*channel_layout<\/td>2\.0 \(L R\)<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.ac3", 524288] } ] }, // ---- OGG Vorbis ---- { name: "Extract Audio Metadata: OGG container and common tags", input: OGG_HEX, expectedMatch: /Container<\/td>ogg<\/td>.*Title<\/td>Galway<\/td>.*Artist<\/td>Kevin MacLeod<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.ogg", 524288] } ] }, { name: "Extract Audio Metadata: OGG Vorbis comments (vendor, encoder)", input: OGG_HEX, expectedMatch: /Vorbis Comments.*Vendor<\/td>Lavf56\.40\.101<\/td>.*ENCODER<\/td>Lavc56\.60\.100 libvorbis<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.ogg", 524288] } ] }, // ---- Opus ---- { name: "Extract Audio Metadata: Opus container and common tags", input: OPUS_HEX, expectedMatch: /Container<\/td>opus<\/td>.*Title<\/td>Galway<\/td>.*Artist<\/td>Kevin MacLeod<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.opus", 524288] } ] }, { name: "Extract Audio Metadata: Opus Vorbis comments (vendor, encoder)", input: OPUS_HEX, expectedMatch: /Vorbis Comments.*Vendor<\/td>Lavf58\.19\.102<\/td>.*ENCODER<\/td>Lavc58\.34\.100 libopus<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.opus", 524288] } ] }, // ---- WMA/ASF ---- { name: "Extract Audio Metadata: WMA container and MIME", input: WMA_HEX, expectedMatch: /Container<\/td>wma<\/td>.*MIME<\/td>audio\/x-ms-wma<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.wma", 524288] } ] }, { name: "Extract Audio Metadata: WMA common tags (title, artist)", input: WMA_HEX, expectedMatch: /Title<\/td>Galway<\/td>.*Artist<\/td>Kevin MacLeod<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.wma", 524288] } ] }, { name: "Extract Audio Metadata: WMA ASF Content Description", input: WMA_HEX, expectedMatch: /ASF Content Description.*title<\/td>Galway<\/td>.*author<\/td>Kevin MacLeod<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.wma", 524288] } ] }, { name: "Extract Audio Metadata: WMA ASF Extended Content (encoding settings)", input: WMA_HEX, expectedMatch: /ASF Extended Content.*WM\/EncodingSettings<\/td>Lavf56\.40\.101<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.wma", 524288] } ] }, { name: "Extract Audio Metadata: WMA detections", input: WMA_HEX, expectedMatch: /Metadata systems<\/td>asf_header, asf_content_desc, asf_ext_content_desc<\/td>/, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.wma", 524288] } ] }, // ---- M4A ---- { name: "Extract Audio Metadata: M4A container, MIME and brand", input: M4A_HEX, expectedMatch: /Container<\/td>m4a<\/td>.*MIME<\/td>audio\/mp4<\/td>.*Brand<\/td>M4A <\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.m4a", 524288] } ] }, { name: "Extract Audio Metadata: M4A top-level atoms (ftyp, mdat)", input: M4A_HEX, expectedMatch: /MP4 Top-Level Atoms.*ftyp<\/td>.*mdat<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.m4a", 524288] } ] }, // ---- AIFF ---- { name: "Extract Audio Metadata: AIFF container, MIME and brand", input: AIFF_HEX, expectedMatch: /Container<\/td>aiff<\/td>.*MIME<\/td>audio\/aiff<\/td>.*Brand<\/td>AIFF<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.aiff", 524288] } ] }, { name: "Extract Audio Metadata: AIFF common tag (title from NAME chunk)", input: AIFF_HEX, expectedMatch: /Title<\/td>Galway<\/td>/, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.aiff", 524288] } ] }, { name: "Extract Audio Metadata: AIFF chunks (NAME)", input: AIFF_HEX, expectedMatch: /AIFF Chunks.*NAME<\/td>Galway<\/td>/s, recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract Audio Metadata", args: ["test.aiff", 524288] } ] }, ]); ================================================ FILE: tests/operations/tests/ExtractEmailAddresses.mjs ================================================ /** * extract email address tests. * * @author Klaxon [klaxon@veyr.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Extract email address", 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", 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", recipeConfig: [ { "op": "Extract email addresses", "args": [false] }, ], }, { name: "Extract email address - Display total", 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", 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", recipeConfig: [ { "op": "Extract email addresses", "args": [true] }, ], }, { name: "Extract email address (Internationalized)", 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", 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", recipeConfig: [ { "op": "Extract email addresses", "args": [false] }, ], }, { name: "Extract email address - Display total (Internationalized)", 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", 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", recipeConfig: [ { "op": "Extract email addresses", "args": [true] }, ], }, ]); ================================================ FILE: tests/operations/tests/ExtractHashes.mjs ================================================ /** * ExtractHashes tests. * * @author mshwed [m@ttshwed.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Extract MD5 hash", input: "The quick brown fox jumps over the lazy dog\n\nMD5: 9e107d9d372bb6826bd81d3542a419d6", expectedOutput: "9e107d9d372bb6826bd81d3542a419d6", recipeConfig: [ { "op": "Extract hashes", "args": [32, false, false] }, ], }, { name: "Extract SHA1 hash", input: "The quick brown fox jumps over the lazy dog\n\nSHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", expectedOutput: "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", recipeConfig: [ { "op": "Extract hashes", "args": [40, false, false] }, ], }, { name: "Extract SHA256 hash", input: "The quick brown fox jumps over the lazy dog\n\nSHA256: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", expectedOutput: "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", recipeConfig: [ { "op": "Extract hashes", "args": [64, false, false] }, ], }, { name: "Extract SHA512 hash", input: "The quick brown fox jumps over the lazy dog\n\nSHA512: 07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6", expectedOutput: "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6", recipeConfig: [ { "op": "Extract hashes", "args": [128, false, false] }, ], }, { name: "Extract all hashes", input: "The quick brown fox jumps over the lazy dog\n\nMD5: 9e107d9d372bb6826bd81d3542a419d6\nSHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\nSHA256: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", expectedOutput: "9e107d9d372bb6826bd81d3542a419d6\n2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\nd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", recipeConfig: [ { "op": "Extract hashes", "args": [0, true, false] }, ], }, { name: "Extract hashes with total count", input: "The quick brown fox jumps over the lazy dog\n\nMD5: 9e107d9d372bb6826bd81d3542a419d6\nSHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\nSHA256: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", expectedOutput: "Total Results: 3\n\n9e107d9d372bb6826bd81d3542a419d6\n2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\nd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", recipeConfig: [ { "op": "Extract hashes", "args": [0, true, true] }, ], } ]); ================================================ FILE: tests/operations/tests/ExtractIPAddresses.mjs ================================================ /** * ExtractIPAddresses tests. * * @author gchqdev365 [gchqdev365@outlook.com] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "ExtractIPAddress All Zeros", input: "0.0.0.0", expectedOutput: "0.0.0.0", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress All 10s", input: "10.10.10.10", expectedOutput: "10.10.10.10", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress All 10s", input: "100.100.100.100", expectedOutput: "100.100.100.100", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress 255s", input: "255.255.255.255", expectedOutput: "255.255.255.255", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress double digits", input: "10.10.10.10 25.25.25.25 99.99.99.99", expectedOutput: "10.10.10.10\n25.25.25.25\n99.99.99.99", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress 256 in middle", input: "255.256.255.255 255.255.256.255", expectedOutput: "", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress 256 at each end", input: "256.255.255.255 255.255.255.256", expectedOutput: "", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress silly example", input: "710.65.0.456", expectedOutput: "", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress longer dotted decimal", input: "1.2.3.4.5.6.7.8", expectedOutput: "1.2.3.4\n5.6.7.8", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress octal valid", input: "01.01.01.01 0123.0123.0123.0123 0377.0377.0377.0377", expectedOutput: "01.01.01.01\n0123.0123.0123.0123\n0377.0377.0377.0377", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, { name: "ExtractIPAddress octal invalid", input: "0378.01.01.01 03.0377.2.3", expectedOutput: "", recipeConfig: [ { "op": "Extract IP addresses", "args": [true, true, false, false, false, false] }, ], }, ]); ================================================ FILE: tests/operations/tests/Fernet.mjs ================================================ /** * Fernet tests. * * @author Karsten Silkenbäumer [github.com/kassi] * @copyright Karsten Silkenbäumer 2019 * @license Apache-2.0 */ import TestRegister from "../TestRegister"; TestRegister.addTests([ { name: "Fernet Decrypt: no input", input: "", expectedOutput: "Error: Invalid version", recipeConfig: [ { op: "Fernet Decrypt", args: ["MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="] } ], }, { name: "Fernet Decrypt: no secret", input: "gAAAAABce-Tycae8klRxhDX2uenJ-uwV8-A1XZ2HRnfOXlNzkKKfRxviNLlgtemhT_fd1Fw5P_zFUAjd69zaJBQyWppAxVV00SExe77ql8c5n62HYJOnoIU=", expectedOutput: "Error: Secret must be 32 url-safe base64-encoded bytes.", recipeConfig: [ { op: "Fernet Decrypt", args: [""] } ], }, { name: "Fernet Decrypt: valid arguments", input: "gAAAAABce-Tycae8klRxhDX2uenJ-uwV8-A1XZ2HRnfOXlNzkKKfRxviNLlgtemhT_fd1Fw5P_zFUAjd69zaJBQyWppAxVV00SExe77ql8c5n62HYJOnoIU=", expectedOutput: "This is a secret message.\n", recipeConfig: [ { op: "Fernet Decrypt", args: ["VGhpc0lzVGhpcnR5VHdvQ2hhcmFjdGVyc0xvbmdLZXk="] } ], } ]); TestRegister.addTests([ { name: "Fernet Encrypt: no input", input: "", expectedMatch: /^gAAAAABce-[\w-]+={0,2}$/, recipeConfig: [ { op: "Fernet Encrypt", args: ["MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="] } ], }, { name: "Fernet Encrypt: no secret", input: "This is a secret message.\n", expectedOutput: "Error: Secret must be 32 url-safe base64-encoded bytes.", recipeConfig: [ { op: "Fernet Encrypt", args: [""] } ], }, { name: "Fernet Encrypt: valid arguments", input: "This is a secret message.\n", expectedMatch: /^gAAAAABce-[\w-]+={0,2}$/, recipeConfig: [ { op: "Fernet Encrypt", args: ["MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="] } ], } ]); ================================================ FILE: tests/operations/tests/FileTree.mjs ================================================ /** * File tree tests. * * @author sw5678 * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "File Tree: basic example", "input": "/test_dir1/test_file1.txt\n/test_dir1/test_file2.txt\n/test_dir2/test_file1.txt", "expectedOutput": "test_dir1\n|---test_file1.txt\n|---test_file2.txt\ntest_dir2\n|---test_file1.txt", "recipeConfig": [ { "op": "File Tree", "args": ["/", "Line feed"], }, ], } ]); ================================================ FILE: tests/operations/tests/FlaskSession.mjs ================================================ /** * Flask Session tests * * @author ThePlayer372-FR [] * * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const validTokenSha1 = "eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aZ-KEw.E_x6bOhA4GU9t72pMinJUjN-O3I"; const validTokenSha256 = "eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aab3Ew.Jsx2DOx_H9anZg0YcvhsASxQ11897EFHeQfS2oja4y8"; const validKey = "mysecretkey"; const wrongKey = "notTheKey"; const outputObject = { user: "admin", role: "superuser", }; const outputVerify = { valid: true, payload: outputObject, }; TestRegister.addTests([ { name: "Flask Session: Decode", input: validTokenSha1, expectedOutput: outputObject, recipeConfig: [ { op: "Flask Session Decode", args: [ false ], } ] }, { name: "Flask Session: Verify Sha1", input: validTokenSha1, expectedOutput: outputVerify, recipeConfig: [ { op: "Flask Session Verify", args: [ { string: validKey, option: "UTF8" }, { string: "cookie-session", option: "UTF8" }, "sha1", false, ], } ] }, { name: "Flask Session: Verify Sha256", input: validTokenSha256, expectedOutput: outputVerify, recipeConfig: [ { op: "Flask Session Verify", args: [ { string: validKey, option: "UTF8" }, { string: "cookie-session", option: "UTF8" }, "sha256", false, ], } ] }, { name: "Flask Session: Sign Sha1", input: outputObject, expectedOutput: outputVerify, recipeConfig: [ { op: "Flask Session Sign", args: [ { string: validKey, option: "UTF8" }, { string: "cookie-session", option: "UTF8" }, "sha1" ] }, { op: "Flask Session Verify", args: [ { string: validKey, option: "UTF8" }, { string: "cookie-session", option: "UTF8" }, "sha1", false, ], } ] }, { name: "Flask Session: Sign Sha256", input: outputObject, expectedOutput: outputVerify, recipeConfig: [ { op: "Flask Session Sign", args: [ { string: validKey, option: "UTF8" }, { string: "cookie-session", option: "UTF8" }, "sha256" ] }, { op: "Flask Session Verify", args: [ { string: validKey, option: "UTF8" }, { string: "cookie-session", option: "UTF8" }, "sha256", false, ], } ] }, { name: "Flask Session: Verify Sha1 Wrong Key", input: validTokenSha1, expectedOutput: "Invalid signature!", recipeConfig: [ { op: "Flask Session Verify", args: [ { string: wrongKey, option: "UTF8" }, { string: "cookie-session", option: "UTF8" }, "sha1", false, ], } ] }, { name: "Flask Session: Verify Sha256 Wrong Key", input: validTokenSha256, expectedOutput: "Invalid signature!", recipeConfig: [ { op: "Flask Session Verify", args: [ { string: wrongKey, option: "UTF8" }, { string: "cookie-session", option: "UTF8" }, "sha256", false, ], } ] }, { name: "Flask Session: Verify Sha1 Wrong Salt", input: validTokenSha1, expectedOutput: "Invalid signature!", recipeConfig: [ { op: "Flask Session Verify", args: [ { string: validKey, option: "UTF8" }, { string: "notTheSalt", option: "UTF8" }, "sha1", false, ], } ] }, { name: "Flask Session: Verify Sha256 Wrong Salt", input: validTokenSha256, expectedOutput: "Invalid signature!", recipeConfig: [ { op: "Flask Session Verify", args: [ { string: validKey, option: "UTF8" }, { string: "notTheSalt", option: "UTF8" }, "sha256", false, ], } ] }, ]); ================================================ FILE: tests/operations/tests/FletcherChecksum.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Fletcher-16 Checksum: abcde", input: "abcde", expectedOutput: "c8f0", recipeConfig: [ { op: "Fletcher-16 Checksum", args: [], }, ], }, { name: "Fletcher-16 Checksum: abcdef", input: "abcdef", expectedOutput: "2057", recipeConfig: [ { op: "Fletcher-16 Checksum", args: [], }, ], }, { name: "Fletcher-16 Checksum: abcdefgh", input: "abcdefgh", expectedOutput: "0627", recipeConfig: [ { op: "Fletcher-16 Checksum", args: [], }, ], }, { name: "Fletcher-32 Checksum: abcde", input: "abcde", expectedOutput: "f04fc729", recipeConfig: [ { op: "Fletcher-32 Checksum", args: [], }, ], }, { name: "Fletcher-32 Checksum: abcdef", input: "abcdef", expectedOutput: "56502d2a", recipeConfig: [ { op: "Fletcher-32 Checksum", args: [], }, ], }, { name: "Fletcher-32 Checksum: abcdefgh", input: "abcdefgh", expectedOutput: "ebe19591", recipeConfig: [ { op: "Fletcher-32 Checksum", args: [], }, ], }, { name: "Fletcher-64 Checksum: abcde", input: "abcde", expectedOutput: "c8c6c527646362c6", recipeConfig: [ { op: "Fletcher-64 Checksum", args: [], }, ], }, { name: "Fletcher-64 Checksum: abcdef", input: "abcdef", expectedOutput: "c8c72b276463c8c6", recipeConfig: [ { op: "Fletcher-64 Checksum", args: [], }, ], }, { name: "Fletcher-64 Checksum: abcdefgh", input: "abcdefgh", expectedOutput: "312e2b28cccac8c6", recipeConfig: [ { op: "Fletcher-64 Checksum", args: [], }, ], }, ]); ================================================ FILE: tests/operations/tests/Float.mjs ================================================ /** * Float tests. * * @author tcode2k16 [tcode2k16@gmail.com] * * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "To Float: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["Auto"] }, { op: "To Float", args: ["Big Endian", "Float (4 bytes)", "Space"] } ], }, { name: "To Float (Big Endian, 4 bytes): 0.5", input: "3f0000003f000000", expectedOutput: "0.5 0.5", recipeConfig: [ { op: "From Hex", args: ["Auto"] }, { op: "To Float", args: ["Big Endian", "Float (4 bytes)", "Space"] } ] }, { name: "To Float (Little Endian, 4 bytes): 0.5", input: "0000003f0000003f", expectedOutput: "0.5 0.5", recipeConfig: [ { op: "From Hex", args: ["Auto"] }, { op: "To Float", args: ["Little Endian", "Float (4 bytes)", "Space"] } ] }, { name: "To Float (Big Endian, 8 bytes): 0.5", input: "3fe00000000000003fe0000000000000", expectedOutput: "0.5 0.5", recipeConfig: [ { op: "From Hex", args: ["Auto"] }, { op: "To Float", args: ["Big Endian", "Double (8 bytes)", "Space"] } ] }, { name: "To Float (Little Endian, 8 bytes): 0.5", input: "000000000000e03f000000000000e03f", expectedOutput: "0.5 0.5", recipeConfig: [ { op: "From Hex", args: ["Auto"] }, { op: "To Float", args: ["Little Endian", "Double (8 bytes)", "Space"] } ] }, { name: "From Float: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Float", args: ["Big Endian", "Float (4 bytes)", "Space"] }, { op: "To Hex", args: ["None"] } ] }, { name: "From Float (Big Endian, 4 bytes): 0.5", input: "0.5 0.5", expectedOutput: "3f0000003f000000", recipeConfig: [ { op: "From Float", args: ["Big Endian", "Float (4 bytes)", "Space"] }, { op: "To Hex", args: ["None"] } ] }, { name: "From Float (Little Endian, 4 bytes): 0.5", input: "0.5 0.5", expectedOutput: "0000003f0000003f", recipeConfig: [ { op: "From Float", args: ["Little Endian", "Float (4 bytes)", "Space"] }, { op: "To Hex", args: ["None"] } ] }, { name: "From Float (Big Endian, 8 bytes): 0.5", input: "0.5 0.5", expectedOutput: "3fe00000000000003fe0000000000000", recipeConfig: [ { op: "From Float", args: ["Big Endian", "Double (8 bytes)", "Space"] }, { op: "To Hex", args: ["None"] } ] }, { name: "From Float (Little Endian, 8 bytes): 0.5", input: "0.5 0.5", expectedOutput: "000000000000e03f000000000000e03f", recipeConfig: [ { op: "From Float", args: ["Little Endian", "Double (8 bytes)", "Space"] }, { op: "To Hex", args: ["None"] } ] } ]); ================================================ FILE: tests/operations/tests/Fork.mjs ================================================ /** * Fork tests * * @author tlwr [toby@toby.codes] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Fork: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "Fork", args: ["\n", "\n", false], }, ], }, { name: "Fork, Merge: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "Fork", args: ["\n", "\n", false], }, { op: "Merge", args: [true], }, ], }, { name: "Fork, (expect) Error, Merge", input: "1,2,3,4\n\n3,4,5,6", expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?", recipeConfig: [ { op: "Fork", args: ["\n\n", "\n\n", false], }, { op: "Set Union", args: ["\n\n", ","], }, { op: "Merge", args: [true], }, ], }, { name: "Fork, Conditional Jump, Encodings", input: "Some data with a 1 in it\nSome data with a 2 in it", 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", recipeConfig: [ {"op": "Fork", "args": ["\\n", "\\n", false]}, {"op": "Conditional Jump", "args": ["1", false, "skipReturn", "10"]}, {"op": "To Hex", "args": ["Space"]}, {"op": "Return", "args": []}, {"op": "Label", "args": ["skipReturn"]}, {"op": "To Base64", "args": ["A-Za-z0-9+/="]} ] }, { name: "Fork, Partial Merge", input: "Hello World", expectedOutput: "48656c6c6f 576f726c64", recipeConfig: [ { "op": "Fork", "args": [" ", " ", false] }, { "op": "Fork", "args": ["l", "l", false] }, { "op": "Merge", "args": [false] }, { "op": "To Hex", "args": ["None", 0] }, ] }, ]); ================================================ FILE: tests/operations/tests/FromDecimal.mjs ================================================ /** * From Decimal tests * * @author qistoph * @copyright Crown Copyright 2018 * @licence Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "From Decimal", input: "83 97 109 112 108 101 32 84 101 120 116", expectedOutput: "Sample Text", recipeConfig: [ { op: "From Decimal", args: ["Space", false] }, ], }, { name: "From Decimal with negatives", input: "-130,-140,-152,-151,115,33,0,-1", expectedOutput: "~this!\u0000\u00ff", recipeConfig: [ { op: "From Decimal", args: ["Comma", true] }, ], }, ]); ================================================ FILE: tests/operations/tests/GOST.mjs ================================================ /** * GOST tests. * * The GOST library already includes a range of tests for the correctness of * the algorithms. These tests are intended only to confirm that the library * has been correctly integrated into CyberChef. * * @author n1474335 [n1474335@gmail.com] * * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "GOST Encrypt: 1989", input: "Hello, World!", expectedOutput: "f124ac5c0853870906dbaf9b56", recipeConfig: [ { op: "GOST Encrypt", args: [ { "option": "Hex", "string": "00112233" }, { "option": "Hex", "string": "0011223344556677" }, "Raw", "Hex", "GOST 28147 (1989)", "E-SC", "OFB", "CP", "ZERO" ] } ], }, { name: "GOST Encrypt: Kuznyechik", input: "Hello, World!", expectedOutput: "8673d490dfa4a66d5e3ff00ba316724f", recipeConfig: [ { op: "GOST Encrypt", args: [ { "option": "Hex", "string": "00112233" }, { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, "Raw", "Hex", "GOST R 34.12 (Kuznyechik, 2015)", "E-SC", "CBC", "CP", "PKCS5" ] } ], }, { name: "GOST Decrypt: 1989", input: "f124ac5c0853870906dbaf9b56", expectedOutput: "Hello, World!", recipeConfig: [ { op: "GOST Decrypt", args: [ { "option": "Hex", "string": "00112233" }, { "option": "Hex", "string": "0011223344556677" }, "Hex", "Raw", "GOST 28147 (1989)", "E-SC", "OFB", "CP", "ZERO" ] } ], }, { name: "GOST Decrypt: Kuznyechik", input: "8673d490dfa4a66d5e3ff00ba316724f", expectedOutput: "Hello, World!\0\0\0", recipeConfig: [ { op: "GOST Decrypt", args: [ { "option": "Hex", "string": "00112233" }, { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, "Hex", "Raw", "GOST R 34.12 (Kuznyechik, 2015)", "E-TEST", "CBC", "CP", "PKCS5" ] } ], }, { name: "GOST Sign", input: "Hello, World!", expectedOutput: "810d0c40e965", recipeConfig: [ { op: "GOST Sign", args: [ { "option": "Hex", "string": "00112233" }, { "option": "Hex", "string": "0011223344556677" }, "Raw", "Hex", "GOST 28147 (1989)", "E-C", 48 ] } ], }, { name: "GOST Verify", input: "Hello, World!", expectedOutput: "The signature matches", recipeConfig: [ { op: "GOST Verify", args: [ { "option": "Hex", "string": "00112233" }, { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, { "option": "Hex", "string": "42b77fb3d6f6bf04" }, "Raw", "GOST R 34.12 (Kuznyechik, 2015)", "E-TEST" ] } ], }, { name: "GOST Key Wrap", input: "Hello, World!123", expectedOutput: "0bb706e92487fceef97589911faeb28200000000000000000000000000000000\r\n6b7bfd16", recipeConfig: [ { op: "GOST Key Wrap", args: [ { "option": "Hex", "string": "00112233" }, { "option": "Hex", "string": "0011223344556677" }, "Raw", "Hex", "GOST R 34.12 (Magma, 2015)", "E-TEST", "CP" ] } ], }, { name: "GOST Key Unwrap", input: "c8e58458a42d21974d50103d59b469f2c8e58458a42d21974d50103d59b469f2\r\na32a1575", expectedOutput: "0123456789abcdef0123456789abcdef", recipeConfig: [ { op: "GOST Key Unwrap", args: [ { "option": "Hex", "string": "" }, { "option": "Latin1", "string": "00112233" }, "Hex", "Raw", "GOST 28147 (1989)", "E-Z", "CP" ] } ], }, ]); ================================================ FILE: tests/operations/tests/GenerateAllChecksums.mjs ================================================ /** * GenerateAllChecksums tests. * * @author r4mos [2k95ljkhg@mozmail.com] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const CHECK_STRING = "123456789"; TestRegister.addTests([ { name: "Full generate all checksums with name", input: CHECK_STRING, expectedOutput: `CRC-3/GSM: 4 CRC-3/ROHC: 6 CRC-4/G-704: 7 CRC-4/INTERLAKEN: b CRC-4/ITU: 7 CRC-5/EPC: 00 CRC-5/EPC-C1G2: 00 CRC-5/G-704: 07 CRC-5/ITU: 07 CRC-5/USB: 19 CRC-6/CDMA2000-A: 0d CRC-6/CDMA2000-B: 3b CRC-6/DARC: 26 CRC-6/G-704: 06 CRC-6/GSM: 13 CRC-6/ITU: 06 CRC-7/MMC: 75 CRC-7/ROHC: 53 CRC-7/UMTS: 61 CRC-8: f4 CRC-8/8H2F: df CRC-8/AES: 97 CRC-8/AUTOSAR: df CRC-8/BLUETOOTH: 26 CRC-8/CDMA2000: da CRC-8/DARC: 15 CRC-8/DVB-S2: bc CRC-8/EBU: 97 CRC-8/GSM-A: 37 CRC-8/GSM-B: 94 CRC-8/HITAG: b4 CRC-8/I-432-1: a1 CRC-8/I-CODE: 7e CRC-8/ITU: a1 CRC-8/LTE: ea CRC-8/MAXIM: a1 CRC-8/MAXIM-DOW: a1 CRC-8/MIFARE-MAD: 99 CRC-8/NRSC-5: f7 CRC-8/OPENSAFETY: 3e CRC-8/ROHC: d0 CRC-8/SAE-J1850: 4b CRC-8/SAE-J1850-ZERO: 37 CRC-8/SMBUS: f4 CRC-8/TECH-3250: 97 CRC-8/WCDMA: 25 Fletcher-8: 0c CRC-10/ATM: 199 CRC-10/CDMA2000: 233 CRC-10/GSM: 12a CRC-10/I-610: 199 CRC-11/FLEXRAY: 5a3 CRC-11/UMTS: 061 CRC-12/3GPP: daf CRC-12/CDMA2000: d4d CRC-12/DECT: f5b CRC-12/GSM: b34 CRC-12/UMTS: daf CRC-13/BBC: 04fa CRC-14/DARC: 082d CRC-14/GSM: 30ae CRC-15/CAN: 059e CRC-15/MPT1327: 2566 CRC-16: bb3d CRC-16/A: bf05 CRC-16/ACORN: 31c3 CRC-16/ARC: bb3d CRC-16/AUG-CCITT: e5cc CRC-16/AUTOSAR: 29b1 CRC-16/B: 906e CRC-16/BLUETOOTH: 2189 CRC-16/BUYPASS: fee8 CRC-16/CCITT: 2189 CRC-16/CCITT-FALSE: 29b1 CRC-16/CCITT-TRUE: 2189 CRC-16/CCITT-ZERO: 31c3 CRC-16/CDMA2000: 4c06 CRC-16/CMS: aee7 CRC-16/DARC: d64e CRC-16/DDS-110: 9ecf CRC-16/DECT-R: 007e CRC-16/DECT-X: 007f CRC-16/DNP: ea82 CRC-16/EN-13757: c2b7 CRC-16/EPC: d64e CRC-16/EPC-C1G2: d64e CRC-16/GENIBUS: d64e CRC-16/GSM: ce3c CRC-16/I-CODE: d64e CRC-16/IBM: bb3d CRC-16/IBM-3740: 29b1 CRC-16/IBM-SDLC: 906e CRC-16/IEC-61158-2: a819 CRC-16/ISO-HDLC: 906e CRC-16/ISO-IEC-14443-3-A: bf05 CRC-16/ISO-IEC-14443-3-B: 906e CRC-16/KERMIT: 2189 CRC-16/LHA: bb3d CRC-16/LJ1200: bdf4 CRC-16/LTE: 31c3 CRC-16/M17: 772b CRC-16/MAXIM: 44c2 CRC-16/MAXIM-DOW: 44c2 CRC-16/MCRF4XX: 6f91 CRC-16/MODBUS: 4b37 CRC-16/NRSC-5: a066 CRC-16/OPENSAFETY-A: 5d38 CRC-16/OPENSAFETY-B: 20fe CRC-16/PROFIBUS: a819 CRC-16/RIELLO: 63d0 CRC-16/SPI-FUJITSU: e5cc CRC-16/T10-DIF: d0db CRC-16/TELEDISK: 0fb3 CRC-16/TMS37157: 26b1 CRC-16/UMTS: fee8 CRC-16/USB: b4c8 CRC-16/V-41-LSB: 2189 CRC-16/V-41-MSB: 31c3 CRC-16/VERIFONE: fee8 CRC-16/X-25: 906e CRC-16/XMODEM: 31c3 CRC-16/ZMODEM: 31c3 Fletcher-16: 1ede CRC-17/CAN-FD: 04f03 CRC-21/CAN-FD: 0ed841 CRC-24/BLE: c25a56 CRC-24/FLEXRAY-A: 7979bd CRC-24/FLEXRAY-B: 1f23b8 CRC-24/INTERLAKEN: b4f3e6 CRC-24/LTE-A: cde703 CRC-24/LTE-B: 23ef52 CRC-24/OPENPGP: 21cf02 CRC-24/OS-9: 200fa5 CRC-30/CDMA: 04c34abf CRC-31/PHILIPS: 0ce9e46c Adler-32: 091e01de CRC-32: cbf43926 CRC-32/AAL5: fc891918 CRC-32/ADCCP: cbf43926 CRC-32/AIXM: 3010bf7f CRC-32/AUTOSAR: 1697d06a CRC-32/BASE91-C: e3069283 CRC-32/BASE91-D: 87315576 CRC-32/BZIP2: fc891918 CRC-32/C: e3069283 CRC-32/CASTAGNOLI: e3069283 CRC-32/CD-ROM-EDC: 6ec2edc4 CRC-32/CKSUM: 765e7680 CRC-32/D: 87315576 CRC-32/DECT-B: fc891918 CRC-32/INTERLAKEN: e3069283 CRC-32/ISCSI: e3069283 CRC-32/ISO-HDLC: cbf43926 CRC-32/JAMCRC: 340bc6d9 CRC-32/MEF: d2c22f51 CRC-32/MPEG-2: 0376e6e7 CRC-32/NVME: e3069283 CRC-32/PKZIP: cbf43926 CRC-32/POSIX: 765e7680 CRC-32/Q: 3010bf7f CRC-32/SATA: cf72afe8 CRC-32/V-42: cbf43926 CRC-32/XFER: bd0be338 CRC-32/XZ: cbf43926 Fletcher-32: df09d509 CRC-40/GSM: d4164fc646 CRC-64/ECMA-182: 6c40df5f0b497347 CRC-64/GO-ECMA: 995dc9bbdf1939fa CRC-64/GO-ISO: b90956c775a41001 CRC-64/MS: 75d4b74f024eceea CRC-64/NVME: ae8b14860a799888 CRC-64/REDIS: e9c6d914c4b8d9ca CRC-64/WE: 62ec59e3f1a4f00a CRC-64/XZ: 995dc9bbdf1939fa Fletcher-64: 0d0803376c6a689f CRC-82/DARC: 09ea83f625023801fd612 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["All", true] } ] }, { name: "Full generate all checksums without name", input: CHECK_STRING, expectedOutput: `4 6 7 b 7 00 00 07 07 19 0d 3b 26 06 13 06 75 53 61 f4 df 97 df 26 da 15 bc 97 37 94 b4 a1 7e a1 ea a1 a1 99 f7 3e d0 4b 37 f4 97 25 0c 199 233 12a 199 5a3 061 daf d4d f5b b34 daf 04fa 082d 30ae 059e 2566 bb3d bf05 31c3 bb3d e5cc 29b1 906e 2189 fee8 2189 29b1 2189 31c3 4c06 aee7 d64e 9ecf 007e 007f ea82 c2b7 d64e d64e d64e ce3c d64e bb3d 29b1 906e a819 906e bf05 906e 2189 bb3d bdf4 31c3 772b 44c2 44c2 6f91 4b37 a066 5d38 20fe a819 63d0 e5cc d0db 0fb3 26b1 fee8 b4c8 2189 31c3 fee8 906e 31c3 31c3 1ede 04f03 0ed841 c25a56 7979bd 1f23b8 b4f3e6 cde703 23ef52 21cf02 200fa5 04c34abf 0ce9e46c 091e01de cbf43926 fc891918 cbf43926 3010bf7f 1697d06a e3069283 87315576 fc891918 e3069283 e3069283 6ec2edc4 765e7680 87315576 fc891918 e3069283 e3069283 cbf43926 340bc6d9 d2c22f51 0376e6e7 e3069283 cbf43926 765e7680 3010bf7f cf72afe8 cbf43926 bd0be338 cbf43926 df09d509 d4164fc646 6c40df5f0b497347 995dc9bbdf1939fa b90956c775a41001 75d4b74f024eceea ae8b14860a799888 e9c6d914c4b8d9ca 62ec59e3f1a4f00a 995dc9bbdf1939fa 0d0803376c6a689f 09ea83f625023801fd612 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["All", false] } ] }, { name: "Full generate 3 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-3/GSM: 4 CRC-3/ROHC: 6 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["3", true] } ] }, { name: "Full generate 4 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-4/G-704: 7 CRC-4/INTERLAKEN: b CRC-4/ITU: 7 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["4", true] } ] }, { name: "Full generate 5 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-5/EPC: 00 CRC-5/EPC-C1G2: 00 CRC-5/G-704: 07 CRC-5/ITU: 07 CRC-5/USB: 19 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["5", true] } ] }, { name: "Full generate 6 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-6/CDMA2000-A: 0d CRC-6/CDMA2000-B: 3b CRC-6/DARC: 26 CRC-6/G-704: 06 CRC-6/GSM: 13 CRC-6/ITU: 06 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["6", true] } ] }, { name: "Full generate 7 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-7/MMC: 75 CRC-7/ROHC: 53 CRC-7/UMTS: 61 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["7", true] } ] }, { name: "Full generate 8 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-8: f4 CRC-8/8H2F: df CRC-8/AES: 97 CRC-8/AUTOSAR: df CRC-8/BLUETOOTH: 26 CRC-8/CDMA2000: da CRC-8/DARC: 15 CRC-8/DVB-S2: bc CRC-8/EBU: 97 CRC-8/GSM-A: 37 CRC-8/GSM-B: 94 CRC-8/HITAG: b4 CRC-8/I-432-1: a1 CRC-8/I-CODE: 7e CRC-8/ITU: a1 CRC-8/LTE: ea CRC-8/MAXIM: a1 CRC-8/MAXIM-DOW: a1 CRC-8/MIFARE-MAD: 99 CRC-8/NRSC-5: f7 CRC-8/OPENSAFETY: 3e CRC-8/ROHC: d0 CRC-8/SAE-J1850: 4b CRC-8/SAE-J1850-ZERO: 37 CRC-8/SMBUS: f4 CRC-8/TECH-3250: 97 CRC-8/WCDMA: 25 Fletcher-8: 0c `, recipeConfig: [ { "op": "Generate all checksums", "args": ["8", true] } ] }, { name: "Full generate 10 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-10/ATM: 199 CRC-10/CDMA2000: 233 CRC-10/GSM: 12a CRC-10/I-610: 199 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["10", true] } ] }, { name: "Full generate 11 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-11/FLEXRAY: 5a3 CRC-11/UMTS: 061 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["11", true] } ] }, { name: "Full generate 12 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-12/3GPP: daf CRC-12/CDMA2000: d4d CRC-12/DECT: f5b CRC-12/GSM: b34 CRC-12/UMTS: daf `, recipeConfig: [ { "op": "Generate all checksums", "args": ["12", true] } ] }, { name: "Full generate 13 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-13/BBC: 04fa `, recipeConfig: [ { "op": "Generate all checksums", "args": ["13", true] } ] }, { name: "Full generate 14 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-14/DARC: 082d CRC-14/GSM: 30ae `, recipeConfig: [ { "op": "Generate all checksums", "args": ["14", true] } ] }, { name: "Full generate 15 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-15/CAN: 059e CRC-15/MPT1327: 2566 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["15", true] } ] }, { name: "Full generate 16 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-16: bb3d CRC-16/A: bf05 CRC-16/ACORN: 31c3 CRC-16/ARC: bb3d CRC-16/AUG-CCITT: e5cc CRC-16/AUTOSAR: 29b1 CRC-16/B: 906e CRC-16/BLUETOOTH: 2189 CRC-16/BUYPASS: fee8 CRC-16/CCITT: 2189 CRC-16/CCITT-FALSE: 29b1 CRC-16/CCITT-TRUE: 2189 CRC-16/CCITT-ZERO: 31c3 CRC-16/CDMA2000: 4c06 CRC-16/CMS: aee7 CRC-16/DARC: d64e CRC-16/DDS-110: 9ecf CRC-16/DECT-R: 007e CRC-16/DECT-X: 007f CRC-16/DNP: ea82 CRC-16/EN-13757: c2b7 CRC-16/EPC: d64e CRC-16/EPC-C1G2: d64e CRC-16/GENIBUS: d64e CRC-16/GSM: ce3c CRC-16/I-CODE: d64e CRC-16/IBM: bb3d CRC-16/IBM-3740: 29b1 CRC-16/IBM-SDLC: 906e CRC-16/IEC-61158-2: a819 CRC-16/ISO-HDLC: 906e CRC-16/ISO-IEC-14443-3-A: bf05 CRC-16/ISO-IEC-14443-3-B: 906e CRC-16/KERMIT: 2189 CRC-16/LHA: bb3d CRC-16/LJ1200: bdf4 CRC-16/LTE: 31c3 CRC-16/M17: 772b CRC-16/MAXIM: 44c2 CRC-16/MAXIM-DOW: 44c2 CRC-16/MCRF4XX: 6f91 CRC-16/MODBUS: 4b37 CRC-16/NRSC-5: a066 CRC-16/OPENSAFETY-A: 5d38 CRC-16/OPENSAFETY-B: 20fe CRC-16/PROFIBUS: a819 CRC-16/RIELLO: 63d0 CRC-16/SPI-FUJITSU: e5cc CRC-16/T10-DIF: d0db CRC-16/TELEDISK: 0fb3 CRC-16/TMS37157: 26b1 CRC-16/UMTS: fee8 CRC-16/USB: b4c8 CRC-16/V-41-LSB: 2189 CRC-16/V-41-MSB: 31c3 CRC-16/VERIFONE: fee8 CRC-16/X-25: 906e CRC-16/XMODEM: 31c3 CRC-16/ZMODEM: 31c3 Fletcher-16: 1ede `, recipeConfig: [ { "op": "Generate all checksums", "args": ["16", true] } ] }, { name: "Full generate 17 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-17/CAN-FD: 04f03 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["17", true] } ] }, { name: "Full generate 21 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-21/CAN-FD: 0ed841 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["21", true] } ] }, { name: "Full generate 24 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-24/BLE: c25a56 CRC-24/FLEXRAY-A: 7979bd CRC-24/FLEXRAY-B: 1f23b8 CRC-24/INTERLAKEN: b4f3e6 CRC-24/LTE-A: cde703 CRC-24/LTE-B: 23ef52 CRC-24/OPENPGP: 21cf02 CRC-24/OS-9: 200fa5 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["24", true] } ] }, { name: "Full generate 30 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-30/CDMA: 04c34abf `, recipeConfig: [ { "op": "Generate all checksums", "args": ["30", true] } ] }, { name: "Full generate 31 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-31/PHILIPS: 0ce9e46c `, recipeConfig: [ { "op": "Generate all checksums", "args": ["31", true] } ] }, { name: "Full generate 32 bits checksums with name", input: CHECK_STRING, expectedOutput: `Adler-32: 091e01de CRC-32: cbf43926 CRC-32/AAL5: fc891918 CRC-32/ADCCP: cbf43926 CRC-32/AIXM: 3010bf7f CRC-32/AUTOSAR: 1697d06a CRC-32/BASE91-C: e3069283 CRC-32/BASE91-D: 87315576 CRC-32/BZIP2: fc891918 CRC-32/C: e3069283 CRC-32/CASTAGNOLI: e3069283 CRC-32/CD-ROM-EDC: 6ec2edc4 CRC-32/CKSUM: 765e7680 CRC-32/D: 87315576 CRC-32/DECT-B: fc891918 CRC-32/INTERLAKEN: e3069283 CRC-32/ISCSI: e3069283 CRC-32/ISO-HDLC: cbf43926 CRC-32/JAMCRC: 340bc6d9 CRC-32/MEF: d2c22f51 CRC-32/MPEG-2: 0376e6e7 CRC-32/NVME: e3069283 CRC-32/PKZIP: cbf43926 CRC-32/POSIX: 765e7680 CRC-32/Q: 3010bf7f CRC-32/SATA: cf72afe8 CRC-32/V-42: cbf43926 CRC-32/XFER: bd0be338 CRC-32/XZ: cbf43926 Fletcher-32: df09d509 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["32", true] } ] }, { name: "Full generate 40 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-40/GSM: d4164fc646 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["40", true] } ] }, { name: "Full generate 64 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-64/ECMA-182: 6c40df5f0b497347 CRC-64/GO-ECMA: 995dc9bbdf1939fa CRC-64/GO-ISO: b90956c775a41001 CRC-64/MS: 75d4b74f024eceea CRC-64/NVME: ae8b14860a799888 CRC-64/REDIS: e9c6d914c4b8d9ca CRC-64/WE: 62ec59e3f1a4f00a CRC-64/XZ: 995dc9bbdf1939fa Fletcher-64: 0d0803376c6a689f `, recipeConfig: [ { "op": "Generate all checksums", "args": ["64", true] } ] }, { name: "Full generate 82 bits checksums with name", input: CHECK_STRING, expectedOutput: `CRC-82/DARC: 09ea83f625023801fd612 `, recipeConfig: [ { "op": "Generate all checksums", "args": ["82", true] } ] } ]); ================================================ FILE: tests/operations/tests/GenerateAllHashes.mjs ================================================ /** * GenerateAllHashes tests. * * @author john19696 [john19696@protonmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Full generate all hashes", input: "test", expectedOutput: `MD2: dd34716876364a02d0195e2fb9ae2d1b MD4: db346d691d7acc4dc2625db19f9e3f52 MD5: 098f6bcd4621d373cade4e832627b4f6 MD6: 93c8a7d0ff132f325138a82b2baa98c12a7c9ac982feb6c5b310a1ca713615bd SHA0: f8d3b312442a67706057aeb45b983221afb4f035 SHA1: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 SHA2 224: 90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809 SHA2 256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 SHA2 384: 768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9 SHA2 512: ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff SHA3 224: 3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b SHA3 256: 36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80 SHA3 384: e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7ff0f1b41eecb9db3ff219007c4e097260d58621bd SHA3 512: 9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14 Keccak 224: 3be30a9ff64f34a5861116c5198987ad780165f8366e67aff4760b5e Keccak 256: 9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658 Keccak 384: 53d0ba137307d4c2f9b6674c83edbd58b70c0f4340133ed0adc6fba1d2478a6a03b7788229e775d2de8ae8c0759d0527 Keccak 512: 1e2e9fc2002b002d75198b7503210c05a1baac4560916a3c6d93bcce3a50d7f00fd395bf1647b9abb8d1afcc9c76c289b0c9383ba386a956da4b38934417789e Shake 128: d3b0aa9cd8b7255622cebc631e867d4093d6f6010191a53973c45fec9b07c774 Shake 256: b54ff7255705a71ee2925e4a3e30e41aed489a579d5595e0df13e32e1e4dd202a7c7f68b31d6418d9845eb4d757adda6ab189e1bb340db818e5b3bc725d992fa RIPEMD-128: f1abb5083c9ff8a9dbbca9cd2b11fead RIPEMD-160: 5e52fee47e6b070565f74372468cdc699de89107 RIPEMD-256: fe0289110d07daeee9d9500e14c57787d9083f6ba10e6bcb256f86bb4fe7b981 RIPEMD-320: 3b0a2e841e589cf583634a5dd265d2b5d497c4cc44b241e34e0f62d03e98c1b9dc72970b9bc20eb5 HAS-160: cb15e491eec6e769771d1f811315139c93071084 Whirlpool-0: d50ff71342b521974bae166539871922669afcfc7181250ebbae015c317ebb797173a69e7a05afd11099a9f0918159cd5bc88434d3ca44513d7263caea9244fe Whirlpool-T: e6b4aa087751b4428171777f1893ba585404c7e0171787720eba0d8bccd710dc2c42f874c572bfae4cedabf50f2c80bf923805d4e31c504b86ca3bc59265e7dd Whirlpool: b913d5bbb8e461c2c5961cbe0edcdadfd29f068225ceb37da6defcf89849368f8c6c2eb6a4c4ac75775d032a0ecfdfe8550573062b653fe92fc7b8fb3b7be8d6 BLAKE2b-128: 44a8995dd50b6657a037a7839304535b BLAKE2b-160: a34fc3b6d2cce8beb3216c2bbb5e55739e8121ed BLAKE2b-256: 928b20366943e2afd11ebc0eae2e53a93bf177a4fcf35bcc64d503704e65e202 BLAKE2b-384: 8a84b8666c8fcfb69f2ec41f578d7c85fbdb504ea6510fb05b50fcbf7ed8153c77943bc2da73abb136834e1a0d4f22cb BLAKE2b-512: a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a896c0411f38312e1d66e0bf16386c86a89bea572 BLAKE2s-128: e9ddd9926b9dcb382e09be39ba403d2c BLAKE2s-160: d6197dabec2bd6f4ff303b8e519e8f15d42a453d BLAKE2s-256: f308fc02ce9172ad02a7d75800ecfc027109bc67987ea32aba9b8dcc7b10150e Streebog-256: 12a50838191b5504f1e5f2fd078714cf6b592b9d29af99d0b10d8d02881c3857 Streebog-512: 7200bf5dea560f0d7960d07fdc8874ad9f3b86ece2e45f5502ae2e176f2c928e0e581152281f5aee818318bed7cbe6aa69999589234723ceb33175598365b5c8 GOST: ee67303696d205ddd2b2363e8e01b4b7199a80957d94d7678eaad3fc834c5a27 LM Hash: 01FC5A6BE7BC6929AAD3B435B51404EE NT Hash: 0CB6948805F797BF2A82807973B89537 SSDEEP: 3:Hn:Hn CTPH: A:E:E `, recipeConfig: [ { "op": "Generate all hashes", "args": ["All", true] } ] }, { name: "Hashes with length 32", input: "test", expectedOutput: `MD2: dd34716876364a02d0195e2fb9ae2d1b MD4: db346d691d7acc4dc2625db19f9e3f52 MD5: 098f6bcd4621d373cade4e832627b4f6 RIPEMD-128: f1abb5083c9ff8a9dbbca9cd2b11fead BLAKE2b-128: 44a8995dd50b6657a037a7839304535b BLAKE2s-128: e9ddd9926b9dcb382e09be39ba403d2c LM Hash: 01FC5A6BE7BC6929AAD3B435B51404EE NT Hash: 0CB6948805F797BF2A82807973B89537 `, recipeConfig: [ { "op": "Generate all hashes", "args": ["128", true] } ] }, { name: "Hashes without names", input: "test", expectedOutput: `93c8a7d0ff132f325138a82b2baa98c12a7c9ac982feb6c5b310a1ca713615bd 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80 9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658 d3b0aa9cd8b7255622cebc631e867d4093d6f6010191a53973c45fec9b07c774 fe0289110d07daeee9d9500e14c57787d9083f6ba10e6bcb256f86bb4fe7b981 928b20366943e2afd11ebc0eae2e53a93bf177a4fcf35bcc64d503704e65e202 f308fc02ce9172ad02a7d75800ecfc027109bc67987ea32aba9b8dcc7b10150e 12a50838191b5504f1e5f2fd078714cf6b592b9d29af99d0b10d8d02881c3857 ee67303696d205ddd2b2363e8e01b4b7199a80957d94d7678eaad3fc834c5a27 `, recipeConfig: [ { "op": "Generate all hashes", "args": ["256", false] } ] } ]); ================================================ FILE: tests/operations/tests/GenerateDeBruijnSequence.mjs ================================================ /** * De Brujin Sequence tests. * * @author gchq77703 [gchq77703@gchq.gov.uk] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Generate De Bruijn Sequence: Small Sequence", input: "", expectedOutput: "00010111", recipeConfig: [ { "op": "Generate De Bruijn Sequence", "args": [2, 3] } ] }, { name: "Generate De Bruijn Sequence: Long Sequence", input: "", expectedOutput: "0000010000200003000110001200013000210002200023000310003200033001010010200103001110011200113001210012200123001310013200133002010020200203002110021200213002210022200223002310023200233003010030200303003110031200313003210032200323003310033200333010110101201013010210102201023010310103201033011020110301111011120111301121011220112301131011320113301202012030121101212012130122101222012230123101232012330130201303013110131201313013210132201323013310133201333020210202202023020310203202033021030211102112021130212102122021230213102132021330220302211022120221302221022220222302231022320223302303023110231202313023210232202323023310233202333030310303203033031110311203113031210312203123031310313203133032110321203213032210322203223032310323203233033110331203313033210332203323033310333203333111112111131112211123111321113311212112131122211223112321123311312113131132211323113321133312122121231213212133122131222212223122321223312313123221232312332123331313213133132221322313232132331332213323133321333322222322233223232233323233233333", recipeConfig: [ { "op": "Generate De Bruijn Sequence", "args": [4, 5] } ] } ]); ================================================ FILE: tests/operations/tests/GenerateQRCode.mjs ================================================ /** * Generate QR Code tests * * @author GCHQDeveloper581 * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Generate QR Code : PNG", input: "Hello world!", 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", recipeConfig: [ { "op": "Generate QR Code", "args": ["PNG", 5, 4, "Medium"] }, { "op": "To Hex", "args": ["Space", 0] } ], }, { name: "Generate QR Code : SVG", input: "Hello world!", expectedOutput: '', recipeConfig: [ { "op": "Generate QR Code", "args": ["SVG", 5, 4, "Medium"] }, ], }, { name: "Generate QR Code : EPS", input: "Hello world!", 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", recipeConfig: [ { "op": "Generate QR Code", "args": ["EPS", 6, 5, "Quartile"] }, { "op": "Remove whitespace", "args": [false, true, true, false, false, false] }, ], }, { name: "Generate QR Code : PDF", input: "Hello world!", 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", recipeConfig: [ { "op": "Generate QR Code", "args": ["PDF", 5, 4, "Low"] }, { "op": "Remove whitespace", "args": [false, true, true, false, false, false] }, ], }, ]); ================================================ FILE: tests/operations/tests/GetAllCasings.mjs ================================================ /** * GetAllCasings tests. * * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "All casings of test", input: "test", expectedOutput: "test\nTest\ntEst\nTEst\nteSt\nTeSt\ntESt\nTESt\ntesT\nTesT\ntEsT\nTEsT\nteST\nTeST\ntEST\nTEST", recipeConfig: [ { "op": "Get All Casings", "args": [] } ] }, { name: "All casings of t", input: "t", expectedOutput: "t\nT", recipeConfig: [ { "op": "Get All Casings", "args": [] } ] }, { name: "All casings of null", input: "", expectedOutput: "", recipeConfig: [ { "op": "Get All Casings", "args": [] } ] } ]); ================================================ FILE: tests/operations/tests/Gunzip.mjs ================================================ /** * Gunzip Tests. * * @author n1073645 [n1073645@gmail.com] * * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Gunzip: No comment, no checksum and no filename", input: "1f8b0800f7c8f85d00ff0dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", expectedOutput: "The quick brown fox jumped over the slow dog", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Gunzip", args: [] } ] }, { name: "Gunzip: No comment, no checksum and filename", input: "1f8b080843c9f85d00ff66696c656e616d65000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", expectedOutput: "The quick brown fox jumped over the slow dog", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Gunzip", args: [] } ] }, { name: "Gunzip: Has a comment, no checksum and has a filename", input: "1f8b08186fc9f85d00ff66696c656e616d6500636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", expectedOutput: "The quick brown fox jumped over the slow dog", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Gunzip", args: [] } ] } ]); ================================================ FILE: tests/operations/tests/Gzip.mjs ================================================ /** * Gzip Tests. * * @author n1073645 [n1073645@gmail.com] * * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Gzip: No comment, no checksum and no filename", input: "The quick brown fox jumped over the slow dog", expectedOutput: "0dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", recipeConfig: [ { op: "Gzip", args: ["Dynamic Huffman Coding", "", "", false] }, { op: "Drop bytes", args: [0, 10, false] }, { op: "To Hex", args: ["None"] } ] }, { name: "Gzip: No comment, no checksum and has a filename", input: "The quick brown fox jumped over the slow dog", expectedOutput: "636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", recipeConfig: [ { op: "Gzip", args: ["Dynamic Huffman Coding", "comment", "", false] }, { op: "Drop bytes", args: [0, 10, false] }, { op: "To Hex", args: ["None"] } ] }, { name: "Gzip: Has a comment, no checksum and no filename", input: "The quick brown fox jumped over the slow dog", expectedOutput: "636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", recipeConfig: [ { op: "Gzip", args: ["Dynamic Huffman Coding", "", "comment", false] }, { op: "Drop bytes", args: [0, 10, false] }, { op: "To Hex", args: ["None"] } ] }, { name: "Gzip: Has a comment, no checksum and has a filename", input: "The quick brown fox jumped over the slow dog", expectedOutput: "66696c656e616d6500636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", recipeConfig: [ { op: "Gzip", args: ["Dynamic Huffman Coding", "filename", "comment", false] }, { op: "Drop bytes", args: [0, 10, false] }, { op: "To Hex", args: ["None"] } ] }, ]); ================================================ FILE: tests/operations/tests/HASSH.mjs ================================================ /** * HASSH tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "HASSH Client Fingerprint", input: "000003140814c639665f5425dcb80bf9f0a048380a410000007e6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d7368613235362c6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d6473730000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e73650000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e736500000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d39360000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c6962000000000000000000000000000000000000000000", expectedOutput: "21b457a327ce7a2d4fce5ef2c42400bd", recipeConfig: [ { "op": "HASSH Client Fingerprint", "args": ["Hex", "Hash digest"] } ], }, { name: "HASSH Server Fingerprint", input: "0000027c0b142c7bb93a1da21c9e54f5862e60a5597c000000596469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d647373000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d637472000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d63747200000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d3936000000096e6f6e652c7a6c6962000000096e6f6e652c7a6c6962000000000000000000000000000000000000000000000000", expectedOutput: "f430cd6761697a6a658ee1d45ed22e49", recipeConfig: [ { "op": "HASSH Server Fingerprint", "args": ["Hex", "Hash digest"] } ], } ]); ================================================ FILE: tests/operations/tests/HKDF.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "HKDF: RFC5869 Test Case 1", "input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "expectedOutput": "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c"}, {"option": "Hex", "string": "f0f1f2f3f4f5f6f7f8f9"}, "SHA256", "with salt", 42, ], }, ], }, { "name": "HKDF: RFC5869 Test Case 2", "input": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f", "expectedOutput": "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"}, {"option": "Hex", "string": "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"}, "SHA256", "with salt", 82, ], }, ], }, { "name": "HKDF: RFC5869 Test Case 3", "input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "expectedOutput": "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "SHA256", "with salt", 42, ], }, ], }, { "name": "HKDF: RFC5869 Test Case 4", "input": "0b0b0b0b0b0b0b0b0b0b0b", "expectedOutput": "085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c"}, {"option": "Hex", "string": "f0f1f2f3f4f5f6f7f8f9"}, "SHA1", "with salt", 42, ], }, ], }, { "name": "HKDF: RFC5869 Test Case 5", "input": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f", "expectedOutput": "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"}, {"option": "Hex", "string": "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"}, "SHA1", "with salt", 82, ], }, ], }, { "name": "HKDF: RFC5869 Test Case 6", "input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "expectedOutput": "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "SHA1", "with salt", 42, ], }, ], }, { "name": "HKDF: RFC5869 Test Case 7", "input": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "expectedOutput": "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, "SHA1", "no salt", 42, ], }, ], }, { "name": "HKDF: RFC5869 Test Case 1 with skip extract", "input": "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5", "expectedOutput": "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": "f0f1f2f3f4f5f6f7f8f9"}, "SHA256", "skip", 42, ], }, ], }, { "name": "HKDF: too large L", "input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "expectedOutput": "L too large (maximum length for SHA256 is 8160)", "recipeConfig": [ { "op": "From Hex", "args": ["None"], }, { "op": "Derive HKDF key", "args": [ {"option": "Hex", "string": "000102030405060708090a0b0c"}, {"option": "Hex", "string": "f0f1f2f3f4f5f6f7f8f9"}, "SHA256", "with salt", 8161, ], }, ], }, ]); ================================================ FILE: tests/operations/tests/Hash.mjs ================================================ /** * Hash tests. * * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "MD2", input: "Hello, World!", expectedOutput: "1c8f1e6a94aaa7145210bf90bb52871a", recipeConfig: [ { "op": "MD2", "args": [] } ] }, { name: "MD4", input: "Hello, World!", expectedOutput: "94e3cb0fa9aa7a5ee3db74b79e915989", recipeConfig: [ { "op": "MD4", "args": [] } ] }, { name: "MD5", input: "Hello, World!", expectedOutput: "65a8e27d8879283831b664bd8b7f0ad4", recipeConfig: [ { "op": "MD5", "args": [] } ] }, { name: "MD6", input: "Hello, World!", expectedOutput: "ce5effce32637e6b8edaacc9284b873c3fd4e66f9779a79df67eb4a82dda8230", recipeConfig: [ { "op": "MD6", "args": [256, 64, ""] } ] }, { name: "SHA0", input: "Hello, World!", expectedOutput: "5a5588f0407c6ae9a988758e76965f841b299229", recipeConfig: [ { "op": "SHA0", "args": [] } ] }, { name: "SHA1", input: "Hello, World!", expectedOutput: "0a0a9f2a6772942557ab5355d76af442f8f65e01", recipeConfig: [ { "op": "SHA1", "args": [] } ] }, { name: "SHA2 224", input: "Hello, World!", expectedOutput: "72a23dfa411ba6fde01dbfabf3b00a709c93ebf273dc29e2d8b261ff", recipeConfig: [ { "op": "SHA2", "args": ["224"] } ] }, { name: "SHA2 384", input: "Hello, World!", expectedOutput: "5485cc9b3365b4305dfb4e8337e0a598a574f8242bf17289e0dd6c20a3cd44a089de16ab4ab308f63e44b1170eb5f515", recipeConfig: [ { "op": "SHA2", "args": ["384"] } ] }, { name: "SHA2 256", input: "Hello, World!", expectedOutput: "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f", recipeConfig: [ { "op": "SHA2", "args": ["256"] } ] }, { name: "SHA2 512", input: "Hello, World!", expectedOutput: "374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387", recipeConfig: [ { "op": "SHA2", "args": ["512"] } ] }, { name: "SHA2 512/224", input: "Hello, World!", expectedOutput: "766745f058e8a0438f19de48ae56ea5f123fe738af39bca050a7547a", recipeConfig: [ { "op": "SHA2", "args": ["512/224"] } ] }, { name: "SHA2 512/256", input: "Hello, World!", expectedOutput: "0686f0a605973dc1bf035d1e2b9bad1985a0bff712ddd88abd8d2593e5f99030", recipeConfig: [ { "op": "SHA2", "args": ["512/256"] } ] }, { name: "SHA3 224", input: "Hello, World!", expectedOutput: "853048fb8b11462b6100385633c0cc8dcdc6e2b8e376c28102bc84f2", recipeConfig: [ { "op": "SHA3", "args": ["224"] } ] }, { name: "SHA3 384", input: "Hello, World!", expectedOutput: "aa9ad8a49f31d2ddcabbb7010a1566417cff803fef50eba239558826f872e468c5743e7f026b0a8e5b2d7a1cc465cdbe", recipeConfig: [ { "op": "SHA3", "args": ["384"] } ] }, { name: "SHA3 256", input: "Hello, World!", expectedOutput: "1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef", recipeConfig: [ { "op": "SHA3", "args": ["256"] } ] }, { name: "SHA3 512", input: "Hello, World!", expectedOutput: "38e05c33d7b067127f217d8c856e554fcff09c9320b8a5979ce2ff5d95dd27ba35d1fba50c562dfd1d6cc48bc9c5baa4390894418cc942d968f97bcb659419ed", recipeConfig: [ { "op": "SHA3", "args": ["512"] } ] }, { name: "Keccak 224", input: "Hello, World!", expectedOutput: "4eaaf0e7a1e400efba71130722e1cb4d59b32afb400e654afec4f8ce", recipeConfig: [ { "op": "Keccak", "args": ["224"] } ] }, { name: "Keccak 384", input: "Hello, World!", expectedOutput: "4d60892fde7f967bcabdc47c73122ae6311fa1f9be90d721da32030f7467a2e3db3f9ccb3c746483f9d2b876e39def17", recipeConfig: [ { "op": "Keccak", "args": ["384"] } ] }, { name: "Keccak 256", input: "Hello, World!", expectedOutput: "acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f", recipeConfig: [ { "op": "Keccak", "args": ["256"] } ] }, { name: "Keccak 512", input: "Hello, World!", expectedOutput: "eda765576c84c600ed7f5d97510e92703b61f5215def2a161037fd9dd1f5b6ed4f86ce46073c0e3f34b52de0289e9c618798fff9dd4b1bfe035bdb8645fc6e37", recipeConfig: [ { "op": "Keccak", "args": ["512"] } ] }, { name: "Shake 128", input: "Hello, World!", expectedOutput: "2bf5e6dee6079fad604f573194ba8426bd4d30eb13e8ba2edae70e529b570cbd", recipeConfig: [ { "op": "Shake", "args": ["128", 256] } ] }, { name: "Shake 256", input: "Hello, World!", expectedOutput: "b3be97bfd978833a65588ceae8a34cf59e95585af62063e6b89d0789f372424e8b0d1be4f21b40ce5a83a438473271e0661854f02d431db74e6904d6c347d757", recipeConfig: [ { "op": "Shake", "args": ["256", 512] } ] }, { name: "RIPEMD 128", input: "Hello, World!", expectedOutput: "67f9fe75ca2886dc76ad00f7276bdeba", recipeConfig: [ { "op": "RIPEMD", "args": ["128"] } ] }, { name: "RIPEMD 160", input: "Hello, World!", expectedOutput: "527a6a4b9a6da75607546842e0e00105350b1aaf", recipeConfig: [ { "op": "RIPEMD", "args": ["160"] } ] }, { name: "RIPEMD 256", input: "Hello, World!", expectedOutput: "567750c6d34dcba7ae038a80016f3ca3260ec25bfdb0b68bbb8e730b00b2447d", recipeConfig: [ { "op": "RIPEMD", "args": ["256"] } ] }, { name: "RIPEMD 320", input: "Hello, World!", expectedOutput: "f9832e5bb00576fc56c2221f404eb77addeafe49843c773f0df3fc5a996d5934f3c96e94aeb80e89", recipeConfig: [ { "op": "RIPEMD", "args": ["320"] } ] }, { name: "HAS-160", input: "Hello, World!", expectedOutput: "8f6dd8d7c8a04b1cb3831adc358b1e4ac2ed5984", recipeConfig: [ { "op": "HAS-160", "args": [] } ] }, { name: "Whirlpool-0", input: "Hello, World!", expectedOutput: "1c327026f565a0105a827efbfb3d3635cdb042c0aabb8416e96deb128e6c5c8684b13541cf31c26c1488949df050311c6999a12eb0e7002ad716350f5c7700ca", recipeConfig: [ { "op": "Whirlpool", "args": ["Whirlpool-0"] } ] }, { name: "Whirlpool-T", input: "Hello, World!", expectedOutput: "16c581089b6a6f356ae56e16a63a4c613eecd82a2a894b293f5ee45c37a31d09d7a8b60bfa7e414bd4a7166662cea882b5cf8c96b7d583fc610ad202591bcdb1", recipeConfig: [ { "op": "Whirlpool", "args": ["Whirlpool-T"] } ] }, { name: "Whirlpool", input: "Hello, World!", expectedOutput: "3d837c9ef7bb291bd1dcfc05d3004af2eeb8c631dd6a6c4ba35159b8889de4b1ec44076ce7a8f7bfa497e4d9dcb7c29337173f78d06791f3c3d9e00cc6017f0b", recipeConfig: [ { "op": "Whirlpool", "args": ["Whirlpool"] } ] }, { name: "Snefru 2 128", input: "Hello, World!", expectedOutput: "a4ad2b8848580511d0884fb4233a7e7a", recipeConfig: [ { "op": "Snefru", "args": ["128", "2"] } ] }, { name: "Snefru 4 128", input: "Hello, World!", expectedOutput: "d154eae2c9ffbcd2e1bdaf0b84736126", recipeConfig: [ { "op": "Snefru", "args": ["128", "4"] } ] }, { name: "Snefru 8 128", input: "Hello, World!", expectedOutput: "6f3d55b69557abb0a3c4e9de9d29ba5d", recipeConfig: [ { "op": "Snefru", "args": ["128", "8"] } ] }, { name: "Snefru 2 256", input: "Hello, World!", expectedOutput: "65736daba648de28ef4c4a316b4684584ecf9f22ddb5c457729e6bf0f40113c4", recipeConfig: [ { "op": "Snefru", "args": ["256", "2"] } ] }, { name: "Snefru 4 256", input: "Hello, World!", expectedOutput: "71b0ea4b3e33f2e58bcc67c8a8de060b99ec0107355bbfdc18d8f65f0194ffcc", recipeConfig: [ { "op": "Snefru", "args": ["256", "4"] } ] }, { name: "Snefru 8 256", input: "Hello, World!", expectedOutput: "255cd401414c79588cf689e8d5ff0536a2cfab83fcae36e654f202b09bc4b8a7", recipeConfig: [ { "op": "Snefru", "args": ["256", "8"] } ] }, { name: "SM3 256 64", input: "Hello, World!", expectedOutput: "7ed26cbf0bee4ca7d55c1e64714c4aa7d1f163089ef5ceb603cd102c81fbcbc5", recipeConfig: [ { "op": "SM3", "args": ["256", "64"] } ] }, { name: "HMAC: SHA256", input: "Hello, World!", expectedOutput: "52589bd80ccfa4acbb3f9512dfaf4f700fa5195008aae0b77a9e47dcca75beac", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Latin1", "string": "test"}, "SHA256"] } ] }, { name: "HMAC: RFC4231 Test Case 1 SHA-224", input: "Hi There", expectedOutput: "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"}, "SHA224"] } ] }, { name: "HMAC: RFC4231 Test Case 1 SHA-256", input: "Hi There", expectedOutput: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"}, "SHA256"] } ] }, { name: "HMAC: RFC4231 Test Case 1 SHA-384", input: "Hi There", expectedOutput: "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"}, "SHA384"] } ] }, { name: "HMAC: RFC4231 Test Case 1 SHA-512", input: "Hi There", expectedOutput: "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"}, "SHA512"] } ] }, { name: "HMAC: RFC4231 Test Case 2 SHA-224", input: "what do ya want for nothing?", expectedOutput: "a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "4a656665"}, "SHA224"] } ] }, { name: "HMAC: RFC4231 Test Case 2 SHA-256", input: "what do ya want for nothing?", expectedOutput: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "4a656665"}, "SHA256"] } ] }, { name: "HMAC: RFC4231 Test Case 2 SHA-384", input: "what do ya want for nothing?", expectedOutput: "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "4a656665"}, "SHA384"] } ] }, { name: "HMAC: RFC4231 Test Case 2 SHA-512", input: "what do ya want for nothing?", expectedOutput: "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "4a656665"}, "SHA512"] } ] }, { name: "HMAC: RFC4231 Test Case 3 SHA-224", input: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", expectedOutput: "7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA224"] } ] }, { name: "HMAC: RFC4231 Test Case 3 SHA-256", input: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", expectedOutput: "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA256"] } ] }, { name: "HMAC: RFC4231 Test Case 3 SHA-384", input: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", expectedOutput: "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA384"] } ] }, { name: "HMAC: RFC4231 Test Case 3 SHA-512", input: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", expectedOutput: "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA512"] } ] }, { name: "HMAC: RFC4231 Test Case 4 SHA-224", input: "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", expectedOutput: "6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "HMAC", "args": [{"option": "Hex", "string": "0102030405060708090a0b0c0d0e0f10111213141516171819"}, "SHA224"] } ] }, { name: "HMAC: RFC4231 Test Case 4 SHA-256", input: "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", expectedOutput: "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "HMAC", "args": [{"option": "Hex", "string": "0102030405060708090a0b0c0d0e0f10111213141516171819"}, "SHA256"] } ] }, { name: "HMAC: RFC4231 Test Case 4 SHA-384", input: "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", expectedOutput: "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "HMAC", "args": [{"option": "Hex", "string": "0102030405060708090a0b0c0d0e0f10111213141516171819"}, "SHA384"] } ] }, { name: "HMAC: RFC4231 Test Case 4 SHA-512", input: "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", expectedOutput: "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "HMAC", "args": [{"option": "Hex", "string": "0102030405060708090a0b0c0d0e0f10111213141516171819"}, "SHA512"] } ] }, { name: "HMAC: RFC4231 Test Case 6 SHA-224", input: "Test Using Larger Than Block-Size Key - Hash Key First", expectedOutput: "95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA224"] } ] }, { name: "HMAC: RFC4231 Test Case 6 SHA-256", input: "Test Using Larger Than Block-Size Key - Hash Key First", expectedOutput: "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA256"] } ] }, { name: "HMAC: RFC4231 Test Case 6 SHA-384", input: "Test Using Larger Than Block-Size Key - Hash Key First", expectedOutput: "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA384"] } ] }, { name: "HMAC: RFC4231 Test Case 6 SHA-512", input: "Test Using Larger Than Block-Size Key - Hash Key First", expectedOutput: "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA512"] } ] }, { name: "HMAC: RFC4231 Test Case 7 SHA-224", 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.", expectedOutput: "3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA224"] } ] }, { name: "HMAC: RFC4231 Test Case 7 SHA-256", 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.", expectedOutput: "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA256"] } ] }, { name: "HMAC: RFC4231 Test Case 7 SHA-384", 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.", expectedOutput: "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA384"] } ] }, { name: "HMAC: RFC4231 Test Case 7 SHA-512", 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.", expectedOutput: "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58", recipeConfig: [ { "op": "HMAC", "args": [{"option": "Hex", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA512"] } ] }, { name: "MD5: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "4f4f02e2646545aa8fc42f613c9aa068", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "MD5", "args": [] } ] }, { name: "SHA1: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "2c5400aaee7e8ad4cad29bfbdf8d566924e5442c", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA1", "args": [] } ] }, { name: "SHA2 224: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "66c166eba2529ecc44a7b7b218a64a8e3892f873c8d231e8e3c1ef3d", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA2", "args": ["224"] } ] }, { name: "SHA2 256: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "186ffd22c3af83995afa4a0316023f81a7f8834fd16bd2ed358c7b1b8182ba41", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA2", "args": ["256"] } ] }, { name: "SHA2 384: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "2a6369ffec550ea0bfb810b3b8246b7d6b7f060edfae88441f0f242b98b91549aa4ff407de38c6d03b5f377434ad2f36", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA2", "args": ["384"] } ] }, { name: "SHA2 512: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "544ae686522c05b70d12b460b5b39ea0a758eb4027333edbded7e2b3f467aa605804f71f54db61a7bbe50e6e7898510635efd6721fd418a9ea4d05b286d12806", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA2", "args": ["512"] } ] }, { name: "SHA3 224: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "e2c07562ee8c2d73e3dd309efea257159abd0948ebc14619bab9ffb3", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA3", "args": ["224"] } ] }, { name: "SHA3 256: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "55a55275387586afd1ed64757c9ee7ad1d96ca81a5b7b742c40127856ee78a2d", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA3", "args": ["256"] } ] }, { name: "SHA3 384: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "39f8796dd697dc39e5a943817833793f2c29dc0d1adc7037854c0fb51e135c6bd26b113240c4fb1e3fcc16ff8690c91a", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA3", "args": ["384"] } ] }, { name: "SHA3 512: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", expectedOutput: "ee9061bed83b1ad1e2fc4a4bac72a5a65a23a0fa55193b808af0a3e2013b718a5a3e40474765b4f93d1b2747401058a5b58099cc890a159db92b2ea816287add", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "SHA3", "args": ["512"] } ] }, { name: "MD5: UTF-8", input: "ნუ პანიკას", expectedOutput: "2e93ee2b5b2a337ccb678c7db12eff1b", recipeConfig: [ { "op": "MD5", "args": [] } ] }, { name: "SHA1: UTF-8", input: "ნუ პანიკას", expectedOutput: "87f483b1515dce672be044bf183ae8103e3b2d4b", recipeConfig: [ { "op": "SHA1", "args": [] } ] }, { name: "SHA2 224: UTF-8", input: "ნუ პანიკას", expectedOutput: "563ca57b500157717961a5fa87ce42c6db76a488c98ea9c28d620770", recipeConfig: [ { "op": "SHA2", "args": ["224"] } ] }, { name: "SHA2 256: UTF-8", input: "ნუ პანიკას", expectedOutput: "36abbb4622ffff06aa3e3cea266765601b21457bb3755a0a2cf0a206422863c1", recipeConfig: [ { "op": "SHA2", "args": ["256"] } ] }, { name: "SHA2 384: UTF-8", input: "ნუ პანიკას", expectedOutput: "140b929391a66c9a943bcd60e6964f0d19526d3bc9ba020fbb29aae51cddb8e63a78784d8770f1d36335bf4efff8c131", recipeConfig: [ { "op": "SHA2", "args": ["384"] } ] }, { name: "SHA2 512: UTF-8", input: "ნუ პანიკას", expectedOutput: "04a7887c400bf647b7c67b9a0f1ada70d176348b5afdfebea184f7e62748849828669c7b5160be99455fdbf625589bd1689c003bc06ef60c39607d825a2f8838", recipeConfig: [ { "op": "SHA2", "args": ["512"] } ] }, { name: "SHA3 224: UTF-8", input: "ნუ პანიკას", expectedOutput: "b3ffc9620949f879cb561fb240452494e2566cb4e4f701a85715e14f", recipeConfig: [ { "op": "SHA3", "args": ["224"] } ] }, { name: "SHA3 256: UTF-8", input: "ნუ პანიკას", expectedOutput: "b5f247d725b46546c832502cd07bccb5d4de0c41a6665d3944ed2cc55cd9d156", recipeConfig: [ { "op": "SHA3", "args": ["256"] } ] }, { name: "SHA3 384: UTF-8", input: "ნუ პანიკას", expectedOutput: "93e87b9aa8c9c47eba146adac357c525b418b71677f6db01d1c760d87b058682e639c8d43a8bfe91529cecd9800700e3", recipeConfig: [ { "op": "SHA3", "args": ["384"] } ] }, { name: "SHA3 512: UTF-8", input: "ნუ პანიკას", expectedOutput: "1fbc484b5184982561795162757717474eebc846ca9f10029a75a54cdd897a7b48d1db42f2478fa1d5d213a0dd7de71c809cb19c60581ba57e7289d29408fb36", recipeConfig: [ { "op": "SHA3", "args": ["512"] } ] }, { name: "Bcrypt compare: dolphin", input: "dolphin", expectedOutput: "Match: dolphin", recipeConfig: [ { op: "Bcrypt compare", args: ["$2a$10$qyon0LQCmMxpFFjwWH6Qh.dDdhqntQh./IN0RXCc3XIMILuOYZKgK"] } ] }, { name: "Scrypt: RFC test vector 1", input: "", expectedOutput: "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", recipeConfig: [ { op: "Scrypt", args: [ { "option": "Latin1", "string": "" }, 16, 1, 1, 64 ] } ] }, { name: "Scrypt: RFC test vector 2", input: "password", expectedOutput: "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", recipeConfig: [ { op: "Scrypt", args: [ { "option": "Latin1", "string": "NaCl" }, 1024, 8, 16, 64 ] } ] }, { name: "Scrypt: RFC test vector 3", input: "pleaseletmein", expectedOutput: "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", recipeConfig: [ { op: "Scrypt", args: [ { "option": "Latin1", "string": "SodiumChloride" }, 16384, 8, 1, 64 ] } ] }, { name: "Streebog-256: Test Case 1", input: "", expectedOutput: "3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb", recipeConfig: [ { op: "Streebog", args: ["256"] } ] }, { name: "Streebog-256: Test Case 2", input: "The quick brown fox jumps over the lazy dog", expectedOutput: "3e7dea7f2384b6c5a3d0e24aaa29c05e89ddd762145030ec22c71a6db8b2c1f4", recipeConfig: [ { op: "Streebog", args: ["256"] } ] }, { name: "Streebog-512: Test Case 1", input: "", expectedOutput: "8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a", recipeConfig: [ { op: "Streebog", args: ["512"] } ] }, { name: "Streebog-512: Test Case 2", input: "The quick brown fox jumps over the lazy dog", expectedOutput: "d2b793a0bb6cb5904828b5b6dcfb443bb8f33efc06ad09368878ae4cdc8245b97e60802469bed1e7c21a64ff0b179a6a1e0bb74d92965450a0adab69162c00fe", recipeConfig: [ { op: "Streebog", args: ["512"] } ] }, { name: "GOST R 34.11-94: Test Case 1", input: "", expectedOutput: "981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0", recipeConfig: [ { op: "GOST Hash", args: ["GOST 28147 (1994)", "256", "D-A"] } ] }, { name: "GOST R 34.11-94: Test Case 2", input: "This is message, length=32 bytes", expectedOutput: "2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb", recipeConfig: [ { op: "GOST Hash", args: ["GOST 28147 (1994)", "256", "D-A"] } ] }, /* { // This takes a LONG time to run (over a minute usually). name: "Scrypt: RFC test vector 4", input: "pleaseletmein", expectedOutput: "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4", recipeConfig: [ { op: "Scrypt", args: [ { "option": "Latin1", "string": "SodiumChloride" }, 1048576, 8, 1, 64 ] } ] }, */ { name: "Argon2", input: "argon2password", expectedOutput: "$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$s43my9eBljQADuF/LWCG8vGqwAJzOorKQ0Yog8jFvbw", recipeConfig: [ { op: "Argon2", args: [ {"option": "UTF8", "string": "somesalt"}, 3, 4096, 1, 32, "Argon2i", "Encoded hash" ] } ] }, { name: "Argon2 compare", input: "argon2password", expectedOutput: "Match: argon2password", recipeConfig: [ { op: "Argon2 compare", args: [ "$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$s43my9eBljQADuF/LWCG8vGqwAJzOorKQ0Yog8jFvbw" ] } ] } ]); ================================================ FILE: tests/operations/tests/HaversineDistance.mjs ================================================ /** * Haversine distance tests. * * @author Dachande663 [dachande663@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Haversine distance", input: "51.487263,-0.124323, 38.9517,-77.1467", expectedOutput: "5902542.836307819", recipeConfig: [ { "op": "Haversine distance", "args": [] } ], }, { name: "Haversine distance, zero distance", input: "51.487263,-0.124323, 51.487263,-0.124323", expectedOutput: "0", recipeConfig: [ { "op": "Haversine distance", "args": [] } ], } ]); ================================================ FILE: tests/operations/tests/Hex.mjs ================================================ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "ASCII to Hex stream", input: "aberystwyth", expectedOutput: "6162657279737477797468", recipeConfig: [ { "op": "To Hex", "args": [ "None", 0 ] }, ] }, { name: "ASCII to Hex with colon deliminator ", input: "aberystwyth", expectedOutput: "61:62:65:72:79:73:74:77:79:74:68", recipeConfig: [ { "op": "To Hex", "args": [ "Colon", 0 ] } ] }, { name: "ASCII to 0x Hex with comma", input: "aberystwyth", expectedOutput: "0x61,0x62,0x65,0x72,0x79,0x73,0x74,0x77,0x79,0x74,0x68", recipeConfig: [ { "op": "To Hex", "args": [ "0x with comma", 0 ] } ] }, { name: "ASCII to Hex with percent deliminator", input: "aberystwyth", expectedOutput: "%61%62%65%72%79%73%74%77%79%74%68", recipeConfig: [ { "op": "To Hex", "args": [ "Percent", 0 ] } ] }, { name: "ASCII to 0x Hex with comma and line breaks", input: "aberystwyth", expectedOutput: "0x61,0x62,0x65,0x72,\n0x79,0x73,0x74,0x77,\n0x79,0x74,0x68", recipeConfig: [ { "op": "To Hex", "args": [ "0x with comma", 4 ] } ] }, { name: "Hex stream to UTF-8", input: "e69591e69591e5ada9e5ad90", expectedOutput: "救救孩子", recipeConfig: [ { "op": "From Hex", "args": [ "Auto" ] } ] }, { name: "Multiline 0x hex to ASCII", input: "0x49,0x20,0x73,0x61,0x77,0x20,0x6d,0x79,0x73,0x65,0x6c,0x66,0x20,0x73,0x69,\ 0x74,0x74,0x69,0x6e,0x67,0x20,0x69,0x6e,0x20,0x74,0x68,0x65,0x20,0x63,0x72,\ 0x6f,0x74,0x63,0x68,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x74,0x68,0x69,\ 0x73,0x20,0x66,0x69,0x67,0x20,0x74,0x72,0x65,0x65,0x2c,0x20,0x73,0x74,0x61,\ 0x72,0x76,0x69,0x6e,0x67,0x20,0x74,0x6f,0x20,0x64,0x65,0x61,0x74,0x68,0x2c,\ 0x20,0x6a,0x75,0x73,0x74,0x20,0x62,0x65,0x63,0x61,0x75,0x73,0x65,0x20,0x49,\ 0x20,0x63,0x6f,0x75,0x6c,0x64,0x6e,0x27,0x74,0x20,0x6d,0x61,0x6b,0x65,0x20,\ 0x75,0x70,0x20,0x6d,0x79,0x20,0x6d,0x69,0x6e,0x64,0x20,0x77,0x68,0x69,0x63,\ 0x68,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x66,0x69,0x67,0x73,0x20,0x49,\ 0x20,0x77,0x6f,0x75,0x6c,0x64,0x20,0x63,0x68,0x6f,0x6f,0x73,0x65,0x2e", 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.", recipeConfig: [ { "op": "From Hex", "args": [ "Auto" ] } ] }, { name: "0x with Comma to Ascii", input: "0x74,0x65,0x73,0x74,0x20,0x73,0x74,0x72,0x69,0x6e,0x67", expectedOutput: "test string", recipeConfig: [ { "op": "From Hex", "args": [ "0x with comma" ] } ] }, ]); ================================================ FILE: tests/operations/tests/Hexdump.mjs ================================================ /** * Hexdump tests. * * @author n1474335 [n1474335@gmail.com] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const ALL_BYTES = [ "\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", ].join(""); TestRegister.addTests([ { name: "Hexdump: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Hexdump", args: [16, false, false] }, { op: "From Hexdump", args: [] } ], }, { name: "Hexdump: Hello, World!", input: "Hello, World!", expectedOutput: "Hello, World!", recipeConfig: [ { op: "To Hexdump", args: [16, false, false] }, { op: "From Hexdump", args: [] } ], }, { name: "Hexdump: UTF-8", input: "ნუ პანიკას", expectedOutput: "ნუ პანიკას", recipeConfig: [ { op: "To Hexdump", args: [16, false, false] }, { op: "From Hexdump", args: [] } ], }, { name: "Hexdump: All bytes", input: ALL_BYTES, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "To Hexdump", args: [16, false, false] }, { op: "From Hexdump", args: [] } ], }, { name: "To Hexdump: UTF-8", input: "ნუ პანიკას", expectedOutput: `00000000 e1 83 9c e1 83 a3 20 e1 83 9e e1 83 90 e1 83 9c |á..á.£ á..á..á..| 00000010 e1 83 98 e1 83 99 e1 83 90 e1 83 a1 |á..á..á..á.¡|`, recipeConfig: [ { op: "To Hexdump", args: [16, false, false] } ], }, { name: "To Hexdump: All bytes", input: ALL_BYTES, expectedOutput: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................| 00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................| 00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./| 00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?| 00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO| 00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\\]^_| 00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |\`abcdefghijklmno| 00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.| 00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f |................| 00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f |................| 000000a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af |\xa0¡¢£¤¥¦§¨©ª«¬.®¯| 000000b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf |°±²³´µ¶·¸¹º»¼½¾¿| 000000c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ| 000000d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df |ÐÑÒÓÔÕÖרÙÚÛÜÝÞß| 000000e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef |àáâãäåæçèéêëìíîï| 000000f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff |ðñòóôõö÷øùúûüýþÿ|`, recipeConfig: [ { op: "To Hexdump", args: [16, false, false] } ], }, { name: "From Hexdump: xxd", input: `00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f ................ 00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f ................ 00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f !"#$%&'()*+,-./ 00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f 0123456789:;<=>? 00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f @ABCDEFGHIJKLMNO 00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f PQRSTUVWXYZ[\\]^_ 00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f \`abcdefghijklmno 00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f pqrstuvwxyz{|}~. 00000080: 8081 8283 8485 8687 8889 8a8b 8c8d 8e8f ................ 00000090: 9091 9293 9495 9697 9899 9a9b 9c9d 9e9f ................ 000000a0: a0a1 a2a3 a4a5 a6a7 a8a9 aaab acad aeaf ................ 000000b0: b0b1 b2b3 b4b5 b6b7 b8b9 babb bcbd bebf ................ 000000c0: c0c1 c2c3 c4c5 c6c7 c8c9 cacb cccd cecf ................ 000000d0: d0d1 d2d3 d4d5 d6d7 d8d9 dadb dcdd dedf ................ 000000e0: e0e1 e2e3 e4e5 e6e7 e8e9 eaeb eced eeef ................ 000000f0: f0f1 f2f3 f4f5 f6f7 f8f9 fafb fcfd feff ................`, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "From Hexdump", args: [] } ], }, { name: "From Hexdump: xxd format, odd number of bytes", input: "00000000: 6162 6364 65 abcde", expectedOutput: "abcde", recipeConfig: [ { op: "From Hexdump", args: [] } ], }, { name: "From Hexdump: Wireshark", input: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ........ ........ 00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ........ ........ 00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&' ()*+,-./ 00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 01234567 89:;<=>? 00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFG HIJKLMNO 00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVW XYZ[\\]^_ 00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f \`abcdefg hijklmno 00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvw xyz{|}~. 00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f ........ ........ 00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f ........ ........ 000000A0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af ........ ........ 000000B0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf ........ ........ 000000C0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf ........ ........ 000000D0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df ........ ........ 000000E0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef ........ ........ 000000F0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff ........ ........ `, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "From Hexdump", args: [] } ], }, { name: "From Hexdump: Wireshark alt", input: `0000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 0010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 0020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 0030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 0050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 0060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 0070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 0080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 0090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f 00a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af 00b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf 00c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf 00d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df 00e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef 00f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff`, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "From Hexdump", args: [] } ], }, { name: "From Hexdump: 010", input: `0000h: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ................ 0010h: 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ................ 0020h: 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F !"#$%&'()*+,-./ 0030h: 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 0123456789:;<=>? 0040h: 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F @ABCDEFGHIJKLMNO 0050h: 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F PQRSTUVWXYZ[\\]^_ 0060h: 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F \`abcdefghijklmno 0070h: 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F pqrstuvwxyz{|}~ 0080h: 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F €.‚ƒ„…†‡ˆ‰Š‹Œ... 0090h: 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F .‘’“”•–—˜™š›œ.žŸ 00A0h: A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF \xa0¡¢£¤¥¦§¨©ª«¬­®¯ 00B0h: B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF °±²³´µ¶·¸¹º»¼½¾¿ 00C0h: C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ 00D0h: D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF ÐÑÒÓÔÕÖרÙÚÛÜÝÞß 00E0h: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF àáâãäåæçèéêëìíîï 00F0h: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ðñòóôõö÷øùúûüýþÿ`, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "From Hexdump", args: [] } ], }, { name: "From Hexdump: Linux hexdump", input: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................| 00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................| 00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./| 00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?| 00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO| 00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\\]^_| 00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |\`abcdefghijklmno| 00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.| 00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f |................| 00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f |................| 000000a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af |................| 000000b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf |................| 000000c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |................| 000000d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df |................| 000000e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef |................| 000000f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff |................| 00000100`, expectedOutput: ALL_BYTES, recipeConfig: [ { op: "From Hexdump", args: [] } ], }, ]); ================================================ FILE: tests/operations/tests/IPv6Transition.mjs ================================================ /** * IPv6Transition tests. * * @author jb30795 * * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "IPv6 Transition: IPv4 to IPv6", input: "198.51.100.7", expectedOutput: "6to4: 2002:c633:6407::/48\nIPv4 Mapped: ::ffff:c633:6407\nIPv4 Translated: ::ffff:0:c633:6407\nNat 64: 64:ff9b::c633:6407", recipeConfig: [ { op: "IPv6 Transition Addresses", args: [true, false], }, ], }, { name: "IPv6 Transition: IPv4 /24 Range to IPv6", input: "198.51.100.0/24", 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", recipeConfig: [ { op: "IPv6 Transition Addresses", args: [false, false], }, ], }, { name: "IPv6 Transition: IPv4 to IPv6 Remove headers", input: "198.51.100.7", expectedOutput: "2002:c633:6407::/48\n::ffff:c633:6407\n::ffff:0:c633:6407\n64:ff9b::c633:6407", recipeConfig: [ { op: "IPv6 Transition Addresses", args: [true, true], }, ], }, { name: "IPv6 Transition: IPv6 to IPv4", input: "64:ff9b::c633:6407", expectedOutput: "IPv4: 198.51.100.7", recipeConfig: [ { op: "IPv6 Transition Addresses", args: [true, false], }, ], }, { name: "IPv6 Transition: MAC to EUI-64", input: "a1:b2:c3:d4:e5:f6", expectedOutput: "EUI-64 Interface ID: a3b2:c3ff:fed4:e5f6", recipeConfig: [ { op: "IPv6 Transition Addresses", args: [true, false], }, ], }, ]); ================================================ FILE: tests/operations/tests/Image.mjs ================================================ /** * Image operation tests. * * @author tlwr [toby@toby.codes] * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] * @author n1474335 [n1474335@gmail.com] * * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; import { GIF_ANIMATED_HEX, PNG_HEX, JPG_B64, EXIF_JPG_HEX, NO_EXIF_JPG_HEX } from "../../samples/Images.mjs"; TestRegister.addTests([ { name: "Render Image: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "Render Image", args: ["Raw"] } ] }, { name: "Render Image: raw gif", input: GIF_ANIMATED_HEX, expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["Space"] }, { op: "Render Image", args: ["Raw"] } ] }, { name: "Render Image: hex png", input: PNG_HEX, expectedOutput: "", recipeConfig: [ { op: "Render Image", args: ["Hex"] } ] }, { name: "Render Image: base64 jpg", input: JPG_B64, expectedOutput: "", recipeConfig: [ { op: "Render Image", args: ["Base64"] } ] }, { name: "Extract EXIF: nothing", input: "", expectedOutput: "Found 0 tags.\n", recipeConfig: [ { op: "Extract EXIF", args: [], }, ], }, { name: "Extract EXIF: hello world text (error)", input: "hello world", expectedOutput: "Could not extract EXIF data from image: Error: Invalid JPEG section offset", recipeConfig: [ { op: "Extract EXIF", args: [], }, ], }, { name: "Extract EXIF: meerkat jpeg", input: EXIF_JPG_HEX, expectedOutput: [ "Found 28 tags.", "", "Make: SONY", "Model: DSC-H5", "XResolution: 70", "YResolution: 70", "ResolutionUnit: 2", "Software: Pictomio 1.2.31.0", "ModifyDate: 1278286273", "ExposureTime: 0.008", "FNumber: 3.7", "ExposureProgram: 3", "ISO: 200", "DateTimeOriginal: 1220275486", "CreateDate: 1220275486", "ShutterSpeedValue: 6.965784", "ApertureValue: 3.775051", "ExposureCompensation: 0.3", "MaxApertureValue: 3", "MeteringMode: 5", "LightSource: 10", "Flash: 16", "FocalLength: 72", "CustomRendered: 0", "ExposureMode: 1", "WhiteBalance: 1", "SceneCaptureType: 0", "Contrast: 0", "Saturation: 0", "Sharpness: 0", ].join("\n"), recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract EXIF", args: [], }, ], }, { name: "Extract EXIF: avatar jpeg", input: NO_EXIF_JPG_HEX, expectedOutput: "Found 0 tags.\n", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract EXIF", args: [], }, ], }, { name: "Remove EXIF: hello world text (error)", input: "hello world", expectedOutput: "Could not remove EXIF data from image: Given data is not jpeg.", recipeConfig: [ { op: "Remove EXIF", args: [], }, ], }, { name: "Remove EXIF: meerkat jpeg (has EXIF)", input: EXIF_JPG_HEX, expectedOutput: "Found 0 tags.\n", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Remove EXIF", args: [], }, { op: "Extract EXIF", args: [], }, ], }, { name: "Extract EXIF: avatar jpeg (has no EXIF)", input: NO_EXIF_JPG_HEX, expectedOutput: "Found 0 tags.\n", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Remove EXIF", args: [], }, { op: "Extract EXIF", args: [], }, ], }, { name: "Extract RGBA", input: "424d460400000000000036040000280000000400000004000000010008000000000010000000120b0000120b0000000100000001000000c8000000cf000000d7000000df000000e7000000ef000000f7000000ff000083000000ac000000d5000000ff000000000083000000ac000000d5000000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f070d05030b01090c040e060008020a", 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", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract RGBA", args: [" ", false] } ] }, { name: "Extract LSB", input: PNG_HEX, expectedOutput: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000208000000000000000000008000000000000000000000000248000000200240000000208908000000200240000000200821000000200240000000061249000000240000000000209b69000001a49b00000000a204a1200001a49b00000009800414000001a49b0000000035db6c00000094924000000086dffc20000df6dec8000001e10014a0000df6dec800002564924b00000df6dec80000009a6db20000007edb4124804177fffba0002fffff69249044e0924bc4002fffff6924905fb2db6d04002fffff692490416d2490040001bfffcc92030dbffffdc00037fffffdb6d302c6db6d700037fffffdb6d327eb6db6148037fffffdb6d30db4000014800dffffeb6d9aefffffff640", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Extract LSB", args: ["B", "G", "A", "", "Column", 2] }, { op: "To Hex", args: ["None"] } ] }, { name: "View Bit Plane", input: PNG_HEX, expectedOutput: "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000140494441547801c5c1416ea3400000c1ee11ffff726fe6808410186ce26c95fde0432a154f0cdea4b2aa505151519954ee1a5c50995454aea8ac54ae2c5ca8982a3ea132551c199c507942e58ec1898adf50f1cae084ca15952b2a152a47067f40a5e2c8e00f54a81c199ca8b85271a542a5e2c8e005159527542ace0c5ea8a8f844c54ae5ccc217555c197c41c55d83ff6cf0052a772ddca052b1a752b1a772d7c2432a4f2c3c50f1d4c20b2a1593ca918a4965afe2cac29b2a562a93ca56c55d0b2754b62a269555c554b15251a9b8637040e5884ac54a654ba5a2624be5cce040c5918a55c55ec5a4a232a9549c197c48655239523155bc3278a862af624be5ccc2072aaea854a8549c5978834a85ca5ec5918a57ec076f50a958a9546ca94c1557ec071754a68a2d958a270637544c2a2abf69e1a68a95ca54b152a978d73f2e08bd57b6f839a00000000049454e44ae426082", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "View Bit Plane", args: ["Green", 3] }, { op: "To Hex", args: ["None"] } ] }, { name: "Randomize Colour Palette", "input": PNG_HEX, expectedOutput: "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af4000004f3494441547801c5c10b50cf0700c0f16fbfff8f0ae5f157ed9f1e446358ab28b3913f29b3f89f65deab9859c624cafbf557b6eb3662799c8a285c541479ed3c125d2d2cba52e45d4da8bc66b99d1ebbff6ddd75ddeff7efef75fb7c8c5cb5b10dbc869ccbd1ac4f0c67dfc55cba1f9ec32dd303bc0e854aadd1f20ace5f29e1c9346b4c3982aada03b3285b540f4b099f664ca5720321a34c582466b2f38f3e1842a1526bb4e811efe64a56403a465dc270a998cb517b47e62f7842870b6aaabc5b519627a253784e606a8e25470fbbe358a0c2fd692f9c9668b8bda084e77d5f2247448f790f42a9b9da915d415a6020508a3e49e207e8ec7961cb9594545657e5e26e0fdaf9df5014998a141119f16eae24b4db84f7d775bc8e3e3e9e24f12fabaccf282210292232ce6a5e12dab58e37656be7cf8893916498d72345404681d7365ae217b18b969495261019998f8569145204e47c3e99b7a58f8f278fa76e478a808c522f23de165b3b7f3aee9c8e14011956e9b6841c29419f5d8bfd6889ad9d3fc1c9c654be08428a808cfc9a0c02e7d711973a823751569ac0c79dd72347408fb9ee3d102f4c616d9c82579596dd8df1b5c58caf2d26625d2fe488b420b16c129e3d7825534ccbb01a3881f0803354adba49c5853ce488bc037ba66b48c93586e20298885e02ff338177c0a1ed600c256200973699b0f130cd9dafcd20c87a3bcd9d29d90b1462081103c4d444d1bee2139aab9b7c87ec50479adbd47f13a0c610227a94af8a6565ce118ce72dc1618d3b4ded6f678465840dd905bd519f3e455305e67eb4f1db8ed94fdeb444448f0dbb94740bcec029db9fa636fc99cbf176a708b9e44198af3573469b323cb992469694b031bd133b2a955cb2a8461f0119a3b6f810fcac81619ea1c42cbf43a3711393d8727b1ee37ad6a263b1388cc8a872264cc8a7d1c97116d407ede1ab9c44ac3de610e5f32372142ab5468b84d091d984d79a90147e8dfb8322e8bcf71cdf370ce07ae6158606ee479c164c8794ddf88f76664c5d3d96579ee171db85d9917524b996102e2e65d882753c4c28e690714f9e3ffa1b2902122a0e6571a3c8864657b304ee2e4d6579e1038e0879cc0c716765c67ec2ee1d63cbaa9154db0760ffcc8e0fd7f643d3ba9c31ce69fc3c68124507bae060eb8379dc45e4884850aa4730ca2e97e68a572ca178055c7ff411cb56c4002bf86ef6717c53f94f3c3a4ff72590d6fa09ef134ae0a983ec1efc2b720424fc55b8107dbea8fe81134ecee8687c4d68ae62de45bc636ad099782b126b8f39c81190b0f292127d5295cb98f2b415aa2a055dcb2fb035b081a686f48ee5f4d37ee80c9d91498855007204240cc9ea89efeabec871ec5443c381b1980c9a4f456d1233a38d68ce667c2ee7c2927019e1ce8bb84ce48848f832d10bb1f50dd6540d03d268d4677d39ed1d52514407d3b6d8830327dc49b0984574507b3a3b0573fb5806f762afa37379790e7783ab0849ee0f66fd91a350a9355a24d4bb754239ca02df597598d52dc5c2bb339503cde95213cff3a089240d7f48fd522f9c943eec1db088e53b0412631ea193dec188e4b3a7d9d6eb535a62e4aa8d6da005bfe4d9125de082dbcd6f69ca6ecc66c2166ca329eb198ee4bf5780a5ba3b861030c05cd732eea4acc3c42106671f0d52ac6738727064325b8ff5c752dd1d4329546a8d1603bc2c51f2bbf3356eae2c64b379374c964561b5f53c36d65e14fd3683c7f7179232d686bdf9777915ff00ec08ae8ecb66a3370000000049454e44ae426082", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Randomize Colour Palette", args: ["myseed"] }, { op: "To Hex", args: ["None"] } ] }, /* { This operation only works in a browser name: "Optical Character Recognition", 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", expectedOutput: "Tesseract.js\n", recipeConfig: [ { "op": "From Base64", "args": ["A-Za-z0-9+/=", true] }, { "op": "Optical Character Recognition", "args": [false] } ] } */ ]); ================================================ FILE: tests/operations/tests/IndexOfCoincidence.mjs ================================================ /** * Index of Coincidence tests. * * @author George O [georgeomnet+cyberchef@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Index of Coincidence", input: "Hello world, this is a test to determine the correct IC value.", expectedMatch: /^Index of Coincidence: 0\.07142857142857142\nNormalized: 1\.857142857142857/, recipeConfig: [ { "op": "Index of Coincidence", "args": [] }, ], }, ]); ================================================ FILE: tests/operations/tests/JA3Fingerprint.mjs ================================================ /** * JA3Fingerprint tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "JA3 Fingerprint: TLS 1.0", input: "16030100a4010000a00301543dd2dd48f517ca9a93b1e599f019fdece704a23e86c1dcac588427abbaddf200005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101", expectedOutput: "503053a0c5b2bd9b9334bf7f3d3b8852", recipeConfig: [ { "op": "JA3 Fingerprint", "args": ["Hex", "Hash digest"] } ], }, { name: "JA3 Fingerprint: TLS 1.1", input: "16030100a4010000a00302543dd2ed907e47d0086f34bee2c52dd6ccd8de63ba9387f5e810b09d9d49b38000005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101", expectedOutput: "a314eb64cee6cb832aaaa372c8295bab", recipeConfig: [ { "op": "JA3 Fingerprint", "args": ["Hex", "Hash digest"] } ], }, { name: "JA3 Fingerprint: TLS 1.2", input: "1603010102010000fe0303543dd3283283692d85f9416b5ccc65d2aafca45c6530b3c6eafbf6d371b6a015000094c030c02cc028c024c014c00a00a3009f006b006a0039003800880087c032c02ec02ac026c00fc005009d003d00350084c012c00800160013c00dc003000ac02fc02bc027c023c013c00900a2009e0067004000330032009a009900450044c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff01000041000b000403000102000a000600040018001700230000000d002200200601060206030501050205030401040204030301030203030201020202030101000f000101", expectedOutput: "c1a36e1a870786cc75edddc0009eaf3a", recipeConfig: [ { "op": "JA3 Fingerprint", "args": ["Hex", "Hash digest"] } ], }, { name: "JA3 Fingerprint: TLS 1.3", input: "1603010200010001fc03034355d402c132771a9386b6e9994ae37069e0621af504c26673b1343843c21d8d0000264a4a130113021303c02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010001addada0000ff01000100000000180016000013626c6f672e636c6f7564666c6172652e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b000201000028002b00295a5a000100001d0020cf78b9167af054b922a96752b43973107b2a57766357dd288b2b42ab5df30e08002d00020101002b000b0acaca7f12030303020301000a000a00085a5a001d001700180a0a000100001500e4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "4826a90ec2daf4f7b4b64cc1c8bd343b", recipeConfig: [ { "op": "JA3 Fingerprint", "args": ["Hex", "Hash digest"] } ], }, ]); ================================================ FILE: tests/operations/tests/JA3SFingerprint.mjs ================================================ /** * JA3SFingerprint tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "JA3S Fingerprint: TLS 1.0", input: "160301003d020000390301543dd2ddedbfe33895bd6bc676a3fa6b9fe5773a6e04d5476d1af3bcbc1dcbbb00c011000011ff01000100000b00040300010200230000", expectedOutput: "bed95e1b525d2f41db3a6d68fac5b566", recipeConfig: [ { "op": "JA3S Fingerprint", "args": ["Hex", "Hash digest"] } ], }, { name: "JA3S Fingerprint: TLS 1.1", input: "160302003d020000390302543dd2ed88131999a0120d36c14a4139671d75aae3d7d7779081d3cf7dd7725a00c013000011ff01000100000b00040300010200230000", expectedOutput: "130fac2dc19b142500acb0abc63b6379", recipeConfig: [ { "op": "JA3S Fingerprint", "args": ["Hex", "Hash digest"] } ], }, { name: "JA3S Fingerprint: TLS 1.2", input: "160303003d020000390303543dd328b38b445686739d58fab733fa23838f575e0e5ad9a1b9baace6cc3b4100c02f000011ff01000100000b00040300010200230000", expectedOutput: "ccc514751b175866924439bdbb5bba34", recipeConfig: [ { "op": "JA3S Fingerprint", "args": ["Hex", "Hash digest"] } ], }, // 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. // The published version of TLS1.3 does require a legacy Session ID field (even if it is empty). // { // name: "JA3S Fingerprint: TLS 1.3", // input: "16030100520200004e7f123ef1609fd3f4fa8668aac5822d500fb0639b22671d0fb7258597355795511bf61301002800280024001d0020ae0e282a3b7a463e71064ecbaf671586e979b0edbebf7a4735c31678c70f660c", // expectedOutput: "986ae432c402479fe7a0c6fbe02164c1", // recipeConfig: [ // { // "op": "JA3S Fingerprint", // "args": ["Hex", "Hash digest"] // } // ], // }, ]); ================================================ FILE: tests/operations/tests/JA4.mjs ================================================ /** * JA4 tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "JA4 Fingerprint: TLS 1.3", input: "1603010200010001fc0303b2c03e7ba990ef540c316a665d4d925f8e9079ac4b15687e587dc99016e75a6c20d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f00205a5a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193fafa000000000024002200001f636f6e74656e742d6175746f66696c6c2e676f6f676c65617069732e636f6d0033002b00293a3a000100001d0020fb2cd8ef3d605b96ab03119ec4f30a6e2088cb1af86c41a81feace8706068c50000d001200100403080404010503080505010806060100230000000b00020100ff01000100000a000a00083a3a001d00170018001b000302000244690005000302683200120000002d000201010010000e000c02683208687474702f312e31000500050100000000002b0007060a0a03040303001700001a1a000100001500b800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "t13d1516h2_8daaf6152771_e5627efa2ab1", recipeConfig: [ { "op": "JA4 Fingerprint", "args": ["Hex", "JA4"] } ], }, { name: "JA4 Fingerprint: TLS 1.3 Original Rendering", input: "1603010200010001fc0303b2c03e7ba990ef540c316a665d4d925f8e9079ac4b15687e587dc99016e75a6c20d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f00205a5a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193fafa000000000024002200001f636f6e74656e742d6175746f66696c6c2e676f6f676c65617069732e636f6d0033002b00293a3a000100001d0020fb2cd8ef3d605b96ab03119ec4f30a6e2088cb1af86c41a81feace8706068c50000d001200100403080404010503080505010806060100230000000b00020100ff01000100000a000a00083a3a001d00170018001b000302000244690005000302683200120000002d000201010010000e000c02683208687474702f312e31000500050100000000002b0007060a0a03040303001700001a1a000100001500b800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "t13d1516h2_acb858a92679_5276cb03a33b", recipeConfig: [ { "op": "JA4 Fingerprint", "args": ["Hex", "JA4 Original Rendering"] } ], }, { name: "JA4 Fingerprint: TLS 1.3 with whitespace-only ALPN", input: "1603010200010001fc0303ed338a18e711d670cdc472ff570a5b59f1ace12e5365918bf68bf845019147b6207e4437bfb062d98a4aeb753be8f09022a9dc9413d7694dad4db57fcdcf076e820024130213031301c02cc030c02bc02fcca9cca8c024c028c023c027009f009e006b006700ff0100018f0000001800160000136465762e636f6e74656e74677261622e6e6574000b000403000102000a00160014001d0017001e00190018010001010102010301040023000000100004000201200016000000170000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b00050403040303002d00020101003300260024001d00207af053336d5e2c1675aa4c6ce78de5e5fdbd296538113f051ea17ccb64289f22001500d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "t13d181220_85036bcba153_d41ae481755e", recipeConfig: [ { "op": "JA4 Fingerprint", "args": ["Hex", "JA4"] } ], }, { name: "JA4 Fingerprint: TLS 1.3 with ALPN containing a whitespace", input: "1603010200010001fc0303273682a603be3f64dd025df4ad0f4d2d13043c3a233405a68bb29b865808749a20f4dfc40242b2fce38fae26c516ef9bef20a1b9349eba3c003780168d72471f5c0024130213031301c02cc030c02bc02fcca9cca8c024c028c023c027009f009e006b006700ff0100018f0000001800160000136465762e636f6e74656e74677261622e6e6574000b000403000102000a00160014001d0017001e0019001801000101010201030104002300000010000500030261200016000000170000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b00050403040303002d00020101003300260024001d0020f4dd1567bd858d3a9f1d88db1fee6a10ab0ea1aa6afe96ffb6a7c4d79dea4075001500d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "t13d181260_85036bcba153_d41ae481755e", recipeConfig: [ { "op": "JA4 Fingerprint", "args": ["Hex", "JA4"] } ], }, { name: "JA4 Fingerprint: TLS 1.2", input: "1603010200010001fc0303ecb2691addb2bf6c599c7aaae23de5f42561cc04eb41029acc6fc050a16ac1d22046f8617b580ac9358e2aa44e306d52466bcc989c87c8ca64309f5faf50ba7b4d0022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f00350100019100000021001f00001c636f6e74696c652e73657276696365732e6d6f7a696c6c612e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d00208909858fbeb6ed2f1248ba5b9e2978bead0e840110192c61daed0096798b184400170041044d183d91f5eed35791fa982464e3b0214aaa5f5d1b78616d9b9fbebc22d11f535b2f94c686143136aa795e6e5a875d6c08064ad5b76d44caad766e2483012748002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c000240010015007a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "t13d1715h2_5b57614c22b0_3d5424432f57", recipeConfig: [ { "op": "JA4 Fingerprint", "args": ["Hex", "JA4"] } ], }, { name: "JA4 Fingerprint: TLS 1.2 Original Rendering", input: "1603010200010001fc0303ecb2691addb2bf6c599c7aaae23de5f42561cc04eb41029acc6fc050a16ac1d22046f8617b580ac9358e2aa44e306d52466bcc989c87c8ca64309f5faf50ba7b4d0022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f00350100019100000021001f00001c636f6e74696c652e73657276696365732e6d6f7a696c6c612e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d00208909858fbeb6ed2f1248ba5b9e2978bead0e840110192c61daed0096798b184400170041044d183d91f5eed35791fa982464e3b0214aaa5f5d1b78616d9b9fbebc22d11f535b2f94c686143136aa795e6e5a875d6c08064ad5b76d44caad766e2483012748002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c000240010015007a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "t13d1715h2_5b234860e130_014157ec0da2", recipeConfig: [ { "op": "JA4 Fingerprint", "args": ["Hex", "JA4 Original Rendering"] } ], }, { name: "JA4Server Fingerprint: TLS 1.2 h2 ALPN", input: "16030300640200006003035f0236c07f47bfb12dc2da706ecb3fe7f9eeac9968cc2ddf444f574e4752440120b89ff1ab695278c69b8a73f76242ef755e0b13dc6d459aaaa784fec9c2dfce34cca900001800000000ff01000100000b00020100001000050003026832", expectedOutput: "t1204h2_cca9_1428ce7b4018", recipeConfig: [ { "op": "JA4Server Fingerprint", "args": ["Hex", "JA4S"] } ] }, { name: "JA4Server Fingerprint: TLS 1.2 h2 ALPN Raw", input: "16030300640200006003035f0236c07f47bfb12dc2da706ecb3fe7f9eeac9968cc2ddf444f574e4752440120b89ff1ab695278c69b8a73f76242ef755e0b13dc6d459aaaa784fec9c2dfce34cca900001800000000ff01000100000b00020100001000050003026832", expectedOutput: "t1204h2_cca9_0000,ff01,000b,0010", recipeConfig: [ { "op": "JA4Server Fingerprint", "args": ["Hex", "JA4S Raw"] } ] }, { name: "JA4Server Fingerprint: TLS 1.3", input: "160303007a020000760303236d214556452c55a0754487e64b1a8b0262c50ba23004c9d504166a6de3439920d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f130100002e00330024001d002099e3cc43a2c9941ae75af1b2c7a629bee3ee7031973cad85c82f2f23677fb244002b00020304", expectedOutput: "t130200_1301_234ea6891581", recipeConfig: [ { "op": "JA4Server Fingerprint", "args": ["Hex", "JA4S"] } ] }, { name: "JA4Server Fingerprint: TLS 1.3 Raw", input: "160303007a020000760303236d214556452c55a0754487e64b1a8b0262c50ba23004c9d504166a6de3439920d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f130100002e00330024001d002099e3cc43a2c9941ae75af1b2c7a629bee3ee7031973cad85c82f2f23677fb244002b00020304", expectedOutput: "t130200_1301_0033,002b", recipeConfig: [ { "op": "JA4Server Fingerprint", "args": ["Hex", "JA4S Raw"] } ] }, { name: "JA4Server Fingerprint: TLS 1.3 non-ascii ALPN", input: "160303007a020000760303897c232e3ee313314f2b662307ff4f7e2cf1caeec1b27711bca77f469519168520bc58b92f865e6b9aa4a6371cadcb0afe1da1c0f705209a11d52357f56d5dd962130100002e00330024001d002076b8b7ed0f96b63a773d85ab6f3a87a151c130529785b41a4defb53184055957002b00020304", expectedOutput: "t130200_1301_234ea6891581", recipeConfig: [ { "op": "JA4Server Fingerprint", "args": ["Hex", "JA4S"] } ] }, { name: "JA4Server Fingerprint: TLS 1.3 non-ascii ALPN Raw", input: "160303007a020000760303897c232e3ee313314f2b662307ff4f7e2cf1caeec1b27711bca77f469519168520bc58b92f865e6b9aa4a6371cadcb0afe1da1c0f705209a11d52357f56d5dd962130100002e00330024001d002076b8b7ed0f96b63a773d85ab6f3a87a151c130529785b41a4defb53184055957002b00020304", expectedOutput: "t130200_1301_0033,002b", recipeConfig: [ { "op": "JA4Server Fingerprint", "args": ["Hex", "JA4S Raw"] } ] }, ]); ================================================ FILE: tests/operations/tests/JSONBeautify.mjs ================================================ /** * JSONBeautify tests. * * @author Phillip Nordwall [Phillip.Nordwall@gmail.com] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "JSON Beautify: space, ''", input: "", expectedOutput: "", recipeConfig: [ { op: "JSON Beautify", args: [" ", false, false], }, ], }, { name: "JSON Beautify: space, number", input: "42", expectedOutput: "42", recipeConfig: [ { op: "JSON Beautify", args: [" ", false, false], }, ], }, { name: "JSON Beautify: space, string", input: "\"string\"", expectedOutput: "\"string\"", recipeConfig: [ { op: "JSON Beautify", args: [" ", false, false], }, { op: "HTML To Text", args: [] } ], }, { name: "JSON Beautify: space, boolean", input: "false", expectedOutput: "false", recipeConfig: [ { op: "JSON Beautify", args: [" ", false, false], }, ], }, { name: "JSON Beautify: space, emptyList", input: "[]", expectedOutput: "[]", recipeConfig: [ { op: "JSON Beautify", args: [" ", false, false], }, ], }, { name: "JSON Beautify: space, list", input: "[2,1]", expectedOutput: "[\n 2,\n 1\n]", recipeConfig: [ { op: "JSON Beautify", args: [" ", false, false], }, ], }, { name: "JSON Beautify: tab, list", input: "[2,1]", expectedOutput: "[\n\t2,\n\t1\n]", recipeConfig: [ { op: "JSON Beautify", args: ["\t", false, false], }, ], }, { name: "JSON Beautify: space, object", input: "{\"second\":2,\"first\":3}", expectedOutput: "{\n \"second\": 2,\n \"first\": 3\n}", recipeConfig: [ { op: "JSON Beautify", args: [" ", false, false], }, { op: "HTML To Text", args: [] } ], }, { name: "JSON Beautify: tab, nested", input: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]", 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]", recipeConfig: [ { op: "JSON Beautify", args: ["\t", false, false], }, { op: "HTML To Text", args: [] } ], }, { name: "JSON Beautify: tab, nested, sorted", input: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]", 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]", recipeConfig: [ { op: "JSON Beautify", args: ["\t", true, false], }, { op: "HTML To Text", args: [] } ], }, ]); ================================================ FILE: tests/operations/tests/JSONMinify.mjs ================================================ /** * JSONMinify tests. * * @author Phillip Nordwall [Phillip.Nordwall@gmail.com] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "JSON Minify: ''", input: "", expectedOutput: "", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, { name: "JSON Minify: number", input: "42", expectedOutput: "42", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, { name: "JSON Minify: number", input: "4.2", expectedOutput: "4.2", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, { name: "JSON Minify: string", input: "\"string\"", expectedOutput: "\"string\"", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, { name: "JSON Minify: boolean", input: "false", expectedOutput: "false", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, { name: "JSON Minify: emptyList", input: "[\n \n \t]", expectedOutput: "[]", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, { name: "JSON Minify: list", input: "[2,\n \t1]", expectedOutput: "[2,1]", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, { name: "JSON Minify: object", input: "{\n \"second\": 2,\n \"first\": 3\n}", expectedOutput: "{\"second\":2,\"first\":3}", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, { name: "JSON Minify: tab, nested", 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]", expectedOutput: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]", recipeConfig: [ { op: "JSON Minify", args: [], }, ], }, ]); ================================================ FILE: tests/operations/tests/JSONtoCSV.mjs ================================================ /** * JSON to CSV tests. * * @author mshwed [m@ttshwed.com] * * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const EXPECTED_CSV_SINGLE = "a,b,c\r\n1,2,3\r\n"; const EXPECTED_CSV_MULTIPLE = "a,b,c\r\n1,2,3\r\n1,2,3\r\n"; const EXPECTED_CSV_EMPTY = "\r\n\r\n"; TestRegister.addTests([ { name: "JSON to CSV: strings as values", input: JSON.stringify({a: "1", b: "2", c: "3"}), expectedOutput: EXPECTED_CSV_SINGLE, recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: numbers as values", input: JSON.stringify({a: 1, b: 2, c: 3}), expectedOutput: EXPECTED_CSV_SINGLE, recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: numbers and strings as values", input: JSON.stringify({a: 1, b: "2", c: 3}), expectedOutput: EXPECTED_CSV_SINGLE, recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: boolean and null as values", input: JSON.stringify({a: false, b: null, c: 3}), expectedOutput: "a,b,c\r\nfalse,null,3\r\n", recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: JSON as an array", input: JSON.stringify([{a: 1, b: "2", c: 3}]), expectedOutput: EXPECTED_CSV_SINGLE, recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: multiple JSON values in an array", input: JSON.stringify([{a: 1, b: "2", c: 3}, {a: 1, b: "2", c: 3}]), expectedOutput: EXPECTED_CSV_MULTIPLE, recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: empty JSON", input: JSON.stringify({}), expectedOutput: EXPECTED_CSV_EMPTY, recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: empty JSON in array", input: JSON.stringify([{}]), expectedOutput: EXPECTED_CSV_EMPTY, recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: nested JSON", input: JSON.stringify({a: 1, b: {c: 2, d: 3}}), expectedOutput: "a,b.c,b.d\r\n1,2,3\r\n", recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: nested array", input: JSON.stringify({a: 1, b: [2, 3]}), expectedOutput: "a,b.0,b.1\r\n1,2,3\r\n", recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: nested JSON, nested array", input: JSON.stringify({a: 1, b: {c: [2, 3], d: 4}}), expectedOutput: "a,b.c.0,b.c.1,b.d\r\n1,2,3,4\r\n", recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: nested array, nested JSON", input: JSON.stringify({a: 1, b: [{c: 3, d: 4}]}), expectedOutput: "a,b.0.c,b.0.d\r\n1,3,4\r\n", recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: nested array, nested array", input: JSON.stringify({a: 1, b: [[2, 3]]}), expectedOutput: "a,b.0.0,b.0.1\r\n1,2,3\r\n", recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], }, { name: "JSON to CSV: nested JSON, nested JSON", input: JSON.stringify({a: 1, b: { c: { d: 2, e: 3}}}), expectedOutput: "a,b.c.d,b.c.e\r\n1,2,3\r\n", recipeConfig: [ { op: "JSON to CSV", args: [",", "\\r\\n"] }, ], } ]); ================================================ FILE: tests/operations/tests/JSONtoYAML.mjs ================================================ /** * YAML tests. * * @author ccarpo [ccarpo@gmx.net] * * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const EXAMPLE_YAML = `number: 3\nplain: string\nblock: |\n two\n lines`; const EXAMPLE_JSON = `{ "number": 3, "plain": "string" }`; TestRegister.addTests([ { name: "YAML to JSON", input: EXAMPLE_YAML, expectedOutput: JSON.stringify({ "number": 3, "plain": "string", "block": "two\nlines\n" }, null, 4), recipeConfig: [ { op: "YAML to JSON", args: [], } ], }, { name: "JSON to YAML", input: EXAMPLE_JSON, expectedOutput: `number: 3\nplain: string\n`, recipeConfig: [ { op: "JSON to YAML", args: [], } ], }, ]); ================================================ FILE: tests/operations/tests/JWK.mjs ================================================ /** * JWK conversion * * @author cplussharp * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; // test data for RSA key pair const RSA_512 = { private: { pem1: `-----BEGIN RSA PRIVATE KEY----- MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p 6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o= -----END RSA PRIVATE KEY-----`, pem8: `-----BEGIN PRIVATE KEY----- MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA8qvQOnph0i3M5+Tp ruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe+e3yy0ys/nS3qOrBZDYSMx 2SPp+wIDAQABAkA4lSkzSW/fowBBHdYDCwUXsM7L4ueKC14K9o1aI1XFnqMHIHlB vuxqWX9CW72wUjRnCuVVwpLSSV157slNiUtZAiEA/dQoR862lOBPW1iZ2FqqPGox /EGibRGL6u31qOXOzq8CIQD0vzkHz8advunoB/v/CmZp8vbvs5XhfjEF/MxH5NTM dQIgGr5UtCQidpMfqVcvdm0vDIh/1b0wmN9FZ65EuNPlsz8CIEXsrDqTHkfLV8C3 J0QCkKgMhoiNYnD+QLtZMnQH6NRRAiBXp3A1LKPR95qR4Pz3SCYh36W+X/D/ehRe JzOq+X832g== -----END PRIVATE KEY-----`, jwk: { "kty": "RSA", "n": "8qvQOnph0i3M5-TpruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe-e3yy0ys_nS3qOrBZDYSMx2SPp-w", "e": "AQAB", "d": "OJUpM0lv36MAQR3WAwsFF7DOy-LnigteCvaNWiNVxZ6jByB5Qb7sall_Qlu9sFI0ZwrlVcKS0kldee7JTYlLWQ", "p": "_dQoR862lOBPW1iZ2FqqPGox_EGibRGL6u31qOXOzq8", "q": "9L85B8_Gnb7p6Af7_wpmafL277OV4X4xBfzMR-TUzHU", "dp": "Gr5UtCQidpMfqVcvdm0vDIh_1b0wmN9FZ65EuNPlsz8", "dq": "ReysOpMeR8tXwLcnRAKQqAyGiI1icP5Au1kydAfo1FE", "qi": "V6dwNSyj0feakeD890gmId-lvl_w_3oUXiczqvl_N9o" } }, public: { pem1: `-----BEGIN RSA PUBLIC KEY----- MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB 757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAE= -----END RSA PUBLIC KEY-----`, pem8: `-----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ== -----END PUBLIC KEY-----`, cert: `-----BEGIN CERTIFICATE----- MIIBfTCCASegAwIBAgIUeisK5Nwss2DGg5PCs4uSxxXyyNkwDQYJKoZIhvcNAQEL BQAwEzERMA8GA1UEAwwIUlNBIHRlc3QwHhcNMjExMTE5MTcyMDI2WhcNMzExMTE3 MTcyMDI2WjATMREwDwYDVQQDDAhSU0EgdGVzdDBcMA0GCSqGSIb3DQEBAQUAA0sA MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB 757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAGjUzBRMB0GA1UdDgQWBBRO+jvkqq5p pnQgwMMnRoun6e7eiTAfBgNVHSMEGDAWgBRO+jvkqq5ppnQgwMMnRoun6e7eiTAP BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAR/5HAZM5qBhU/ezDUIFx gmUGoFbIb5kJD41YCnaSdrgWglh4He4melSs42G/oxBBjuCJ0bUpqWnLl+lJkv1z IA== -----END CERTIFICATE-----`, jwk: { "kty": "RSA", "n": "8qvQOnph0i3M5-TpruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe-e3yy0ys_nS3qOrBZDYSMx2SPp-w", "e": "AQAB" } } }; // test data for EC key pair const EC_P256 = { private: { pem1: `-----BEGIN EC PRIVATE KEY----- MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49 AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== -----END EC PRIVATE KEY-----`, pem8: `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC 6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p -----END PRIVATE KEY-----`, jwk: { "kty": "EC", "crv": "P-256", "x": "DUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fk", "y": "CfGZkzYggmurC4Edrw9VTYdnYoq1oCjx-D1TCmr-Xuk", "d": "21OPBSSB8CJLCqBwYBdbITS54hbqfaTf3l2ZBne8avg" } }, public: { pem8: `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== -----END PUBLIC KEY-----`, cert: `-----BEGIN CERTIFICATE----- MIIBfzCCASWgAwIBAgIUK4H8J3Hr7NpRLPrACj8Pje4JJJ0wCgYIKoZIzj0EAwIw FTETMBEGA1UEAwwKUC0yNTYgdGVzdDAeFw0yMTExMTkxNzE5NDVaFw0zMTExMTcx NzE5NDVaMBUxEzARBgNVBAMMClAtMjU2IHRlc3QwWTATBgcqhkjOPQIBBggqhkjO PQMBBwNCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC6yBwATwfrzXR+QnxmZM2IIJr qwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7po1MwUTAdBgNVHQ4EFgQU/SxodXrpkybM gcIgkxnRKd7HMzowHwYDVR0jBBgwFoAU/SxodXrpkybMgcIgkxnRKd7HMzowDwYD VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBU9PrOa/kXCpTTBInRf/sN ac2iDHmbdpWzcXI+xLKNYAIhAIRR1LRSHVwOTLQ/iBXd+8LCkm5aTB27RW46LN80 ylxt -----END CERTIFICATE-----`, jwk: { "kty": "EC", "crv": "P-256", "x": "DUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fk", "y": "CfGZkzYggmurC4Edrw9VTYdnYoq1oCjx-D1TCmr-Xuk" } } }; const PEM_PRIV_DSA1024 = `-----BEGIN DSA PRIVATE KEY----- MIIBuwIBAAKBgQCkFEttBrPHEJRgcvaT8HbZs9h1pVQLHhn2F452izusRox1czMM IC8Z7YQiM1pt6bgEmf0h8ldx6UFT0YL9JWSbyBy1U5pHKfnz/xjeg7ZMReL4F0/T Gwmu4ercqfM//TmEg9nL3nDxb4WmF2al/SmHN3qlzYmYaIDEFfEuu8vWbwIVAMOq 7pqQiMGUu6uJY/nQTWW0c3IfAoGARWryStp2AElj538qN9tWRuyobRA93Q1ujrdM EqsqVpMZd1a8qtRyMaZVVdB7N3EweNUuFOoSAp10s/SQEH9qhVo6NwvzhB7lEtm4 5FjWW9+9WCuuFOGZpTy8PSFAvQcfUqunP/DeaDliNmgKci+n0nfIBakuQn10Zmqk vGu8NZICgYBUsoQeXSJ19e6XZenk6G8wVI3yXFqnRAwb6s7sAVoPwfDCsOXTxC7W Mlfz0HcYMiifFKEd28NnuAZ2e0ngyPHsb9s5phzTgRfO3GFzOjsjwgx3DmQI2Ck2 yOWHSAtaNhH4DoBZEyNsb1akiB50vx9b09EHN4weqbgAu743NMDHRQIVAIG5uiiO OnWUYieHAiVIPkBCrYUd -----END DSA PRIVATE KEY-----`; // https://datatracker.ietf.org/doc/html/rfc8037#appendix-A.2 const JWK_PUB_ED25591 = { "kty": "OKP", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" }; TestRegister.addTests([ { name: "PEM to JWK: Missing footer", input: RSA_512.private.pem1.substring(0, RSA_512.private.pem1.length / 2), expectedOutput: "PEM footer '-----END RSA PRIVATE KEY-----' not found", recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "PEM to JWK: DSA not supported", input: PEM_PRIV_DSA1024, expectedOutput: "DSA keys are not supported for JWK", recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, // test RSA key convertion { name: "PEM to JWK: RSA Private Key PKCS1", input: RSA_512.private.pem1, expectedOutput: JSON.stringify(RSA_512.private.jwk), recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "PEM to JWK: RSA Private Key PKCS8", input: RSA_512.private.pem8, expectedOutput: JSON.stringify(RSA_512.private.jwk), recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "PEM to JWK: RSA Public Key PKCS1", input: RSA_512.public.pem1, expectedOutput: "Unsupported RSA public key format. Only PKCS#8 is supported.", recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "PEM to JWK: RSA Public Key PKCS8", input: RSA_512.public.pem8, expectedOutput: JSON.stringify(RSA_512.public.jwk), recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "PEM to JWK: Certificate with RSA Public Key", input: RSA_512.public.cert, expectedOutput: JSON.stringify(RSA_512.public.jwk), recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, // test EC key conversion { name: "PEM to JWK: EC Private Key PKCS1", input: EC_P256.private.pem1, expectedOutput: JSON.stringify(EC_P256.private.jwk), recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "PEM to JWK: EC Private Key PKCS8", input: EC_P256.private.pem8, expectedOutput: JSON.stringify(EC_P256.private.jwk), recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "PEM to JWK: EC Public Key", input: EC_P256.public.pem8, expectedOutput: JSON.stringify(EC_P256.public.jwk), recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "PEM to JWK: Certificate with EC Public Key", input: EC_P256.public.cert, expectedOutput: JSON.stringify(EC_P256.public.jwk), recipeConfig: [ { op: "PEM to JWK", args: [], } ], }, { name: "JWK to PEM: not a JWK", input: "\"foobar\"", expectedOutput: "Input is not a JSON Web Key", recipeConfig: [ { op: "JWK to PEM", args: [], } ], }, { name: "JWK to PEM: unsupported key type", input: JSON.stringify(JWK_PUB_ED25591), expectedOutput: "Unsupported JWK key type 'OKP'", recipeConfig: [ { op: "JWK to PEM", args: [], } ], }, // test RSA key conversion { name: "JWK to PEM: RSA Private Key", input: JSON.stringify(RSA_512.private.jwk), expectedOutput: RSA_512.private.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n", recipeConfig: [ { op: "JWK to PEM", args: [], } ], }, { name: "JWK to PEM: RSA Public Key", input: JSON.stringify(RSA_512.public.jwk), expectedOutput: RSA_512.public.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n", recipeConfig: [ { op: "JWK to PEM", args: [], } ], }, // test EC key conversion { name: "JWK to PEM: EC Private Key", input: JSON.stringify(EC_P256.private.jwk), expectedOutput: EC_P256.private.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n", recipeConfig: [ { op: "JWK to PEM", args: [], } ], }, { name: "JWK to PEM: EC Public Key", input: JSON.stringify(EC_P256.public.jwk), expectedOutput: EC_P256.public.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n", recipeConfig: [ { op: "JWK to PEM", args: [], } ], }, { name: "JWK to PEM: Array of keys", input: JSON.stringify([RSA_512.public.jwk, EC_P256.public.jwk]), expectedOutput: (RSA_512.public.pem8 + "\n" + EC_P256.public.pem8 + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "JWK to PEM", args: [], } ], }, { name: "JWK to PEM: JSON Web Key Set", input: JSON.stringify({"keys": [RSA_512.public.jwk, EC_P256.public.jwk]}), expectedOutput: (RSA_512.public.pem8 + "\n" + EC_P256.public.pem8 + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "JWK to PEM", args: [], } ], } ]); ================================================ FILE: tests/operations/tests/JWTDecode.mjs ================================================ /** * JWT Decode tests * * @author gchq77703 [] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const outputObject = JSON.stringify({ String: "SomeString", Number: 42, iat: 1 }, null, 4); TestRegister.addTests([ { name: "JWT Decode: HS", input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", expectedOutput: outputObject, recipeConfig: [ { op: "JWT Decode", args: [], } ], }, { name: "JWT Decode: RS", input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y", expectedOutput: outputObject, recipeConfig: [ { op: "JWT Decode", args: [], } ], }, { name: "JWT Decode: ES", input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA", expectedOutput: outputObject, recipeConfig: [ { op: "JWT Decode", args: [], } ], } ]); ================================================ FILE: tests/operations/tests/JWTSign.mjs ================================================ /** * JWT Sign tests * * @author gchq77703 [] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const inputObject = JSON.stringify({ String: "SomeString", Number: 42, iat: 1 }, null, 4); const hsKey = "secret_cat"; const rsKey1024 = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw 33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW +jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS 3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE 2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY 6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== -----END RSA PRIVATE KEY-----`; const rsKey2048 = `-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAk0VOoksAblwP82DALTG6xGC86Hfho3nChbcPGWyqn+ScfHBF cg3SeKyy6aWCyLcKfNwE5cPYzuYvVBsZyIrdfFOuV90D/aRYbuw6UkKR3cmmy9qE qvu05dogvc0BcmkwbC37Q8JnsZBRcosoLGgTFxcK+LXdsG7DukajpsGesxQjOLb2 1jnx+ypzx74xvj7grqlXkxeDKr22q7QkO3A1ApoOuJRAU+SjEEZmqdXzRery2RWx hkWbCXuQw4PnW5Lh3Wwabnu7XKVIa6wJa1pqL2IAxmlZ0bvGTfjtO5ggNfgJk5V4 bGSOXnsplpG71AWMrK2q6NqHjFIE1szEycUKrwIDAQABAoIBAAivyt6Zy/G2g8kC 852hfvcRubLV92eRdAmNGFqTOqaUcS00i3QZyp4MRGqxtOV/88y/nEOtP1RHkZJw HXTjHq4JsDvwhnQR8JbCX6z1zkLQdS01u3jrwJTaPpooxdATfPlfO6CYjqM+SapB o7dS1ZAZb4U8vPx+MWoDEVNxvO7/xyqho1Oc4H9MwqQUiyG2WfIoqxLSrBYcambv RmySwTIpgQZTr61EeWf/0eWpV0iEYbSnkB/VaKW+5tg4gCjPgy5v6/LQ0u/pzlYz ayCL3xN2rp0tigXsiiWz3cM5gDsnatK4nVNRs9y3JSZpWpI236ZfZjs8Lts+WBUw hAEoE9kCgYEAyEIGD1A7R/t5EYk5HhHDH5tGdyxejAcQL5AIz0YnTZU8Iixyc7FR uDmAMiuKIcJY/nUlxZjSxNc3MkOfZNggQvf9ONrt+ftQ1yyTjv+019NfU4w4d0Ep LNaiAHgaPKimBUZjYXbLgiMXj/1pBaQmgUYTK/VlO3PVdowxxzxMYlMCgYEAvEOG GrhVaQV1nAYx86BgZ3wn90hBFXZWGaN+eXUmyrast93Ih3TCSgQDKPuN3pdv/TIe cpQv/BxEMpW+6d5Z1NP3GbrLpaZUiUNk8fqw1S3pmD5aWZrYIUaNukAyOxnZVgjv EWD9QTpI663gODaeZZTkDYiRNzTzGOg5HtzporUCgYBBOphEtqqImNXnq13qeHip O+eo+8/UJpzUEUN9WGmG8NxEeVvSaWin7DrgnKQCuQ5J3Biwk0XcDgoRmks6Ctf/ WE2oDk/DxGOhowhxZMMgJd6AFUVzOstRqpvcMULCjWB+iV3nqk1Bl3KeWTmzN7O/ Gfc2s1kFE4btdV7lebObtwKBgE3rkLS8eLVYCh6Cvef9CAms7Im/wRhV+zrvXWh9 4YljZEdRpy7RV5z03i33N/faLALa3JlF1jp9pIhfTD5Vxk59ULe4hZNRLYoGd+Bj hw8kyps1q4WMvkm/fueIrIGjqD2gwvopb4iwy/+n3rbFfHfE0UL8tEXqR3eWnhW1 D4pFAoGAccR4eMJD43hJWaUQLtsj0RoW9lFKVXj7aqkIIeupXwt7Ic2z/FhCAJi+ V0MWpd3K6+kPl+ifdt8U4kcYfubPMfJhd7IkMcgQS+yZK1+5xWdRISvI8GpNwIHE LUkVkCCadXNNZ7b1nmUKjse95u4IaE6hwAqjSTNb05gPmCfoEjg= -----END RSA PRIVATE KEY-----`; const esKeyP256 = `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r 1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G -----END PRIVATE KEY-----`; const esKeyP384 = `-----BEGIN PRIVATE KEY----- MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDpgCvB2frnLKd7TuWe JM1ejXXmr9y/5gskxKuuylLvpQTiDdtLtuhJnvw1/zWKWO6hZANiAAQ5Crhsi5FD t55i53dCtdzG9OzCnbDFf/6136ZfEiakDTDeWCdUvNnB3WQEcVBr97BfSWLI9mO+ T5yzm0RfhgvWIq/tBou+sIDeGp6NQfJwhDhf+JsdeF174gtfNMZGj/s= -----END PRIVATE KEY-----`; const esKeyP521 = `-----BEGIN PRIVATE KEY----- MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIA0dBErrZ5ovKq4Xf/ iTlRkYxuOfgBZ6+tWIfG13YwthB1XrH06YmteZGNjHHLZEeycwUt0jM4kUb+tOsJ 3ckhj1ihgYkDgYYABACYgsa8JWKH46CQagwNw14v/L+DIs1WAjJdMXZySjKlRkD9 LtLMxkbX2H4H4Zl2KzCMJkwTSETzSKNlXvAUJqKbRwHezCp4y5XZN9MOBYdmyylZ NOVxwwTouimNkJ0K6A8+/Im5S3PWB8Ra1D6t+bT1WHHhEePZcltSLLFlbIIyot5m 2w== -----END PRIVATE KEY-----`; TestRegister.addTests([ { name: "JWT Sign: HS256", input: inputObject, expectedOutput: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", recipeConfig: [ { op: "JWT Sign", args: [hsKey, "HS256", "{}"], } ], }, { name: "JWT Sign: HS256 with custom header", input: inputObject, expectedOutput: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1c3RvbS5rZXkifQ.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.kXln8btJburfRlND8IDZAQ8NZGFFZhvHyooHa6N9za8", recipeConfig: [ { op: "JWT Sign", args: [hsKey, "HS256", `{"kid":"custom.key"}`], } ], }, { name: "JWT Sign: HS384", input: inputObject, expectedOutput: "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ._bPK-Y3mIACConbJqkGFMQ_L3vbxgKXy9gSxtL9hA5XTganozTSXxD0vX0N1yT5s", recipeConfig: [ { op: "JWT Sign", args: [hsKey, "HS384", "{}"], } ], }, { name: "JWT Sign: HS512", input: inputObject, expectedOutput: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.vZIJU4XYMFt3FLE1V_RZOxEetmV4RvxtPZQGzJthK_d47pjwlEb6pQE23YxHFmOj8H5RLEdqqLPw4jNsOyHRzA", recipeConfig: [ { op: "JWT Sign", args: [hsKey, "HS512", "{}"], } ], }, { name: "JWT Sign: ES256", input: inputObject, expectedOutput: inputObject, recipeConfig: [ { op: "JWT Sign", args: [esKeyP256, "ES256", "{}"], }, { op: "JWT Decode", args: [] } ], }, { name: "JWT Sign: ES384 - P256 key", input: inputObject, 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. Error: "alg" parameter "ES384" requires curve "secp384r1".`, recipeConfig: [ { op: "JWT Sign", args: [esKeyP256, "ES384", "{}"], }, { op: "JWT Decode", args: [] } ], }, { name: "JWT Sign: ES384", input: inputObject, expectedOutput: inputObject, recipeConfig: [ { op: "JWT Sign", args: [esKeyP384, "ES384", "{}"], }, { op: "JWT Decode", args: [] } ], }, { name: "JWT Sign: ES512", input: inputObject, expectedOutput: inputObject, recipeConfig: [ { op: "JWT Sign", args: [esKeyP521, "ES512", "{}"], }, { op: "JWT Decode", args: [] } ], }, { name: "JWT Sign: RS256, weak key", input: inputObject, 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. Error: secretOrPrivateKey has a minimum key size of 2048 bits for RS256`, recipeConfig: [ { op: "JWT Sign", args: [rsKey1024, "RS256", "{}"], }, { op: "JWT Decode", args: [] } ], }, { name: "JWT Sign: RS256", input: inputObject, expectedOutput: inputObject, recipeConfig: [ { op: "JWT Sign", args: [rsKey2048, "RS256", "{}"], }, { op: "JWT Decode", args: [] } ], }, { name: "JWT Sign: RS384", input: inputObject, expectedOutput: inputObject, recipeConfig: [ { op: "JWT Sign", args: [rsKey2048, "RS384", "{}"], }, { op: "JWT Decode", args: [] } ], }, { name: "JWT Sign: RS512", input: inputObject, expectedOutput: inputObject, recipeConfig: [ { op: "JWT Sign", args: [rsKey2048, "RS512", "{}"], }, { op: "JWT Decode", args: [] } ], } ]); ================================================ FILE: tests/operations/tests/JWTVerify.mjs ================================================ /** * JWT Verify tests * * @author gchq77703 [] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const outputObject = JSON.stringify({ String: "SomeString", Number: 42, iat: 1 }, null, 4); const hsKey = "secret_cat"; /* Retaining private key as a comment const rsPriv = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw 33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW +jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS 3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE 2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY 6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== -----END RSA PRIVATE KEY-----`; */ const rsPub = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D o2kQ+X5xK9cipRgEKwIDAQAB -----END PUBLIC KEY-----`; /* Retaining private key as a comment const esPriv = `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r 1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G -----END PRIVATE KEY-----`; */ const esPub = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9 q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg== -----END PUBLIC KEY-----`; TestRegister.addTests([ { name: "JWT Verify: HS", input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", expectedOutput: outputObject, recipeConfig: [ { op: "JWT Verify", args: [hsKey], } ], }, { name: "JWT Verify: RS", input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y", expectedOutput: outputObject, recipeConfig: [ { op: "JWT Verify", args: [rsPub], } ], }, { name: "JWT Verify: ES", input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA", expectedOutput: outputObject, recipeConfig: [ { op: "JWT Verify", args: [esPub], } ], } ]); ================================================ FILE: tests/operations/tests/Jsonata.mjs ================================================ /** * Jsonata Query tests. * * @author Jon King [jon@ajarsoftware.com] * * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const INPUT_JSON_OBJECT_WITH_ARRAYS = `{ "FirstName": "Fred", "Surname": "Smith", "Age": 28, "Address": { "Street": "Hursley Park", "City": "Winchester", "Postcode": "SO21 2JN" }, "Phone": [ { "type": "home", "number": "0203 544 1234" }, { "type": "office", "number": "01962 001234" }, { "type": "office", "number": "01962 001235" }, { "type": "mobile", "number": "077 7700 1234" } ], "Email": [ { "type": "work", "address": ["fred.smith@my-work.com", "fsmith@my-work.com"] }, { "type": "home", "address": ["freddy@my-social.com", "frederic.smith@very-serious.com"] } ], "Other": { "Over 18 ?": true, "Misc": null, "Alternative.Address": { "Street": "Brick Lane", "City": "London", "Postcode": "E1 6RF" } } }`; const INPUT_ARRAY_OF_OBJECTS = `[ { "ref": [ 1,2 ] }, { "ref": [ 3,4 ] } ]`; const INPUT_NUMBER_ARRAY = `{ "Numbers": [1, 2.4, 3.5, 10, 20.9, 30] }`; TestRegister.addTests([ { name: "Jsonata: Returns a JSON string (double quoted)", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"Smith"', recipeConfig: [ { op: "Jsonata Query", args: ["Surname"], }, ], }, { name: "Jsonata: Returns a JSON number", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: "28", recipeConfig: [ { op: "Jsonata Query", args: ["Age"], }, ], }, { name: "Jsonata: Field references are separated by '.'", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"Winchester"', recipeConfig: [ { op: "Jsonata Query", args: ["Address.City"], }, ], }, { name: "Jsonata: Matched the path and returns the null value", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: "null", recipeConfig: [ { op: "Jsonata Query", args: ["Other.Misc"], }, ], }, { name: "Jsonata: Path not found. Returns nothing", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '""', recipeConfig: [ { op: "Jsonata Query", args: ["Other.DoesntExist"], }, ], }, { name: "Jsonata: Field references containing whitespace or reserved tokens can be enclosed in backticks", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: "true", recipeConfig: [ { op: "Jsonata Query", args: ["Other.`Over 18 ?`"], }, ], }, { name: "Jsonata: Returns the first item (an object)", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '{"type":"home","number":"0203 544 1234"}', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[0]"], }, ], }, { name: "Jsonata: Returns the second item", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '{"type":"office","number":"01962 001234"}', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[1]"], }, ], }, { name: "Jsonata: Returns the last item", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '{"type":"mobile","number":"077 7700 1234"}', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[-1]"], }, ], }, { name: "Jsonata: Negative indexed count from the end", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '{"type":"office","number":"01962 001235"}', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[-2]"], }, ], }, { name: "Jsonata: Doesn't exist - returns nothing", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '""', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[8]"], }, ], }, { name: "Jsonata: Selects the number field in the first item", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"0203 544 1234"', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[0].number"], }, ], }, { 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", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '["0203 544 1234","01962 001234","01962 001235","077 7700 1234"]', recipeConfig: [ { op: "Jsonata Query", args: ["Phone.number"], }, ], }, { 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", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '["0203 544 1234","01962 001234","01962 001235","077 7700 1234"]', recipeConfig: [ { op: "Jsonata Query", args: ["Phone.number[0]"], }, ], }, { name: "Jsonata: Applies the index to the array returned by Phone.number.", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"0203 544 1234"', recipeConfig: [ { op: "Jsonata Query", args: ["(Phone.number)[0]"], }, ], }, { name: "Jsonata: Returns a range of items by creating an array of indexes", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '[{"type":"home","number":"0203 544 1234"},{"type":"office","number":"01962 001234"}]', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[[0..1]]"], }, ], }, // Predicates { name: "Jsonata: Select the Phone items that have a type field that equals 'mobile'", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '{"type":"mobile","number":"077 7700 1234"}', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[type='mobile']"], }, ], }, { name: "Jsonata: Select the mobile phone number", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"077 7700 1234"', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[type='mobile'].number"], }, ], }, { name: "Jsonata: Select the office phone numbers - there are two of them", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '["01962 001234","01962 001235"]', recipeConfig: [ { op: "Jsonata Query", args: ["Phone[type='office'].number"], }, ], }, // Wildcards { name: "Jsonata: Select the values of all the fields of 'Address'", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '["Hursley Park","Winchester","SO21 2JN"]', recipeConfig: [ { op: "Jsonata Query", args: ["Address.*"], }, ], }, { name: "Jsonata: Select the 'Postcode' value of any child object", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"SO21 2JN"', recipeConfig: [ { op: "Jsonata Query", args: ["*.Postcode"], }, ], }, { name: "Jsonata: Select all Postcode values, regardless of how deeply nested they are in the structure", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '["SO21 2JN","E1 6RF"]', recipeConfig: [ { op: "Jsonata Query", args: ["**.Postcode"], }, ], }, // String Expressions { name: "Jsonata: Concatenate 'FirstName' followed by space followed by 'Surname'", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"Fred Smith"', recipeConfig: [ { op: "Jsonata Query", args: ["FirstName & ' ' & Surname"], }, ], }, { name: "Jsonata: Concatenates the 'Street' and 'City' from the 'Address' object with a comma separator", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"Hursley Park, Winchester"', recipeConfig: [ { op: "Jsonata Query", args: ["Address.(Street & ', ' & City)"], }, ], }, { name: "Jsonata: Casts the operands to strings, if necessary", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: '"50true"', recipeConfig: [ { op: "Jsonata Query", args: ["5&0&true"], }, ], }, // Numeric Expressions { name: "Jsonata: Addition", input: INPUT_NUMBER_ARRAY, expectedOutput: "3.4", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] + Numbers[1]"], }, ], }, { name: "Jsonata: Subtraction", input: INPUT_NUMBER_ARRAY, expectedOutput: "-19.9", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] - Numbers[4]"], }, ], }, { name: "Jsonata: Multiplication", input: INPUT_NUMBER_ARRAY, expectedOutput: "30", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] * Numbers[5]"], }, ], }, { name: "Jsonata: Division", input: INPUT_NUMBER_ARRAY, expectedOutput: "0.04784688995215311", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] / Numbers[4]"], }, ], }, { name: "Jsonata: Modulus", input: INPUT_NUMBER_ARRAY, expectedOutput: "3.5", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[2] % Numbers[5]"], }, ], }, { name: "Jsonata: Equality", input: INPUT_NUMBER_ARRAY, expectedOutput: "false", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] = Numbers[5]"], }, ], }, { name: "Jsonata: Inequality", input: INPUT_NUMBER_ARRAY, expectedOutput: "true", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] != Numbers[4]"], }, ], }, { name: "Jsonata: Less than", input: INPUT_NUMBER_ARRAY, expectedOutput: "true", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] < Numbers[4]"], }, ], }, { name: "Jsonata: Less than or equal to", input: INPUT_NUMBER_ARRAY, expectedOutput: "true", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] <= Numbers[4]"], }, ], }, { name: "Jsonata: Greater than", input: INPUT_NUMBER_ARRAY, expectedOutput: "false", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[0] > Numbers[4]"], }, ], }, { name: "Jsonata: Greater than or equal to", input: INPUT_NUMBER_ARRAY, expectedOutput: "false", recipeConfig: [ { op: "Jsonata Query", args: ["Numbers[2] >= Numbers[4]"], }, ], }, { name: "Jsonata: Value is contained in", input: INPUT_JSON_OBJECT_WITH_ARRAYS, expectedOutput: "true", recipeConfig: [ { op: "Jsonata Query", args: ['"01962 001234" in Phone.number'], }, ], }, // Boolean Expressions { name: "Jsonata: and", input: INPUT_NUMBER_ARRAY, expectedOutput: "true", recipeConfig: [ { op: "Jsonata Query", args: ["(Numbers[2] != 0) and (Numbers[5] != Numbers[1])"], }, ], }, { name: "Jsonata: or", input: INPUT_NUMBER_ARRAY, expectedOutput: "true", recipeConfig: [ { op: "Jsonata Query", args: ["(Numbers[2] != 0) or (Numbers[5] = Numbers[1])"], }, ], }, // Array tests { name: "Jsonata: $ at the start of an expression refers to the entire input document, subscripting it with 0 selects the first item", input: INPUT_ARRAY_OF_OBJECTS, expectedOutput: '{"ref":[1,2]}', recipeConfig: [ { op: "Jsonata Query", args: ["$[0]"], }, ], }, { name: "Jsonata: .ref here returns the entire internal array", input: INPUT_ARRAY_OF_OBJECTS, expectedOutput: "[1,2]", recipeConfig: [ { op: "Jsonata Query", args: ["$[0].ref"], }, ], }, { name: "Jsonata: returns element on first position of the internal array", input: INPUT_ARRAY_OF_OBJECTS, expectedOutput: "1", recipeConfig: [ { op: "Jsonata Query", args: ["$[0].ref[0]"], }, ], }, { name: "Jsonata: $.field_reference flattens the result into a single array", input: INPUT_ARRAY_OF_OBJECTS, expectedOutput: "[1,2,3,4]", recipeConfig: [ { op: "Jsonata Query", args: ["$.ref"], }, ], }, ]); ================================================ FILE: tests/operations/tests/Jump.mjs ================================================ /** * Jump tests * * @author tlwr [toby@toby.codes] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Jump: Empty Label", input: [ "should be changed", ].join("\n"), expectedOutput: [ "c2hvdWxkIGJlIGNoYW5nZWQ=", ].join("\n"), recipeConfig: [ { op: "Jump", args: ["", 10], }, { op: "To Base64", args: ["A-Za-z0-9+/="], }, ], }, { name: "Jump: skips 1", input: [ "shouldnt be changed", ].join("\n"), expectedOutput: [ "shouldnt be changed", ].join("\n"), recipeConfig: [ { op: "Jump", args: ["skipReplace", 10], }, { op: "To Base64", args: ["A-Za-z0-9+/="], }, { op: "Label", args: ["skipReplace"] }, ], } ]); ================================================ FILE: tests/operations/tests/LS47.mjs ================================================ /** * LS47 tests. * * @author n1073645 [n1073645@gmail.com] * * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "LS47 Encrypt", input: "thequickbrownfoxjumped", expectedOutput: "(,t74ci78cp/8trx*yesu:alp1wqy", recipeConfig: [ { op: "LS47 Encrypt", args: ["helloworld", 0, "test"], }, ], }, { name: "LS47 Decrypt", input: "(,t74ci78cp/8trx*yesu:alp1wqy", expectedOutput: "thequickbrownfoxjumped---test", recipeConfig: [ { op: "LS47 Decrypt", args: ["helloworld", 0], }, ], }, { name: "LS47 Encrypt", input: "thequickbrownfoxjumped", expectedOutput: "Letter H is not included in LS47", recipeConfig: [ { op: "LS47 Encrypt", args: ["Helloworld", 0, "test"], }, ], } ]); ================================================ FILE: tests/operations/tests/LZNT1Decompress.mjs ================================================ /** * LZNT1 Decompress tests. * * @author 0xThiebaut [thiebaut.dev] * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "LZNT1 Decompress", input: "\x1a\xb0\x00compress\x00edtestda\x04ta\x07\x88alot", expectedOutput: "compressedtestdatacompressedalot", recipeConfig: [ { op: "LZNT1 Decompress", args: [] } ], } ]); ================================================ FILE: tests/operations/tests/LZString.mjs ================================================ /** * LZString tests. * * @author crespyl [peter@crespyl.net] * @copyright Peter Jacobs 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "LZString Compress To Base64", input: "hello world", expectedOutput: "BYUwNmD2AEDukCcwBMg=", recipeConfig: [ { "op": "LZString Compress", "args": ["Base64"] } ], }, { name: "LZString Decompress From Base64", input: "BYUwNmD2AEDukCcwBMg=", expectedOutput: "hello world", recipeConfig: [ { "op": "LZString Decompress", "args": ["Base64"] } ], } ]); ================================================ FILE: tests/operations/tests/LevenshteinDistance.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "Levenshtein Distance: Wikipedia example 1", "input": "kitten\nsitting", "expectedOutput": "3", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 1, 1, 1, ], }, ], }, { "name": "Levenshtein Distance: Wikipedia example 2", "input": "saturday\nsunday", "expectedOutput": "3", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 1, 1, 1, ], }, ], }, { "name": "Levenshtein Distance: Wikipedia example 1 with substitution cost 2", "input": "kitten\nsitting", "expectedOutput": "5", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 1, 1, 2, ], }, ], }, { "name": "Levenshtein Distance: varied costs 1", "input": "kitten\nsitting", "expectedOutput": "230", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 10, 100, 1000, ], }, ], }, { "name": "Levenshtein Distance: varied costs 2", "input": "kitten\nsitting", "expectedOutput": "1020", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 1000, 100, 10, ], }, ], }, { "name": "Levenshtein Distance: another delimiter", "input": "kitten sitting", "expectedOutput": "3", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ " ", 1, 1, 1, ], }, ], }, { "name": "Levenshtein Distance: too few samples", "input": "kitten", "expectedOutput": "Incorrect number of samples. Check your input and/or delimiter.", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 1, 1, 1, ], }, ], }, { "name": "Levenshtein Distance: too many samples", "input": "kitten\nsitting\nkitchen", "expectedOutput": "Incorrect number of samples. Check your input and/or delimiter.", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 1, 1, 1, ], }, ], }, { "name": "Levenshtein Distance: negative insertion cost", "input": "kitten\nsitting", "expectedOutput": "Negative costs are not allowed.", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", -1, 1, 1, ], }, ], }, { "name": "Levenshtein Distance: negative deletion cost", "input": "kitten\nsitting", "expectedOutput": "Negative costs are not allowed.", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 1, -1, 1, ], }, ], }, { "name": "Levenshtein Distance: negative substitution cost", "input": "kitten\nsitting", "expectedOutput": "Negative costs are not allowed.", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 1, 1, -1, ], }, ], }, { "name": "Levenshtein Distance: cost zero", "input": "kitten\nsitting", "expectedOutput": "0", "recipeConfig": [ { "op": "Levenshtein Distance", "args": [ "\\n", 0, 0, 0, ], }, ], }, ]); ================================================ FILE: tests/operations/tests/Lorenz.mjs ================================================ /** * Lorenz SZ40/42a/42b machine tests. * * @author VirtualColossus * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { // Simple test first - plain text to ITA2 name: "Lorenz SZ40: no pattern, plain text", input: "HELLO WORLD, THIS IS A TEST MESSAGE.", expectedOutput: "HELLO9WORLD55N889THIS9IS9A9TEST9MESSAGE55M", recipeConfig: [ { "op": "Lorenz", "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.."] } ] }, { // KH Pattern name: "Lorenz SZ40: KH pattern, plain text, all 1s", input: "HELLO WORLD, THIS IS A TEST MESSAGE.", expectedOutput: "VIC3TS/CUJA/3II9W9JWDI5DAFXT4SOIF3999IZD9T", recipeConfig: [ { "op": "Lorenz", "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.."] } ] }, { // KH Pattern, Random Start name: "Lorenz SZ40: KH pattern, plain text, random start", input: "HELLO WORLD, THIS IS A TEST MESSAGE.", expectedOutput: "KGZP5ONYCHNNOXS9SN45MIE3SC3DJBZVJUOE5SLVGI", recipeConfig: [ { "op": "Lorenz", "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.."] } ] }, { // ZMUG Pattern, Random Start name: "Lorenz SZ40: ZMUG pattern, plain text, random start", input: "HELLO WORLD, THIS IS A TEST MESSAGE.", expectedOutput: "IQVPAANDCA3CHDNO3V/CZQ/BTPZIKW8YAAQXQGLDMV", recipeConfig: [ { "op": "Lorenz", "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.."] } ] }, { // Bream Pattern, Random Start name: "Lorenz SZ40: Bream pattern, plain text, random start", input: "HELLO WORLD, THIS IS A TEST MESSAGE.", expectedOutput: "/89OALRPJEZQGOO84WOEQZ/I9NBRZOQPBTANC8E/GK", recipeConfig: [ { "op": "Lorenz", "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.."] } ] }, { // KH Pattern, all 1s name: "Lorenz SZ42a: KH pattern, plain text, all 1s", input: "HELLO WORLD, THIS IS A TEST MESSAGE.", expectedOutput: "VIC3TS/ZOHUYXWLTUXPV9ZNOTW9IXJPFDLIBB5ZD9K", recipeConfig: [ { "op": "Lorenz", "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.."] } ] }, { // KH Pattern, Full Test on real message name: "Lorenz SZ42a: Receive, KH pattern, ITA2 output", 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", expectedOutput: "XWOLLE9WI9R99AUCH9BLEIBEN955Z88KR99GWFRL5X89FUES959WPYP9QXT9QIPQ9V9AACQEM9959AA899QEE9959AA8999AN99OB5M89SUEDW/ST5V9AA8GEHEIME9KOMMANDOSACHE5AA89959AA89NUR9ZUR9PERSOENLICHENSINTERRICHTUNG5N889WEITERGABE9VERBOTEN5MA9AA8LAGEBERICHT9VOM99GSEPMM98NN99VOM9959EPMRM9QORT9AA889KATEN9NN9KAT9NN9KARTEN9959Q9C9QSVPP9PPP9A9MA98889ROEM959QM89WESTEN9959C899AOK999L9U9C88889ROEMS59QWM89A5M89K5MCMA989PANZERUNTERSTUEZ9NN9PANZSRUNTERSTUETZTESFEINDANGRIFFE99GEGEN9EIGENE9STUETZPU9NKTFRONT9NAHMEN9O4D9ELSTER5N89SOHL5N89RAU9NN9RAU9N9UND9BAD9BRAMBACH95K9WR8889NN9995KWT88SKM9BFO9HOF5LM89NEUE9STUETZPUNKTFRONT9B99DIESER9ORT5M89UEBRIGE9CORPSFRONT9BEIDERSEITIGE9AUFKLAERUNGSTAETIGKEI5M9DIESER9ORT5M89I/BRIGE9KORPSFRONT9BEIDERSEITIGE9AUFKLAERUNGSTAETIGKEI5MA99A8STELWV5M89ROEM959QEM89A5M8K5MMA989ZUNEHMENDER9FEINDDRUCK9IM9RAUXSN9UND99W99BAERNAU95K9T889NN9959KWT889KM9SW9MARIENBAD5LM889FEINDAFGRIF9F99VON99SO99GEGEN9PAULUSBRUNN5M899LAGE9DORT9UNGEKL4ERT5M89SCHWACHE99EIGENEN9SICHERUNGEN9DURCH9FEIND9AUS99SCHAENWAWDE9UND9WOSANT995K9Y89KM9SO99TACHAU5L89NACH9O99ZURUECKGEWOFNN9TURUEIKGEWORFEN5M89FEIND99MIT9INF9UND9PZ5M89IN9PEISSIGKAU9UND9WLAD9NS9DAHENTEN5M89EIGENE9SICHERANGEN9IN9MOLGAU959A89DRI9UND9WLAD9N993AHENTEN5M89EIGENE9SIC4ERUNGENSIGKAU9UND9WLAD9N99DAHENTEN5M89EUGENE9SICHERUNGEN9IN9MOLGAU959A89DRISSGLOBEN959A89WURKAU5M89IM9A9SNN9IM9RAUM99TAUS99PANZERUNTERSTUETZTE99FEINDANGRIFFE5MA99AA899A5X8O5M8K5M99QC9MA9989UEBER9ISAR9MIT9INF9UND9PZ5M89UEBERBESETZTEJ9FEIND9STIESZ99UEBER9S9NN9UEBER99VILSA9BSCHNITT9VOR9UND9NAHXS9AUNKIRCHEN959A89ALDERSBACH959A89EGGERSDORF995KWP889KM9W9P49SAU5LMA9A88ROEM9959IWM89A5MLK5MCMA989DWP889KM9W9PASSAU5LMASA88ROEM9959IWM89A5MLK5MCMA989DER9ZWISCHEN9PLATTLING9UND9LANDAI9FU9NN3/UF99BREITER9FRONT99UEBER9DIE9ISAR99UEBERB99NNN9UEBER5ESETZTE99FEIND9DRUECKTE9EIGENEN9LINIE5N89TROTZ9HIFTIGEN9WIDERST4F3ES5N89AUN9VISLABSCHNITT9ZUREUCK585M89FEINDPANZER9N9NN9IN9ARTOFJFN9IN9ARNTORF959KQT889KM9SO9LANDAU5B89UND99N9ROTTESDORF599IN9ARNZORZ959KQT889KM9SO9LANDAU5L89UND99N9ROTTESDORF59KI89KM9S9LANDAUGWMA59A89ROEM959QEM8939NN9959A899ROEM9959QK4OLE", recipeConfig: [ { "op": "Lorenz", "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.."] } ] }, { // ZMUG Pattern, Full Test, Receive name: "Lorenz SZ42b: Receive, ZMUG pattern, KT-Schalte, Plaintext output", 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", 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", recipeConfig: [ { "op": "Lorenz", "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.."] } ] } ]); ================================================ FILE: tests/operations/tests/LuhnChecksum.mjs ================================================ /** * From Decimal tests * * @author n1073645 [n1073645@gmail.com] * @author k3ach [k3ach@proton.me] * @copyright Crown Copyright 2020 * @licence Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const testCases = [ { radix: 2, input: "01", checksum: "1", checkdigit: "1", }, { radix: 2, input: "001111", checksum: "0", checkdigit: "0", }, { radix: 2, input: "00011101", checksum: "0", checkdigit: "0", }, { radix: 2, input: "0100101101", checksum: "1", checkdigit: "1", }, { radix: 4, input: "0123", checksum: "1", checkdigit: "1", }, { radix: 4, input: "130100", checksum: "2", checkdigit: "2", }, { radix: 4, input: "32020313", checksum: "3", checkdigit: "0", }, { radix: 4, input: "302233210112", checksum: "3", checkdigit: "0", }, { radix: 6, input: "012345", checksum: "4", checkdigit: "4", }, { radix: 6, input: "134255", checksum: "2", checkdigit: "4", }, { radix: 6, input: "15021453", checksum: "5", checkdigit: "4", }, { radix: 6, input: "211450230513", checksum: "3", checkdigit: "1", }, { radix: 8, input: "01234567", checksum: "2", checkdigit: "2", }, { radix: 8, input: "340624", checksum: "0", checkdigit: "4", }, { radix: 8, input: "07260247", checksum: "3", checkdigit: "3", }, { radix: 8, input: "026742114675", checksum: "7", checkdigit: "1", }, { radix: 10, input: "0123456789", checksum: "7", checkdigit: "7", }, { radix: 10, input: "468543", checksum: "7", checkdigit: "4", }, { radix: 10, input: "59377601", checksum: "5", checkdigit: "6", }, { radix: 10, input: "013909981254", checksum: "1", checkdigit: "3", }, { radix: 12, input: "0123456789ab", checksum: "3", checkdigit: "3", }, { radix: 12, input: "284685", checksum: "0", checkdigit: "6", }, { radix: 12, input: "951a2661", checksum: "0", checkdigit: "8", }, { radix: 12, input: "898202676387", checksum: "b", checkdigit: "9", }, { radix: 14, input: "0123456789abcd", checksum: "a", checkdigit: "a", }, { radix: 14, input: "33db25", checksum: "0", checkdigit: "d", }, { radix: 14, input: "0b4ac128", checksum: "b", checkdigit: "3", }, { radix: 14, input: "3d1c6d16160d", checksum: "3", checkdigit: "c", }, { radix: 16, input: "0123456789abcdef", checksum: "4", checkdigit: "4", }, { radix: 16, input: "e1fe64", checksum: "b", checkdigit: "6", }, { radix: 16, input: "241a5dcd", checksum: "1", checkdigit: "9", }, { radix: 16, input: "1fea740e0e1f", checksum: "7", checkdigit: "4", }, { radix: 18, input: "0123456789abcdefgh", checksum: "d", checkdigit: "d", }, { radix: 18, input: "995dgf", checksum: "9", checkdigit: "1", }, { radix: 18, input: "9f80h32h", checksum: "1", checkdigit: "0", }, { radix: 18, input: "5f9428e493g4", checksum: "8", checkdigit: "c", }, { radix: 20, input: "0123456789abcdefghij", checksum: "5", checkdigit: "5", }, { radix: 20, input: "918jci", checksum: "h", checkdigit: "d", }, { radix: 20, input: "jab7j50d", checksum: "g", checkdigit: "j", }, { radix: 20, input: "c56fe85eb6gg", checksum: "g", checkdigit: "5", }, { radix: 22, input: "0123456789abcdefghijkl", checksum: "g", checkdigit: "g", }, { radix: 22, input: "de57le", checksum: "5", checkdigit: "l", }, { radix: 22, input: "e3fg6dfc", checksum: "f", checkdigit: "d", }, { radix: 22, input: "1f8l80ai4kbg", checksum: "l", checkdigit: "f", }, { radix: 24, input: "0123456789abcdefghijklmn", checksum: "6", checkdigit: "6", }, { radix: 24, input: "agne7d", checksum: "4", checkdigit: "f", }, { radix: 24, input: "1l4d9cf4", checksum: "d", checkdigit: "c", }, { radix: 24, input: "blc1j09i3296", checksum: "8", checkdigit: "7", }, { radix: 26, input: "0123456789abcdefghijklmnop", checksum: "j", checkdigit: "j", }, { radix: 26, input: "82n9op", checksum: "i", checkdigit: "2", }, { radix: 26, input: "e9cddn70", checksum: "9", checkdigit: "i", }, { radix: 26, input: "ck0ep419knom", checksum: "p", checkdigit: "g", }, { radix: 28, input: "0123456789abcdefghijklmnopqr", checksum: "7", checkdigit: "7", }, { radix: 28, input: "a6hnoo", checksum: "h", checkdigit: "9", }, { radix: 28, input: "lblc7kh0", checksum: "a", checkdigit: "f", }, { radix: 28, input: "64k5piod3lmf", checksum: "0", checkdigit: "p", }, { radix: 30, input: "0123456789abcdefghijklmnopqrst", checksum: "m", checkdigit: "m", }, { radix: 30, input: "t69j7d", checksum: "9", checkdigit: "s", }, { radix: 30, input: "p54o9ig3", checksum: "a", checkdigit: "o", }, { radix: 30, input: "gc1njrt55030", checksum: "6", checkdigit: "1", }, { radix: 32, input: "0123456789abcdefghijklmnopqrstuv", checksum: "8", checkdigit: "8", }, { radix: 32, input: "rdou19", checksum: "u", checkdigit: "3", }, { radix: 32, input: "ighj0pc7", checksum: "3", checkdigit: "8", }, { radix: 32, input: "op4nn5fvjsrs", checksum: "g", checkdigit: "j", }, { radix: 34, input: "0123456789abcdefghijklmnopqrstuvwx", checksum: "p", checkdigit: "p", }, { radix: 34, input: "nvftj5", checksum: "b", checkdigit: "f", }, { radix: 34, input: "u9v9g162", checksum: "j", checkdigit: "b", }, { radix: 34, input: "o5gqg5d7gjh9", checksum: "5", checkdigit: "q", }, { radix: 36, input: "0123456789abcdefghijklmnopqrstuvwxyz", checksum: "9", checkdigit: "9", }, { radix: 36, input: "29zehu", checksum: "i", checkdigit: "j", }, { radix: 36, input: "1snmikbu", checksum: "s", checkdigit: "v", }, { radix: 36, input: "jpkar545q7gb", checksum: "3", checkdigit: "d", }, ]; testCases.forEach(element => { TestRegister.addTests([ { name: "Luhn Checksum Mod " + element.radix + " on " + element.input, input: element.input, expectedOutput: "Checksum: " + element.checksum + "\nCheckdigit: " + element.checkdigit + "\nLuhn Validated String: " + element.input + element.checkdigit, recipeConfig: [ { op: "Luhn Checksum", args: [element.radix] }, ], }, ]); }); TestRegister.addTests([ { name: "Luhn Checksum on standard data", input: "35641709012469", expectedOutput: "Checksum: 7\nCheckdigit: 0\nLuhn Validated String: 356417090124690", recipeConfig: [ { op: "Luhn Checksum", args: [10] }, ], }, { name: "Luhn Checksum on standard data 2", input: "896101950123440000", expectedOutput: "Checksum: 5\nCheckdigit: 1\nLuhn Validated String: 8961019501234400001", recipeConfig: [ { op: "Luhn Checksum", args: [10] }, ], }, { name: "Luhn Checksum on standard data 3", input: "35726908971331", expectedOutput: "Checksum: 6\nCheckdigit: 7\nLuhn Validated String: 357269089713317", recipeConfig: [ { op: "Luhn Checksum", args: [10] }, ], }, { name: "Luhn Checksum on empty data", input: "", expectedOutput: "", recipeConfig: [ { op: "Luhn Checksum", args: [10] }, ], }, ]); ================================================ FILE: tests/operations/tests/MIMEDecoding.mjs ================================================ /** * MIME Header Decoding tests * * @author mshwed [m@ttshwed.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Encoded comments", input: "(=?ISO-8859-1?Q?a?=)", expectedOutput: "(a)", recipeConfig: [ { "op": "MIME Decoding", "args": [] } ] }, { name: "Encoded adjacent comments whitespace", input: "(=?ISO-8859-1?Q?a?= b)", expectedOutput: "(a b)", recipeConfig: [ { "op": "MIME Decoding", "args": [] } ] }, { name: "Encoded adjacent single whitespace ignored", input: "(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)", expectedOutput: "(ab)", recipeConfig: [ { "op": "MIME Decoding", "args": [] } ] }, { name: "Encoded adjacent double whitespace ignored", input: "(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)", expectedOutput: "(ab)", recipeConfig: [ { "op": "MIME Decoding", "args": [] } ] }, { name: "Encoded adjacent CRLF whitespace ignored", input: "(=?ISO-8859-1?Q?a?=\r\n =?ISO-8859-1?Q?b?=)", expectedOutput: "(ab)", recipeConfig: [ { "op": "MIME Decoding", "args": [] } ] }, { name: "UTF-8 Encodings Multiple Headers", input: "=?utf-8?q?=C3=89ric?= , =?utf-8?q?Ana=C3=AFs?= ", expectedOutput: "Éric , Anaïs ", recipeConfig: [ { "op": "MIME Decoding", "args": [] } ] }, { name: "ISO Decoding", input: "From: =?US-ASCII?Q?Keith_Moore?= \nTo: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= \nCC: =?ISO-8859-1?Q?Andr=E9?= Pirard \nSubject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=", expectedOutput: "From: Keith Moore \nTo: Keld Jørn Simonsen \nCC: André Pirard \nSubject: If you can read this you understand the example.", recipeConfig: [ { "op": "MIME Decoding", "args": [] } ] } ]); ================================================ FILE: tests/operations/tests/MS.mjs ================================================ /** * MS tests. * * @author bwhitn [brian.m.whitney@outlook.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Microsoft Script Decoder", input: "#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&@*eEI@#@&@#@&\x7fjm.raY 214Wv:zms/obI0xEAAA==^#~@", expectedOutput: "var my_msg = \"Testing <1><2><3>!\";\r\n\r\nWScript.Echo(my_msg);", recipeConfig: [ { "op": "Microsoft Script Decoder", "args": [] }, ], }, ]); ================================================ FILE: tests/operations/tests/Magic.mjs ================================================ /** * Magic tests. * * @author n1474335 [n1474335@gmail.com] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; import { JPG_RAW } from "../../samples/Images.mjs"; TestRegister.addTests([ { name: "Magic: nothing", input: "", expectedOutput: "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?", recipeConfig: [ { op: "Magic", args: [3, false, false] } ], }, { name: "Magic: hex, correct rank", input: "41 42 43 44 45", expectedMatch: /Properties[^#]+?#recipe=From_Hex\('Space'\)"/, recipeConfig: [ { op: "Magic", args: [3, false, false] } ], }, { name: "Magic: jpeg render", input: JPG_RAW, expectedMatch: /Render_Image\('Raw'\)/, recipeConfig: [ { op: "Magic", args: [3, false, false] } ], }, { name: "Magic: mojibake", 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", expectedMatch: /Быртрар коричневар лира прыгает через ленивую робаку./, recipeConfig: [ { op: "Magic", args: [1, true, false] } ], }, { name: "Magic: extensive language support, Yiddish", input: "די שנעל ברוין פאָקס דזשאַמפּס איבער די פויל הונט.", expectedMatch: /Yiddish/, recipeConfig: [ { op: "Magic", args: [1, false, true] } ], }, { name: "Magic Chain: Base64", input: "WkVkV2VtUkRRbnBrU0Vwd1ltMWpQUT09", 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\)/, recipeConfig: [ { op: "Magic", args: [3, false, false] } ], }, { name: "Magic Chain: Hex -> Hexdump -> Base64", input: "MDAwMDAwMDAgIDM3IDM0IDIwIDM2IDM1IDIwIDM3IDMzIDIwIDM3IDM0IDIwIDMyIDMwIDIwIDM3ICB8NzQgNjUgNzMgNzQgMjAgN3wKMDAwMDAwMTAgIDMzIDIwIDM3IDM0IDIwIDM3IDMyIDIwIDM2IDM5IDIwIDM2IDY1IDIwIDM2IDM3ICB8MyA3NCA3MiA2OSA2ZSA2N3w=", expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true,false\)\nFrom_Hexdump\(\)\nFrom_Hex\('Space'\)/, recipeConfig: [ { op: "Magic", args: [3, false, false] } ], }, { name: "Magic Chain: Charcode -> Octal -> Base32", input: "GY3SANRUEA2DAIBWGYQDMNJAGQYCANRXEA3DGIBUGAQDMNZAGY2CANBQEA3DEIBWGAQDIMBAGY3SANRTEA2DAIBWG4QDMNBAGQYCANRXEA3DEIBUGAQDMNRAG4YSANBQEA3DMIBRGQ2SANBQEA3DMIBWG4======", expectedMatch: /From_Base32\('A-Z2-7=',false\)\nFrom_Octal\('Space'\)\nFrom_Hex\('Space'\)/, recipeConfig: [ { op: "Magic", args: [3, false, false] } ], }, { name: "Magic Chain: Base64 output", input: "WkVkV2VtUkRRbnBrU0Vwd1ltMWpQUT09", expectedMatch: /test string/, recipeConfig: [ { op: "Magic", args: [3, false, false] } ], }, { name: "Magic Chain: Decimal -> Base32 -> Base32", input: "I5CVSVCNJFBFER2BLFJUCTKKKJDVKUKEINGUUV2FIFNFIRKJIJJEORJSKNAU2SSSI5MVCRCDJVFFKRKBLFKECTSKIFDUKWKUIFEUEUSHIFNFCPJ5HU6Q====", expectedMatch: /test string/, recipeConfig: [ { op: "Magic", args: [3, false, false] } ], }, { name: "Magic: Raw Inflate", 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", expectedMatch: /#recipe=Raw_Inflate(.|\n)+CyberChef is a simple, intuitive web app for carrying out all manner of /, recipeConfig: [ { op: "Magic", args: [1, false, false] } ] }, { name: "Magic: Defang IP Address, valid", input: "192.168.0.1", expectedMatch: /Properties[^#]+?#recipe=Defang_IP_Addresses\(\)"/, recipeConfig: [ { op: "Magic", args: [1, false, false] } ] }, { name: "Magic: Defang IP Address, invalid", input: "192.168.0.1.0", unexpectedMatch: /Defang_IP_Addresses/, recipeConfig: [ { op: "Magic", args: [1, false, false] } ] } ]); ================================================ FILE: tests/operations/tests/Media.mjs ================================================ /** * Media operation tests. * @author anthony-arnold [anthony.arnold@uqconnect.edu.au] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Play Media: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "Play Media", args: ["Raw"] } ] }, { name: "Play Media: raw wav", input: "52494646bcaf010057415645666d74201000000001000100401f0000401f0000010008006461746198af0100818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081818281807f82807f817e81808280827d8086817d80828184817f80807d847f7e7e8582837b81857e7e82867f7c7f857e7d838182837f83847b7c838082807c7d838481827e83827c7f848383807c7f7c80848085808180827e83827f7c82827c7f858183807c837b847f7f82827a85837a8287817f7882827f89837e7f877b7e808281817f7e807c827b7e7d8383838486838883837d7d7682797d847f7d868287867d83847c81807d7e818080828082828380807e7f827d8181807e828082858581808081807f7a7f81808183807d7c7f7f858180848484857f8082817e827b7e7f7f8385807f7f7d858183808081807f7f8183847f7e7d81828284857d7f7e8084868182837d7e7d7a7e7e8085837f80807b7f7b7a80827d7f817e8386838588868386868689868586838386868584807c7a78787875706e6c6a6c6d70757a7c7e7f80858b94a1a29a9996989a9e9ca09ea19caa8e554129243e4d475e7184a0ada2a4a0918179646167696f7b7a8390939ca4aab2af9c9691909aa2a3ae74321e162a4d5c506c88a5babca6a4a7a18c7a5e5c646f6a6b6d7f929a8f858182827e79849299a1abb2c7cc975b3826324447496788a5b8b6a9aaa9977b60494c586467707c949fa195938f8f847a717c88a0aab7b7d6a46045362f3c483d6089a1acb4a3a4a89570615652676f6d788f97a29c8e8992898381808393999da9bf8e515e504c4e5d4879929a90a1909e9c855c5b5f717679768b9b9f958c838c8d8c7f8782969ca797a963585f63425d60687e93818b99938c84786f7e7a7b7f86838a867f8486858899939697a7a6be6e465f6c4d465d678e948275958a867063688e8b828fa2a0988b7a7f7c737185898a8e90989c9b8c8c6e5f5a655159646f727f737382857c7f898a9aa4b1b6cac5d4957d695b403c4152", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["Space"] }, { op: "Play Media", args: ["Raw"] } ] }, { name: "Play Media: hex ogg", input: "4f676753000200000000000000003129000000000000642493e3011e01766f72626973000000000244ac0000000000008138010000000000b8014f676753000000000000000000003129000001000000a3565ae9102dffffffffffffffffffffffffffff2403766f726269731d000000586970682e4f7267206c6962566f726269732049203230303230373137000000000105766f726269732242435601000001009c739a318799629452892194de3968196394526929a55a4aa9a183166babbdf7de7befbdf7de7bef1d739431469552524aa99d739631471563524a89a5945642682184d662abbdf7de6befb5f6de7bef99424c29a41442084a281d538c29a494424a4a0825640e3ac61c538c52093dd65e6bccbdb6d87beda163ce39e61c534c4a6821740e3ae69c534c4a68a984524206a153d05289adf7de62ebb9a5da7bef81d0905500000100c040101ab20a00500000108aa1188a028486ac020032000004e0288ee3388ee23892623916101ab20a00000200100000c0900c4bb114cdd1244dd22ccf134dd3377dd3366d55d7755dd7755dd77520346415000001004040a719a61a20c28c6416080d590500200000004420c3140342435601000001000052243949a2e4a494520e836431492ae5a494521ec5e4514d3206a594524a29a594524a29a594520a8364394a2ae5a4945212a364314aaad4a494521ee5e4", expectedOutput: "", recipeConfig: [ { op: "Play Media", args: ["Hex"] } ] }, { name: "Play Media: base64 webm", 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==", expectedOutput: "", recipeConfig: [ { op: "Play Media", args: ["Base64"] } ] } ]); ================================================ FILE: tests/operations/tests/Modhex.mjs ================================================ /** * Modhex operation tests. * @author linuxgemini [ilteris@asenkron.com.tr] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "ASCII to Modhex stream", input: "aberystwyth", expectedOutput: "hbhdhgidikieifiiikifhj", recipeConfig: [ { "op": "To Modhex", "args": [ "None", 0 ] }, ] }, { name: "ASCII to Modhex with colon deliminator", input: "aberystwyth", expectedOutput: "hb:hd:hg:id:ik:ie:if:ii:ik:if:hj", recipeConfig: [ { "op": "To Modhex", "args": [ "Colon", 0 ] } ] }, { name: "Modhex stream to UTF-8", input: "uhkgkbuhkgkbugltlkugltkc", expectedOutput: "救救孩子", recipeConfig: [ { "op": "From Modhex", "args": [ "Auto" ] } ] }, { name: "Mixed case Modhex stream to UTF-8", input: "uhKGkbUHkgkBUGltlkugltkc", expectedOutput: "救救孩子", recipeConfig: [ { "op": "From Modhex", "args": [ "Auto" ] } ] }, { name: "Mutiline Modhex with comma to ASCII (Auto Mode)", input: "fk,dc,ie,hb,ii,dc,ht,ik,ie,hg,hr,hh,dc,ie,hk,\n\ if,if,hk,hu,hi,dc,hk,hu,dc,if,hj,hg,dc,he,id,\n\ hv,if,he,hj,dc,hv,hh,dc,if,hj,hg,dc,if,hj,hk,\n\ ie,dc,hh,hk,hi,dc,if,id,hg,hg,dr,dc,ie,if,hb,\n\ id,ih,hk,hu,hi,dc,if,hv,dc,hf,hg,hb,if,hj,dr,\n\ dc,hl,ig,ie,if,dc,hd,hg,he,hb,ig,ie,hg,dc,fk,\n\ dc,he,hv,ig,hr,hf,hu,di,if,dc,ht,hb,hn,hg,dc,\n\ ig,ic,dc,ht,ik,dc,ht,hk,hu,hf,dc,ii,hj,hk,he,\n\ hj,dc,hv,hh,dc,if,hj,hg,dc,hh,hk,hi,ie,dc,fk,\n\ dc,ii,hv,ig,hr,hf,dc,he,hj,hv,hv,ie,hg,du", 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.", recipeConfig: [ { "op": "From Modhex", "args": [ "Auto" ] } ] }, { name: "Mutiline Modhex with percent to ASCII (Percent Mode)", input: "fk%dc%ie%hb%ii%dc%ht%ik%ie%hg%hr%hh%dc%ie%hk%\n\ if%if%hk%hu%hi%dc%hk%hu%dc%if%hj%hg%dc%he%id%\n\ hv%if%he%hj%dc%hv%hh%dc%if%hj%hg%dc%if%hj%hk%\n\ ie%dc%hh%hk%hi%dc%if%id%hg%hg%dr%dc%ie%if%hb%\n\ id%ih%hk%hu%hi%dc%if%hv%dc%hf%hg%hb%if%hj%dr%\n\ dc%hl%ig%ie%if%dc%hd%hg%he%hb%ig%ie%hg%dc%fk%\n\ dc%he%hv%ig%hr%hf%hu%di%if%dc%ht%hb%hn%hg%dc%\n\ ig%ic%dc%ht%ik%dc%ht%hk%hu%hf%dc%ii%hj%hk%he%\n\ hj%dc%hv%hh%dc%if%hj%hg%dc%hh%hk%hi%ie%dc%fk%\n\ dc%ii%hv%ig%hr%hf%dc%he%hj%hv%hv%ie%hg%du", 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.", recipeConfig: [ { "op": "From Modhex", "args": [ "Percent" ] } ] }, { name: "Mutiline Modhex with semicolon to ASCII (Semi-colon Mode)", input: "fk;dc;ie;hb;ii;dc;ht;ik;ie;hg;hr;hh;dc;ie;hk;\n\ if;if;hk;hu;hi;dc;hk;hu;dc;if;hj;hg;dc;he;id;\n\ hv;if;he;hj;dc;hv;hh;dc;if;hj;hg;dc;if;hj;hk;\n\ ie;dc;hh;hk;hi;dc;if;id;hg;hg;dr;dc;ie;if;hb;\n\ id;ih;hk;hu;hi;dc;if;hv;dc;hf;hg;hb;if;hj;dr;\n\ dc;hl;ig;ie;if;dc;hd;hg;he;hb;ig;ie;hg;dc;fk;\n\ dc;he;hv;ig;hr;hf;hu;di;if;dc;ht;hb;hn;hg;dc;\n\ ig;ic;dc;ht;ik;dc;ht;hk;hu;hf;dc;ii;hj;hk;he;\n\ hj;dc;hv;hh;dc;if;hj;hg;dc;hh;hk;hi;ie;dc;fk;\n\ dc;ii;hv;ig;hr;hf;dc;he;hj;hv;hv;ie;hg;du", 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.", recipeConfig: [ { "op": "From Modhex", "args": [ "Semi-colon" ] } ] }, { name: "ASCII to Modhex with comma and line breaks", input: "aberystwyth", expectedOutput: "hb,hd,hg,id,\nik,ie,if,ii,\nik,if,hj", recipeConfig: [ { "op": "To Modhex", "args": [ "Comma", 4 ] } ] }, ]); ================================================ FILE: tests/operations/tests/MorseCode.mjs ================================================ /** * Base58 tests. * * @author tlwr [toby@toby.codes] * * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "To Morse Code: 'SOS'", input: "SOS", expectedOutput: "... --- ...", recipeConfig: [ { op: "To Morse Code", args: ["-/.", "Space", "Line feed"], }, ], }, { name: "From Morse Code '... --- ...'", input: "... --- ...", expectedOutput: "SOS", recipeConfig: [ { op: "From Morse Code", args: ["Space", "Line feed"], }, ], }, ]); ================================================ FILE: tests/operations/tests/MultipleBombe.mjs ================================================ /** * Bombe machine tests. * @author s2224834 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Multi-Bombe: 3 rotor", input: "BBYFLTHHYIJQAYBBYS", expectedMatch: /LGA<\/td> {2}SS<\/td> {2}VFISUSGTKSTMPSUNAK<\/td>/, recipeConfig: [ { "op": "Multiple Bombe", "args": [ // I, II and III "User defined", "EKMFLGDQVZNTOWYHXUSPAIBRCJLHSC<\/td>SS<\/td>HHHSSSGQUUQPKSEKWK<\/td>/, recipeConfig: [ { "op": "Multiple Bombe", "args": [ // I, II and III "User defined", "EKMFLGDQVZNTOWYHXUSPAIBRCJ Bob", input: `-----BEGIN PGP MESSAGE----- Version: Keybase OpenPGP v2.0.77 Comment: https://keybase.io/crypto wTwDhHCB1rfyD4MBAX9ld8xGcf2v+X+pwINN0R0TvkWxNesKOQIKPV01AH8JG0J+ +yFqLXqDHgYSLANNamfSwQoBOTWuh/5V6gpiXVm2oLHPv997AtoD/kVQrqylF5Xo HUsqPGtSgBA5WPX8tMoHKuqWxEy9FviLnIv73OZN0Ph70uo2E+QIv0Qx27znK0Jy KDSERvcldgShmVbDP3Pxtxkfr9xa2gar5f0OPovOmKGsTGciQJqPkclRwzIXg12L hyd2ElYOMf6vg/yOc06sX4Ih1Tn6JkYqMVJydykMv3g4Z8OXTfwrMLxwO1n3ZB/T OLdhBdsnREnyCqntBVjMKoRTQhfwq48n7b6caZ+aCPISdDIyDKBpxEzXaNBeEY2V GCqORM9WhsQ4A6pAx2SP694qH5vgOwrYrgeOU17oK++mzd1GyU2CXoFi73/PANJD TdC3hGr+S4XeuqZ368QG1cBWhNybsOu5sM2YbArb71ZMYuLDp+VolJbEkVf4c/dD pVEOaX39NVKe6HcpOiw+CFO6GEkQqCXNprWK6ivBHzkAlF2pjjqlS6qhWxFPicSD +1ZKM1fmZu99bhTmdqE3MJx//QMu7mvlHaM85OQkWhWPBxGw/60GVBX9YtvUtfMS IOE1W/Zqmqzq+4frwnzWwYv9/U1RwIs/qlFVnzliREOzW+om8EncSSd7fQ== =fEAT -----END PGP MESSAGE----- `, expectedOutput: `Signed by PGP key ID: DF98E485 PGP fingerprint: e94e06dd0b3744a0e970de9d84246548df98e485 Signed on Tue, 29 May 2018 15:44:52 GMT ---------------------------------- ${UTF8_TEXT}`, recipeConfig: [ { "op": "PGP Decrypt and Verify", "args": [ALICE_PUBLIC, BOB_PRIVATE, ""] } ] }, { name: "PGP Decrypt: ASCII, Alice -> Bob", input: `-----BEGIN PGP MESSAGE----- Version: Keybase OpenPGP v2.0.77 Comment: https://keybase.io/crypto wYwDPtlTQFIjCzoBBACSlbN7tmQVxR5ZD0rvCwXUkxO3RU8WgBkkmrTCUs9a+xrS F9HuKcpX/N6XrwTXyuX3BN2tGys4zd6nHV8jYqBoIyWJsWe3viTa1dh/x4183+GP fP61gizi3pj0gi2vfGnMhnThbdiO32PVKAeHLHBK+r3XlXZ0kzZCQKRgd55yr9Kk Aa4SR+qpvtdobkDzbnbhcPLR6CQ8TMjTiNXEpgTc1i0JcP8jaMVFzBt8qgmDMdqU H2qMY1O7hezH3fp+EZzCAccJMtK7VPk13WAgMRH22HirG4aK1i75IVOtjBgObzDh 8zKua7QLi6wJD/AtQ+D3/NgVpzoXwdoLvTjEcAyy+YWNWkJF/jvx3XV1Q/Fz7sHJ /bspORYvbi591S4U0m4pikwiOZk= =AVb/ -----END PGP MESSAGE-----`, expectedOutput: ASCII_TEXT, recipeConfig: [ { "op": "PGP Decrypt", "args": [ALICE_PRIVATE, ""] } ] }, { name: "PGP Verify: ASCII, Alice", input: `-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools. -----BEGIN PGP SIGNATURE----- iLMEAQEIAB0WIQRLbJy6MLpYOr9qojE+2VNAUiMLOgUCXRTsvwAKCRA+2VNAUiML OuaHBADMMNtsuN92Fb+UrDimsv6TDQpbJhDkwp9kZdKYP5HAmSYAhXBG7N+YCMw+ v2FSpUu9jJiPBm1K1SEwLufQVexoRv6RsBNolRFB07sArau0s0DnIXUchCZWvyTP 1KsjBnDr84U2b11H58g4DlTT4gQrz30rFuHz9AGmPAtDHbSXIA== =vnk/ -----END PGP SIGNATURE-----`, expectedOutput: `Signed by PGP key ID: DF98E485 PGP fingerprint: e94e06dd0b3744a0e970de9d84246548df98e485 Signed on Thu, 27 Jun 2019 16:20:15 GMT ---------------------------------- A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools.`, recipeConfig: [ { "op": "PGP Verify", "args": [ALICE_PUBLIC] } ] } ]); ================================================ FILE: tests/operations/tests/PHP.mjs ================================================ /** * PHP tests. * * @author Jarmo van Lenthe * * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "PHP Deserialize empty array", input: "a:0:{}", expectedOutput: "{}", recipeConfig: [ { op: "PHP Deserialize", args: [true], }, ], }, { name: "PHP Deserialize integer", input: "i:10;", expectedOutput: "10", recipeConfig: [ { op: "PHP Deserialize", args: [true], }, ], }, { name: "PHP Deserialize string", input: "s:17:\"PHP Serialization\";", expectedOutput: "\"PHP Serialization\"", recipeConfig: [ { op: "PHP Deserialize", args: [true], }, ], }, { name: "PHP Deserialize array (JSON)", input: "a:2:{s:1:\"a\";i:10;i:0;a:1:{s:2:\"ab\";b:1;}}", expectedOutput: "{\"a\": 10,\"0\": {\"ab\": true}}", recipeConfig: [ { op: "PHP Deserialize", args: [true], }, ], }, { name: "PHP Deserialize array (non-JSON)", input: "a:2:{s:1:\"a\";i:10;i:0;a:1:{s:2:\"ab\";b:1;}}", expectedOutput: "{\"a\": 10,0: {\"ab\": true}}", recipeConfig: [ { op: "PHP Deserialize", args: [false], }, ], }, ]); ================================================ FILE: tests/operations/tests/PHPSerialize.mjs ================================================ /** * PHP Serialization tests. * * @author brun0ne [brunonblok@gmail.com] * * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "PHP Serialize empty array", input: "[]", expectedOutput: "a:0:{}", recipeConfig: [ { op: "PHP Serialize", args: [] } ] }, { name: "PHP Serialize empty object", input: "{}", expectedOutput: "a:0:{}", recipeConfig: [ { op: "PHP Serialize", args: [] } ] }, { name: "PHP Serialize null", input: "null", expectedOutput: "N;", recipeConfig: [ { op: "PHP Serialize", args: [] } ] }, { name: "PHP Serialize integer", input: "10", expectedOutput: "i:10;", recipeConfig: [ { op: "PHP Serialize", args: [] } ] }, { name: "PHP Serialize float", input: "14.523", expectedOutput: "d:14.523;", recipeConfig: [ { op: "PHP Serialize", args: [] } ] }, { name: "PHP Serialize boolean", input: "[true, false]", expectedOutput: "a:2:{i:0;b:1;i:1;b:0;}", recipeConfig: [ { op: "PHP Serialize", args: [] } ] }, { name: "PHP Serialize string", input: "\"Test string to serialize\"", expectedOutput: "s:24:\"Test string to serialize\";", recipeConfig: [ { op: "PHP Serialize", args: [] } ] }, { name: "PHP Serialize object", input: "{\"a\": 10,\"0\": {\"ab\": true}}", expectedOutput: "a:2:{s:1:\"0\";a:1:{s:2:\"ab\";b:1;}s:1:\"a\";i:10;}", recipeConfig: [ { op: "PHP Serialize", args: [] } ] }, { name: "PHP Serialize array", input: "[1,\"abc\",true,{\"x\":1,\"y\":2}]", 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;}}", recipeConfig: [ { op: "PHP Serialize", args: [] } ] } ]); ================================================ FILE: tests/operations/tests/ParseCSR.mjs ================================================ /** * Parse CSR tests. * * @author jkataja * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; // openssl req -newkey rsa:1024 -keyout test-rsa-1024.key -out test-rsa-1024.csr \ // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ // -addext "basicConstraints = critical,CA:FALSE" \ // -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_RSA_1024 = `-----BEGIN CERTIFICATE REQUEST----- MIICHzCCAYgCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEF AAOBjQAwgYkCgYEArrTrLI6FkzjX8FZfclt2ox1Dz7KRwt5f6ffZic7twLAKJ4ao /H3APjwoFVUXGjiNj/XF2RlId4UxB1b6CgWjujBb9W51rTdvfWLyAHsrLcptpVz+ V9Y8X9kEFCRGGDyG5+X+Nu6COzTpUPDj4bIIX/uPk3fDYDEqLClVy8/VS48CAwEA AaBtMGsGCSqGSIb3DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3 dy5leGFtcGxlLmNvbTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNV HSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOBgQB0mUlPgt6pt/kjD0pz OUNk5e9nBFQYQGuGIHGYbPX3mi4Wd9vUCdPixtPSTunHWs2cxX2nM8+MdcNTY+7Q NFgFNIvSXhbqMYoHAAApMHJOxiWpBFdYKp3tESnlgh2lUh7lQtmOjD4a1dzfU8PU oViyp+UJGasN2WRd+4VtaPw64w== -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_RSA_1024 = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: RSA Length: 1024 bits Modulus: 00:ae:b4:eb:2c:8e:85:93:38:d7:f0:56:5f:72:5b:76: a3:1d:43:cf:b2:91:c2:de:5f:e9:f7:d9:89:ce:ed:c0: b0:0a:27:86:a8:fc:7d:c0:3e:3c:28:15:55:17:1a:38: 8d:8f:f5:c5:d9:19:48:77:85:31:07:56:fa:0a:05:a3: ba:30:5b:f5:6e:75:ad:37:6f:7d:62:f2:00:7b:2b:2d: ca:6d:a5:5c:fe:57:d6:3c:5f:d9:04:14:24:46:18:3c: 86:e7:e5:fe:36:ee:82:3b:34:e9:50:f0:e3:e1:b2:08: 5f:fb:8f:93:77:c3:60:31:2a:2c:29:55:cb:cf:d5:4b: 8f Exponent: 65537 (0x10001) Signature Algorithm: SHA256withRSA Signature: 74:99:49:4f:82:de:a9:b7:f9:23:0f:4a:73:39:43:64: e5:ef:67:04:54:18:40:6b:86:20:71:98:6c:f5:f7:9a: 2e:16:77:db:d4:09:d3:e2:c6:d3:d2:4e:e9:c7:5a:cd: 9c:c5:7d:a7:33:cf:8c:75:c3:53:63:ee:d0:34:58:05: 34:8b:d2:5e:16:ea:31:8a:07:00:00:29:30:72:4e:c6: 25:a9:04:57:58:2a:9d:ed:11:29:e5:82:1d:a5:52:1e: e5:42:d9:8e:8c:3e:1a:d5:dc:df:53:c3:d4:a1:58:b2: a7:e5:09:19:ab:0d:d9:64:5d:fb:85:6d:68:fc:3a:e3 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com`; // openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \ // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ // -addext "basicConstraints = critical,CA:FALSE" \ // -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_RSA_2048 = `-----BEGIN CERTIFICATE REQUEST----- MIIDJDCCAgwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAKPogLmWPuK/IGdct2v/3MFKVaVeKp2Hl5at/zDFLCAe 51bwh7BqNVJEci4ApwlXA1WVmQPBFBJlYwQZVjz5UAN2CmNHxud5nV03YmZ2/Iml RzpKcZMPqU+liJCC04L+XIbOdx+Vz52dF++Cc+FuSFq803yW+qefK8JsJNO9KuPx RLYKSAADa9MIJisru1PzcBAOcimOmNnFWuo+LKsd4lU30OExDdKHwtyt62Mj1c3o lO1JjvkjtWWjwHI+0EgTjvkeXlcUYZvvLlysdKERMRozvMTGqqoHWCgWl+Rq9Z6P TgNsRO4CKug1Zwmh8y6acZ7sYb/dar8HOeqJnc0pCv8CAwEAAaBtMGsGCSqGSIb3 DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNv bTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF BQcDATANBgkqhkiG9w0BAQsFAAOCAQEAG0cjfRBY1pBzu+jf7yMQrK5mQrh72air VuXHmochmyUxyt0G7ovnNhKEr+X9snShJLi5qlyvnb2roiwlCmuwGIZxErN1svQL Z3kQNZgH+Vyu5IRL2DlPs5AAxVmzPpbnbXNhMHyAK/ziLcU031O1PoCpxwfvPsjW HWOCjbZUVaJnxdp8AHqImoGAiVhJwc37feFvb2UQlLedUypQkPg/poNWduaRDoj8 m9cpVxuxGLtONBnohzohnFECytSXWEXPIj8L9SpYK97G02nJYYCAcb5BF11Alfux sNxtsr6zgPaLRrvOBT11WxJVKerbhfezAJ3naem1eM3VLxCGWwMwxg== -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_RSA_2048 = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: RSA Length: 2048 bits Modulus: 00:a3:e8:80:b9:96:3e:e2:bf:20:67:5c:b7:6b:ff:dc: c1:4a:55:a5:5e:2a:9d:87:97:96:ad:ff:30:c5:2c:20: 1e:e7:56:f0:87:b0:6a:35:52:44:72:2e:00:a7:09:57: 03:55:95:99:03:c1:14:12:65:63:04:19:56:3c:f9:50: 03:76:0a:63:47:c6:e7:79:9d:5d:37:62:66:76:fc:89: a5:47:3a:4a:71:93:0f:a9:4f:a5:88:90:82:d3:82:fe: 5c:86:ce:77:1f:95:cf:9d:9d:17:ef:82:73:e1:6e:48: 5a:bc:d3:7c:96:fa:a7:9f:2b:c2:6c:24:d3:bd:2a:e3: f1:44:b6:0a:48:00:03:6b:d3:08:26:2b:2b:bb:53:f3: 70:10:0e:72:29:8e:98:d9:c5:5a:ea:3e:2c:ab:1d:e2: 55:37:d0:e1:31:0d:d2:87:c2:dc:ad:eb:63:23:d5:cd: e8:94:ed:49:8e:f9:23:b5:65:a3:c0:72:3e:d0:48:13: 8e:f9:1e:5e:57:14:61:9b:ef:2e:5c:ac:74:a1:11:31: 1a:33:bc:c4:c6:aa:aa:07:58:28:16:97:e4:6a:f5:9e: 8f:4e:03:6c:44:ee:02:2a:e8:35:67:09:a1:f3:2e:9a: 71:9e:ec:61:bf:dd:6a:bf:07:39:ea:89:9d:cd:29:0a: ff Exponent: 65537 (0x10001) Signature Algorithm: SHA256withRSA Signature: 1b:47:23:7d:10:58:d6:90:73:bb:e8:df:ef:23:10:ac: ae:66:42:b8:7b:d9:a8:ab:56:e5:c7:9a:87:21:9b:25: 31:ca:dd:06:ee:8b:e7:36:12:84:af:e5:fd:b2:74:a1: 24:b8:b9:aa:5c:af:9d:bd:ab:a2:2c:25:0a:6b:b0:18: 86:71:12:b3:75:b2:f4:0b:67:79:10:35:98:07:f9:5c: ae:e4:84:4b:d8:39:4f:b3:90:00:c5:59:b3:3e:96:e7: 6d:73:61:30:7c:80:2b:fc:e2:2d:c5:34:df:53:b5:3e: 80:a9:c7:07:ef:3e:c8:d6:1d:63:82:8d:b6:54:55:a2: 67:c5:da:7c:00:7a:88:9a:81:80:89:58:49:c1:cd:fb: 7d:e1:6f:6f:65:10:94:b7:9d:53:2a:50:90:f8:3f:a6: 83:56:76:e6:91:0e:88:fc:9b:d7:29:57:1b:b1:18:bb: 4e:34:19:e8:87:3a:21:9c:51:02:ca:d4:97:58:45:cf: 22:3f:0b:f5:2a:58:2b:de:c6:d3:69:c9:61:80:80:71: be:41:17:5d:40:95:fb:b1:b0:dc:6d:b2:be:b3:80:f6: 8b:46:bb:ce:05:3d:75:5b:12:55:29:ea:db:85:f7:b3: 00:9d:e7:69:e9:b5:78:cd:d5:2f:10:86:5b:03:30:c6 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com`; // openssl genpkey -genparam -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out test-ec-param.pem // openssl req -newkey ec:test-ec-param.pem -keyout test-ec.key -out test-ec.csr \ // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ // -addext "basicConstraints = critical,CA:FALSE" \ // -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_EC_P256 = `-----BEGIN CERTIFICATE REQUEST----- MIIBmzCCAUECAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqG SM49AwEHA0IABAmpYXNh+L9E0Q3sLhrO+MF1XgKCfqJntrOyIkrGwoiQftHbJWTA 6duxQhU/3d9B+SN/ibeKY+xeiNBrs2eTYZ6gbTBrBgkqhkiG9w0BCQ4xXjBcMCcG A1UdEQQgMB6CC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb20wDAYDVR0TAQH/ BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCgYIKoZI zj0EAwIDSAAwRQIgQkum/qaLzE3QZ3WD00uLpalUn113FObd7rM5Mr3HQwQCIQCr 7OjzYI9v7qIJp/E9N16XfJN87G2ZVIZ4FuPXVjokCQ== -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_EC_P256 = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: ECDSA Length: 256 bits Pub: 04:09:a9:61:73:61:f8:bf:44:d1:0d:ec:2e:1a:ce:f8: c1:75:5e:02:82:7e:a2:67:b6:b3:b2:22:4a:c6:c2:88: 90:7e:d1:db:25:64:c0:e9:db:b1:42:15:3f:dd:df:41: f9:23:7f:89:b7:8a:63:ec:5e:88:d0:6b:b3:67:93:61: 9e ASN1 OID: secp256r1 NIST CURVE: P-256 Signature Algorithm: SHA256withECDSA Signature: 30:45:02:20:42:4b:a6:fe:a6:8b:cc:4d:d0:67:75:83: d3:4b:8b:a5:a9:54:9f:5d:77:14:e6:dd:ee:b3:39:32: bd:c7:43:04:02:21:00:ab:ec:e8:f3:60:8f:6f:ee:a2: 09:a7:f1:3d:37:5e:97:7c:93:7c:ec:6d:99:54:86:78: 16:e3:d7:56:3a:24:09 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com`; // openssl ecparam -name secp384r1 -genkey -noout -out test-ec-key.pem // openssl req -new -key test-ec-key.pem -out test-ec.csr // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" // -addext "basicConstraints = critical,CA:FALSE" // -addext "keyUsage = critical,digitalSignature,keyEncipherment" // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_EC_P384 = `-----BEGIN CERTIFICATE REQUEST----- MIIB2TCCAV4CAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTB2MBAGByqGSM49AgEGBSuB BAAiA2IABE3rpRO164NtXx2kYMP1zlN7YgHEincO4YgwoyAYyJm3LwcbR+XyKg6A /i+DUaGWa2FQ+f8w8VmEUFAgLozVxwnntPOCSODrXAQwJFPLCqs7m3o8OuzU3t07 POGhPtj7f6BtMGsGCSqGSIb3DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5j b22CD3d3dy5leGFtcGxlLmNvbTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF oDATBgNVHSUEDDAKBggrBgEFBQcDATAKBggqhkjOPQQDAgNpADBmAjEAlq7RaEXU aNHEC+qfuIitonWHOatm+qiiaNSh80QjLw5P1rszg9yQQigHd8cD7I4DAjEAzmo1 DLpcESwZCBrh3sPflDA38TZjoedRNeWcVxdn1QmwDWMeprD/zgPAey8GOmyj -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_EC_P384 = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: ECDSA Length: 384 bits Pub: 04:4d:eb:a5:13:b5:eb:83:6d:5f:1d:a4:60:c3:f5:ce: 53:7b:62:01:c4:8a:77:0e:e1:88:30:a3:20:18:c8:99: b7:2f:07:1b:47:e5:f2:2a:0e:80:fe:2f:83:51:a1:96: 6b:61:50:f9:ff:30:f1:59:84:50:50:20:2e:8c:d5:c7: 09:e7:b4:f3:82:48:e0:eb:5c:04:30:24:53:cb:0a:ab: 3b:9b:7a:3c:3a:ec:d4:de:dd:3b:3c:e1:a1:3e:d8:fb: 7f ASN1 OID: secp384r1 NIST CURVE: P-384 Signature Algorithm: SHA256withECDSA Signature: 30:66:02:31:00:96:ae:d1:68:45:d4:68:d1:c4:0b:ea: 9f:b8:88:ad:a2:75:87:39:ab:66:fa:a8:a2:68:d4:a1: f3:44:23:2f:0e:4f:d6:bb:33:83:dc:90:42:28:07:77: c7:03:ec:8e:03:02:31:00:ce:6a:35:0c:ba:5c:11:2c: 19:08:1a:e1:de:c3:df:94:30:37:f1:36:63:a1:e7:51: 35:e5:9c:57:17:67:d5:09:b0:0d:63:1e:a6:b0:ff:ce: 03:c0:7b:2f:06:3a:6c:a3 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com`; // openssl ecparam -name secp521r1 -genkey -noout -out test-ec-key.pem // openssl req -new -key test-ec-key.pem -out test-ec.csr // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" // -addext "basicConstraints = critical,CA:FALSE" // -addext "keyUsage = critical,digitalSignature,keyEncipherment" // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_EC_P521 = `-----BEGIN CERTIFICATE REQUEST----- MIICIjCCAYQCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCBmzAQBgcqhkjOPQIBBgUr gQQAIwOBhgAEAKf5BRB57svfglRz5dM0bnJAnieMFjNjOFca5/pJ2bOpORkp9Uol x//mHY5WOMYYC/xvM5lJRcmUnL791zQ6rf6pAD/CrEpDF2svae6e5nA/fN2XsB98 xjmkTpYZVC5nFT83Ceo9J0kHbvliYlAMsEOO60qGghyWV7myiDgORfE+POU3oG0w awYJKoZIhvcNAQkOMV4wXDAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4 YW1wbGUuY29tMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM MAoGCCsGAQUFBwMBMAoGCCqGSM49BAMCA4GLADCBhwJBDeIpSuvIT+kiE0ZnJwPS DVik93CLqjFm5Ieq02d81GwusSgAA82WlZZVZRsTEjkZXtk96zMBnh5/uxk+wN+j +PoCQgEDmXREwi0BPkHj6QlktE+7SLELVkrd75D9mfw/SV6ZJiLiLIT9yeoA0Zon uhcl2rK/DLQutuJF6JIBe5s7lieKfQ== -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_EC_P521 = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: ECDSA Length: 521 bits Pub: 04:00:a7:f9:05:10:79:ee:cb:df:82:54:73:e5:d3:34: 6e:72:40:9e:27:8c:16:33:63:38:57:1a:e7:fa:49:d9: b3:a9:39:19:29:f5:4a:25:c7:ff:e6:1d:8e:56:38:c6: 18:0b:fc:6f:33:99:49:45:c9:94:9c:be:fd:d7:34:3a: ad:fe:a9:00:3f:c2:ac:4a:43:17:6b:2f:69:ee:9e:e6: 70:3f:7c:dd:97:b0:1f:7c:c6:39:a4:4e:96:19:54:2e: 67:15:3f:37:09:ea:3d:27:49:07:6e:f9:62:62:50:0c: b0:43:8e:eb:4a:86:82:1c:96:57:b9:b2:88:38:0e:45: f1:3e:3c:e5:37 ASN1 OID: secp521r1 NIST CURVE: P-521 Signature Algorithm: SHA256withECDSA Signature: 30:81:87:02:41:0d:e2:29:4a:eb:c8:4f:e9:22:13:46: 67:27:03:d2:0d:58:a4:f7:70:8b:aa:31:66:e4:87:aa: d3:67:7c:d4:6c:2e:b1:28:00:03:cd:96:95:96:55:65: 1b:13:12:39:19:5e:d9:3d:eb:33:01:9e:1e:7f:bb:19: 3e:c0:df:a3:f8:fa:02:42:01:03:99:74:44:c2:2d:01: 3e:41:e3:e9:09:64:b4:4f:bb:48:b1:0b:56:4a:dd:ef: 90:fd:99:fc:3f:49:5e:99:26:22:e2:2c:84:fd:c9:ea: 00:d1:9a:27:ba:17:25:da:b2:bf:0c:b4:2e:b6:e2:45: e8:92:01:7b:9b:3b:96:27:8a:7d Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com`; // openssl dsaparam -out dsaparam.pem 1024 // openssl gendsa -out dsakey.pem dsaparam.pem // openssl req -new -key dsakey.pem -out test-dsa.csr \ // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ // -addext "basicConstraints = critical,CA:FALSE" \ // -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_DSA_1024 = `-----BEGIN CERTIFICATE REQUEST----- MIIC/jCCAqoCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAcAwggE0BgcqhkjOOAQB MIIBJwKBgQD8vvCmdM8wttdbq3kWigTEnnug4+2SLMl2RNXrlCQjmuZc7tGMyP1u gsSc9Pxd/tMrPKRawFP5SvUOkZ4cIrujdJVTb/hlfnGH4cWACe8EupwRzoqwZB1x awiHFzL9G6Go0HOy7bSbRdxBIYu46fnxNsDFf7lMlcBOKdq4Y12kvwIdAN4/vtK9 KxhQfcrrzHsPXW+/xW0CMfr+NQir8PkCgYEAiNdM7IRZhXPaGRtGDpepSoRAf4uQ LWY9q+vFUx4fVRSSgwKBKLjW+BvzE2eJq0pXv7O09QHOghtcwzY3UrdN952sjUkJ LItt+5FxB7/JqCBPRrrVsyGEjR3+WbeI3wl6OvQFxm/OTNTTkemFdAfpT/YDSw+n 1xLODTfegT/oyOoDgYUAAoGBAMz15lRPVAj8cje3ShbuACHPVE85d0Tk0Dw9qUcQ NCNS6A3STSbUiLGKeiRMGg2v/HM9ivV8tq1rywmgBAwtidcQ6P5yqYSZs6z3x9xZ OzeQ5jXftBQ1GXeU8zi1fC99inFGNixbPFVIz4/KiV0+So44n9ki2ylhbz0YQtpU wMF+oG0wawYJKoZIhvcNAQkOMV4wXDAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIP d3d3LmV4YW1wbGUuY29tMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMG A1UdJQQMMAoGCCsGAQUFBwMBMAsGCWCGSAFlAwQDAgNBADA+Ah0AkTogUUyKE5v9 ezKrOKpP07i2E9Zz0n/yjIvw4wIdAMB5yVMOEgI877vOFQ7zzf7oDR9eJMYlf4QV 2sQ= -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_DSA_1024 = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: DSA Length: 1024 bits Pub: 00:cc:f5:e6:54:4f:54:08:fc:72:37:b7:4a:16:ee:00: 21:cf:54:4f:39:77:44:e4:d0:3c:3d:a9:47:10:34:23: 52:e8:0d:d2:4d:26:d4:88:b1:8a:7a:24:4c:1a:0d:af: fc:73:3d:8a:f5:7c:b6:ad:6b:cb:09:a0:04:0c:2d:89: d7:10:e8:fe:72:a9:84:99:b3:ac:f7:c7:dc:59:3b:37: 90:e6:35:df:b4:14:35:19:77:94:f3:38:b5:7c:2f:7d: 8a:71:46:36:2c:5b:3c:55:48:cf:8f:ca:89:5d:3e:4a: 8e:38:9f:d9:22:db:29:61:6f:3d:18:42:da:54:c0:c1: 7e P: 00:fc:be:f0:a6:74:cf:30:b6:d7:5b:ab:79:16:8a:04: c4:9e:7b:a0:e3:ed:92:2c:c9:76:44:d5:eb:94:24:23: 9a:e6:5c:ee:d1:8c:c8:fd:6e:82:c4:9c:f4:fc:5d:fe: d3:2b:3c:a4:5a:c0:53:f9:4a:f5:0e:91:9e:1c:22:bb: a3:74:95:53:6f:f8:65:7e:71:87:e1:c5:80:09:ef:04: ba:9c:11:ce:8a:b0:64:1d:71:6b:08:87:17:32:fd:1b: a1:a8:d0:73:b2:ed:b4:9b:45:dc:41:21:8b:b8:e9:f9: f1:36:c0:c5:7f:b9:4c:95:c0:4e:29:da:b8:63:5d:a4: bf Q: 00:de:3f:be:d2:bd:2b:18:50:7d:ca:eb:cc:7b:0f:5d: 6f:bf:c5:6d:02:31:fa:fe:35:08:ab:f0:f9 G: 00:88:d7:4c:ec:84:59:85:73:da:19:1b:46:0e:97:a9: 4a:84:40:7f:8b:90:2d:66:3d:ab:eb:c5:53:1e:1f:55: 14:92:83:02:81:28:b8:d6:f8:1b:f3:13:67:89:ab:4a: 57:bf:b3:b4:f5:01:ce:82:1b:5c:c3:36:37:52:b7:4d: f7:9d:ac:8d:49:09:2c:8b:6d:fb:91:71:07:bf:c9:a8: 20:4f:46:ba:d5:b3:21:84:8d:1d:fe:59:b7:88:df:09: 7a:3a:f4:05:c6:6f:ce:4c:d4:d3:91:e9:85:74:07:e9: 4f:f6:03:4b:0f:a7:d7:12:ce:0d:37:de:81:3f:e8:c8: ea Signature Algorithm: SHA256withDSA Signature: R: 00:91:3a:20:51:4c:8a:13:9b:fd:7b:32:ab:38:aa:4f: d3:b8:b6:13:d6:73:d2:7f:f2:8c:8b:f0:e3 S: 00:c0:79:c9:53:0e:12:02:3c:ef:bb:ce:15:0e:f3:cd: fe:e8:0d:1f:5e:24:c6:25:7f:84:15:da:c4 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com`; // openssl dsaparam -out dsaparam.pem 2048 // openssl gendsa -out dsakey.pem dsaparam.pem // openssl req -new -key dsakey.pem -out test-dsa.csr \ // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ // -addext "basicConstraints = critical,CA:FALSE" \ // -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_DSA_2048 = `-----BEGIN CERTIFICATE REQUEST----- MIIEfzCCBCwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCA0IwggI1BgcqhkjOOAQB MIICKAKCAQEAsvoKmCHcR2y8qQ/kpBHOvlaGifq//F/0zhWSpfjvwqI3g2EjqXL7 rCYyu9wxoogODo6Dnenxfw1xp3ZIJNCtfrSJyt0AudjOedtVWMSnTndoQVQtYSI0 mmrBAqFL26i1bmEMxsd6pz2nU3p8yGY/wpYiWwyy+/TZv8a2t58owpw9Qkm4cX4E Po3ih/XbN6eooOx9ZaErcS9mg3UvwQDm0VYD3ZjSeqwP7YWGyhq7gPJsEiMrft12 1SjyNz8rkhXzqZFRujjmfTT5dpCC/Z4d7/ZE30tbqHaNDM+YwBrb/aL7PnoWs847 VpjCVxmVmgIPoMHlTbg29RsIUoFlFScaUQIdAMGwwpzilrReaEqcoX7PY5u4vtV0 5zuiVIqkdBMCggEAQZhk5qdAYoMvZhPi5TOgysTzQE1FeAEtgypxZI65TpwO/JOr AX9vYZ/qCYX/ncj455qiPZenl59lo/iQPzhJUubuCevPWJ3dsKRbAyL/5NCwifnf YBMJGj0UFGL4ekVV0emLL9H5eqYz64w0eV2Sp40O8yCu0qr7QTi3zpqzJZ43E+26 Z9bgR6c1lmgKW2QN72PHwMlTlq0O6mN+eikEWoGr09JWpXMThZemAO2mHLAiq6ju 0+zduzWZyjZPZA1B4XUlTgCtzHveYpUzZ1NhZyM8jcGFOmmZWAFNwt03bq9/Ma0q 3jB0Dyz7IDGm8D6Y770wJRP3jf7iCVYt8jB49gOCAQUAAoIBACnVv+1ROrUiHAwn xXGlsZdTEYZfWbE8Cter15JNNqh/Z1cdIp9m1t/rVF69nSWQvrvLeFo5p5mGxK8r IKHTZTaAn6uO6PcNJc6iB7fS15L4uiB7p73MdjE+3PcYMbhttDlexdm6QxsmCP1F 3LYW3Uh879AURWZwPH3z4NZL2u1AFSyS1vQhtiCmztq94QwhjoDf9anFR8q05dAC juPlKYEIhMsoq+r/l/kOM1UghhXX6BmeF8R9hhW1p4Rv+gyAgbYjowJFtZnwE5p0 OYLJzSQWjFMYEzHAoH8J4+D5okt4IXEd0BDxLBkm1WonIxYL/NL95p3qXpgUXqRX M9spEzWgbTBrBgkqhkiG9w0BCQ4xXjBcMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29t gg93d3cuZXhhbXBsZS5jb20wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAw EwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYJYIZIAWUDBAMCA0AAMD0CHQCyrstoqfvs MCfsZUeycKrKQmAJAHxuoGPCKl7yAhwhNH9RNxBm5roO2U901BeF2p0pT410ghH8 oA+F -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_DSA_2048 = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: DSA Length: 2048 bits Pub: 29:d5:bf:ed:51:3a:b5:22:1c:0c:27:c5:71:a5:b1:97: 53:11:86:5f:59:b1:3c:0a:d7:ab:d7:92:4d:36:a8:7f: 67:57:1d:22:9f:66:d6:df:eb:54:5e:bd:9d:25:90:be: bb:cb:78:5a:39:a7:99:86:c4:af:2b:20:a1:d3:65:36: 80:9f:ab:8e:e8:f7:0d:25:ce:a2:07:b7:d2:d7:92:f8: ba:20:7b:a7:bd:cc:76:31:3e:dc:f7:18:31:b8:6d:b4: 39:5e:c5:d9:ba:43:1b:26:08:fd:45:dc:b6:16:dd:48: 7c:ef:d0:14:45:66:70:3c:7d:f3:e0:d6:4b:da:ed:40: 15:2c:92:d6:f4:21:b6:20:a6:ce:da:bd:e1:0c:21:8e: 80:df:f5:a9:c5:47:ca:b4:e5:d0:02:8e:e3:e5:29:81: 08:84:cb:28:ab:ea:ff:97:f9:0e:33:55:20:86:15:d7: e8:19:9e:17:c4:7d:86:15:b5:a7:84:6f:fa:0c:80:81: b6:23:a3:02:45:b5:99:f0:13:9a:74:39:82:c9:cd:24: 16:8c:53:18:13:31:c0:a0:7f:09:e3:e0:f9:a2:4b:78: 21:71:1d:d0:10:f1:2c:19:26:d5:6a:27:23:16:0b:fc: d2:fd:e6:9d:ea:5e:98:14:5e:a4:57:33:db:29:13:35 P: 00:b2:fa:0a:98:21:dc:47:6c:bc:a9:0f:e4:a4:11:ce: be:56:86:89:fa:bf:fc:5f:f4:ce:15:92:a5:f8:ef:c2: a2:37:83:61:23:a9:72:fb:ac:26:32:bb:dc:31:a2:88: 0e:0e:8e:83:9d:e9:f1:7f:0d:71:a7:76:48:24:d0:ad: 7e:b4:89:ca:dd:00:b9:d8:ce:79:db:55:58:c4:a7:4e: 77:68:41:54:2d:61:22:34:9a:6a:c1:02:a1:4b:db:a8: b5:6e:61:0c:c6:c7:7a:a7:3d:a7:53:7a:7c:c8:66:3f: c2:96:22:5b:0c:b2:fb:f4:d9:bf:c6:b6:b7:9f:28:c2: 9c:3d:42:49:b8:71:7e:04:3e:8d:e2:87:f5:db:37:a7: a8:a0:ec:7d:65:a1:2b:71:2f:66:83:75:2f:c1:00:e6: d1:56:03:dd:98:d2:7a:ac:0f:ed:85:86:ca:1a:bb:80: f2:6c:12:23:2b:7e:dd:76:d5:28:f2:37:3f:2b:92:15: f3:a9:91:51:ba:38:e6:7d:34:f9:76:90:82:fd:9e:1d: ef:f6:44:df:4b:5b:a8:76:8d:0c:cf:98:c0:1a:db:fd: a2:fb:3e:7a:16:b3:ce:3b:56:98:c2:57:19:95:9a:02: 0f:a0:c1:e5:4d:b8:36:f5:1b:08:52:81:65:15:27:1a: 51 Q: 00:c1:b0:c2:9c:e2:96:b4:5e:68:4a:9c:a1:7e:cf:63: 9b:b8:be:d5:74:e7:3b:a2:54:8a:a4:74:13 G: 41:98:64:e6:a7:40:62:83:2f:66:13:e2:e5:33:a0:ca: c4:f3:40:4d:45:78:01:2d:83:2a:71:64:8e:b9:4e:9c: 0e:fc:93:ab:01:7f:6f:61:9f:ea:09:85:ff:9d:c8:f8: e7:9a:a2:3d:97:a7:97:9f:65:a3:f8:90:3f:38:49:52: e6:ee:09:eb:cf:58:9d:dd:b0:a4:5b:03:22:ff:e4:d0: b0:89:f9:df:60:13:09:1a:3d:14:14:62:f8:7a:45:55: d1:e9:8b:2f:d1:f9:7a:a6:33:eb:8c:34:79:5d:92:a7: 8d:0e:f3:20:ae:d2:aa:fb:41:38:b7:ce:9a:b3:25:9e: 37:13:ed:ba:67:d6:e0:47:a7:35:96:68:0a:5b:64:0d: ef:63:c7:c0:c9:53:96:ad:0e:ea:63:7e:7a:29:04:5a: 81:ab:d3:d2:56:a5:73:13:85:97:a6:00:ed:a6:1c:b0: 22:ab:a8:ee:d3:ec:dd:bb:35:99:ca:36:4f:64:0d:41: e1:75:25:4e:00:ad:cc:7b:de:62:95:33:67:53:61:67: 23:3c:8d:c1:85:3a:69:99:58:01:4d:c2:dd:37:6e:af: 7f:31:ad:2a:de:30:74:0f:2c:fb:20:31:a6:f0:3e:98: ef:bd:30:25:13:f7:8d:fe:e2:09:56:2d:f2:30:78:f6 Signature Algorithm: SHA256withDSA Signature: R: 00:b2:ae:cb:68:a9:fb:ec:30:27:ec:65:47:b2:70:aa: ca:42:60:09:00:7c:6e:a0:63:c2:2a:5e:f2 S: 21:34:7f:51:37:10:66:e6:ba:0e:d9:4f:74:d4:17:85: da:9d:29:4f:8d:74:82:11:fc:a0:0f:85 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com`; // openssl req -newkey rsa:4096 -keyout test-rsa-4096.key -out test-rsa-4096.csr // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" // -addext "subjectAltName = DNS:example.com,DNS:www.example.com,IP:127.0.0.1, \ // email:user@example.com,URI:http://example.com/api,otherName:1.2.3.4;UTF8:some value" // -addext "basicConstraints = critical,CA:FALSE" // -addext "keyUsage = critical,digitalSignature,keyEncipherment" // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_SAN = `-----BEGIN CERTIFICATE REQUEST----- MIIFbTCCA1UCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEB BQADggIPADCCAgoCggIBAJf8uQDFcQfj6qCuPa4hNyDWr3Lwzfc3qQZdOgNJ/kym GxxRHUXJyBtgkmAqDoSGmg1hUWgt9eZwd/Cf4Wd3qr+Q0ppg6dwZeWgYSunseoKl f0E5FvUfECNyDwCSbltN9TCsom2ePNOOJJHWo4Y3E3jGXz0n1Vwa6ePR0j62Rcey 4lHLscQ3GoNvMLcXbY1HIhnbaI25MmFPB8p4PvpPsAYgbWHbw0jIR9dSxEK0HAU3 2VkRkm8XaF4BOEfugqT3Bc7zAvwdFZRTTTZIICYW5T3zvtxBidJ8OSej16LV6ZeE /4VcTzXYTzIUXbNaev3XN1r5ZodkbZvxxk/EZmfes2OtedPulW4TW27HSl6XBos/ 8VQohelUXiyCLPrtbnjeHKSz47+ZAm23jMAFYWkTVdWvAa+G74UstuRRXfLAKCNv 7VeA3l8IgEkfj48u+EenV6cJ3ZJJ5/qvZo7OUjhAtYJmNtlRYE4r3uWRmaNXYwrD 7vJuMiZafaVC+74/UHLGGm7sHVJdo4KBO/LUbHJ/SKZIYMc14kJLOf6TPZXSGm9N TxbOV9Vzcjzivq1HxaYirLAM+nyVApVwwpVq/uiEFz579yrwySvBuwnewfdfZ6EZ iNAKiBwQ8diFMnFfd/28hJ8TrIlq+5bkVo1ODuhyRIw9YB19IrmytaVvkR8624Ld AgMBAAGggbUwgbIGCSqGSIb3DQEJDjGBpDCBoTBsBgNVHREEZTBjggtleGFtcGxl LmNvbYIPd3d3LmV4YW1wbGUuY29thwR/AAABgRB1c2VyQGV4YW1wbGUuY29thhZo dHRwOi8vZXhhbXBsZS5jb20vYXBpoBMGAyoDBKAMDApzb21lIHZhbHVlMAwGA1Ud EwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G CSqGSIb3DQEBCwUAA4ICAQAtOuh6MEralwgChJHBaGJavBxpCQ0p5K77RlAPIk5Q Mv5086DxiZEFBKCRiZRtkOvo0aCHUn3awDrlEOgECiAYQqMIBUWeNwImtmpDopuI ZMmVmzc2ojf9nUlPrPV+B6P2jTxTIQYpDQocbOgxDkcdZVSvLyMEFnHIMNQV7GS2 gBmUnPp+4z2d8X9XaRspkuEt2nbA1NoXekWaG46jG56VoBycepOiNkwL4AsqunLa T0urcHq34g+HRQWwOA+q/72qP4oaj2ZO0fFJQl2ZsGRT/IuM1g2YsnVSpBOGY/J6 Qi2hDr6EEqphg501ny+FZE1BouQ/lSykafYyauwNq1puu/VyuF8grFmL0SoxWWfP h6viblGM/Vu69Bhl4gkWKtufWpOVpCA4vHzes8IVMFg7vhpwm33Xjo0lCPcIUin6 0CqHZQCsWtj2yIAF66WHB0I1DHL5FNCWRPnQCo54qRZIYqtSP20QRr6GWC2d+ZgX wDxRpmzr8T8owBYWw3j+RK9CtZoWO4O586UR4J1Bn5PQfoR78Z/4mzv2sxVi9Fdf sJzlG6/nhmMaCqneIn97gkguvSgpOuKSeo/fjbpnthufgilrpDQoGrhZaXic0GVZ 6JmbOh3tLMVf4ooyyaLfOCfV2FN12rDa3pdWhQ4MVN4gg9U3Cq0x7yRQKiSBlBnw oA== -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_SAN = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: RSA Length: 4096 bits Modulus: 00:97:fc:b9:00:c5:71:07:e3:ea:a0:ae:3d:ae:21:37: 20:d6:af:72:f0:cd:f7:37:a9:06:5d:3a:03:49:fe:4c: a6:1b:1c:51:1d:45:c9:c8:1b:60:92:60:2a:0e:84:86: 9a:0d:61:51:68:2d:f5:e6:70:77:f0:9f:e1:67:77:aa: bf:90:d2:9a:60:e9:dc:19:79:68:18:4a:e9:ec:7a:82: a5:7f:41:39:16:f5:1f:10:23:72:0f:00:92:6e:5b:4d: f5:30:ac:a2:6d:9e:3c:d3:8e:24:91:d6:a3:86:37:13: 78:c6:5f:3d:27:d5:5c:1a:e9:e3:d1:d2:3e:b6:45:c7: b2:e2:51:cb:b1:c4:37:1a:83:6f:30:b7:17:6d:8d:47: 22:19:db:68:8d:b9:32:61:4f:07:ca:78:3e:fa:4f:b0: 06:20:6d:61:db:c3:48:c8:47:d7:52:c4:42:b4:1c:05: 37:d9:59:11:92:6f:17:68:5e:01:38:47:ee:82:a4:f7: 05:ce:f3:02:fc:1d:15:94:53:4d:36:48:20:26:16:e5: 3d:f3:be:dc:41:89:d2:7c:39:27:a3:d7:a2:d5:e9:97: 84:ff:85:5c:4f:35:d8:4f:32:14:5d:b3:5a:7a:fd:d7: 37:5a:f9:66:87:64:6d:9b:f1:c6:4f:c4:66:67:de:b3: 63:ad:79:d3:ee:95:6e:13:5b:6e:c7:4a:5e:97:06:8b: 3f:f1:54:28:85:e9:54:5e:2c:82:2c:fa:ed:6e:78:de: 1c:a4:b3:e3:bf:99:02:6d:b7:8c:c0:05:61:69:13:55: d5:af:01:af:86:ef:85:2c:b6:e4:51:5d:f2:c0:28:23: 6f:ed:57:80:de:5f:08:80:49:1f:8f:8f:2e:f8:47:a7: 57:a7:09:dd:92:49:e7:fa:af:66:8e:ce:52:38:40:b5: 82:66:36:d9:51:60:4e:2b:de:e5:91:99:a3:57:63:0a: c3:ee:f2:6e:32:26:5a:7d:a5:42:fb:be:3f:50:72:c6: 1a:6e:ec:1d:52:5d:a3:82:81:3b:f2:d4:6c:72:7f:48: a6:48:60:c7:35:e2:42:4b:39:fe:93:3d:95:d2:1a:6f: 4d:4f:16:ce:57:d5:73:72:3c:e2:be:ad:47:c5:a6:22: ac:b0:0c:fa:7c:95:02:95:70:c2:95:6a:fe:e8:84:17: 3e:7b:f7:2a:f0:c9:2b:c1:bb:09:de:c1:f7:5f:67:a1: 19:88:d0:0a:88:1c:10:f1:d8:85:32:71:5f:77:fd:bc: 84:9f:13:ac:89:6a:fb:96:e4:56:8d:4e:0e:e8:72:44: 8c:3d:60:1d:7d:22:b9:b2:b5:a5:6f:91:1f:3a:db:82: dd Exponent: 65537 (0x10001) Signature Algorithm: SHA256withRSA Signature: 2d:3a:e8:7a:30:4a:da:97:08:02:84:91:c1:68:62:5a: bc:1c:69:09:0d:29:e4:ae:fb:46:50:0f:22:4e:50:32: fe:74:f3:a0:f1:89:91:05:04:a0:91:89:94:6d:90:eb: e8:d1:a0:87:52:7d:da:c0:3a:e5:10:e8:04:0a:20:18: 42:a3:08:05:45:9e:37:02:26:b6:6a:43:a2:9b:88:64: c9:95:9b:37:36:a2:37:fd:9d:49:4f:ac:f5:7e:07:a3: f6:8d:3c:53:21:06:29:0d:0a:1c:6c:e8:31:0e:47:1d: 65:54:af:2f:23:04:16:71:c8:30:d4:15:ec:64:b6:80: 19:94:9c:fa:7e:e3:3d:9d:f1:7f:57:69:1b:29:92:e1: 2d:da:76:c0:d4:da:17:7a:45:9a:1b:8e:a3:1b:9e:95: a0:1c:9c:7a:93:a2:36:4c:0b:e0:0b:2a:ba:72:da:4f: 4b:ab:70:7a:b7:e2:0f:87:45:05:b0:38:0f:aa:ff:bd: aa:3f:8a:1a:8f:66:4e:d1:f1:49:42:5d:99:b0:64:53: fc:8b:8c:d6:0d:98:b2:75:52:a4:13:86:63:f2:7a:42: 2d:a1:0e:be:84:12:aa:61:83:9d:35:9f:2f:85:64:4d: 41:a2:e4:3f:95:2c:a4:69:f6:32:6a:ec:0d:ab:5a:6e: bb:f5:72:b8:5f:20:ac:59:8b:d1:2a:31:59:67:cf:87: ab:e2:6e:51:8c:fd:5b:ba:f4:18:65:e2:09:16:2a:db: 9f:5a:93:95:a4:20:38:bc:7c:de:b3:c2:15:30:58:3b: be:1a:70:9b:7d:d7:8e:8d:25:08:f7:08:52:29:fa:d0: 2a:87:65:00:ac:5a:d8:f6:c8:80:05:eb:a5:87:07:42: 35:0c:72:f9:14:d0:96:44:f9:d0:0a:8e:78:a9:16:48: 62:ab:52:3f:6d:10:46:be:86:58:2d:9d:f9:98:17:c0: 3c:51:a6:6c:eb:f1:3f:28:c0:16:16:c3:78:fe:44:af: 42:b5:9a:16:3b:83:b9:f3:a5:11:e0:9d:41:9f:93:d0: 7e:84:7b:f1:9f:f8:9b:3b:f6:b3:15:62:f4:57:5f:b0: 9c:e5:1b:af:e7:86:63:1a:0a:a9:de:22:7f:7b:82:48: 2e:bd:28:29:3a:e2:92:7a:8f:df:8d:ba:67:b6:1b:9f: 82:29:6b:a4:34:28:1a:b8:59:69:78:9c:d0:65:59:e8: 99:9b:3a:1d:ed:2c:c5:5f:e2:8a:32:c9:a2:df:38:27: d5:d8:53:75:da:b0:da:de:97:56:85:0e:0c:54:de:20: 83:d5:37:0a:ad:31:ef:24:50:2a:24:81:94:19:f0:a0 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com IP: 127.0.0.1 EMAIL: user@example.com URI: http://example.com/api Other: 1.2.3.4::some value`; // openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \ // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ // -addext "basicConstraints = critical,CA:FALSE" \ // -addext "keyUsage = critical,digitalSignature,keyEncipherment," \ // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_KEY_USAGE = `-----BEGIN CERTIFICATE REQUEST----- MIIDJDCCAgwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAKHQWxqtdJQ1l7ApTgwgsyrN/kRDrog/DsUlZQg3YodY 4RRAgPr+AeQ1BhuWDVxaXein0XmXOESHgK9Z7X/hLgRy2ifK+n20Ij3+k6VSh6Lt lpjUPwK7PWBtZ969DukBIvq64XrJTNWIJPvXXQxkL4dk5NcDY4TjXWt0GgDVR+GH OU1JwfzviGVRdOmY8+Ckfxc+3QytTdP6KBQaiUk5sBEniovDpKfImtql72JsCRbA 9Wue7X4EbXi2zvoAlJ5NXF3Ps1q2XsVJeIx/mMDcgRW7s5AVM9NQW0O1JLoA7dY+ vSrKZj+ssuKCIWM7u9Big2I0miEl5AXrDlwZPBhM9FMCAwEAAaBtMGsGCSqGSIb3 DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNv bTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIB/jATBgNVHSUEDDAKBggrBgEF BQcDATANBgkqhkiG9w0BAQsFAAOCAQEAPOr6jfq/mXilqXA11CTza69Ydd4fvp6q UG47PefzQqSmYtpUytwZRLGQ1IFRlYeXwbazVLkRmLNwpbB8C5fh9FPp55JCpM/O tgCW2uqLkCtkQMUCaSdRX/Y+9ypYhdBkSNv1Q+3QXi2jmi5QMqwerAwNmeXmH6AZ swMgAhuoLS9OrIqHjFoHGoXsgXMkbLr6m6hgyFt8ZbbwK4WpVcgCZfhtBiLilCJN Xr9GUXL3FqUb7sIaYKAaghr2haqKhFsIH57XVK3DZYhOkLd9uC8TLdl2e+t9Hcy9 ymLwiIGMUfuBQMP8nVu3jGXAQ5N4VV+IZfF8UaBFW8tG+Ms2TeW68Q== -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_KEY_USAGE = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: RSA Length: 2048 bits Modulus: 00:a1:d0:5b:1a:ad:74:94:35:97:b0:29:4e:0c:20:b3: 2a:cd:fe:44:43:ae:88:3f:0e:c5:25:65:08:37:62:87: 58:e1:14:40:80:fa:fe:01:e4:35:06:1b:96:0d:5c:5a: 5d:e8:a7:d1:79:97:38:44:87:80:af:59:ed:7f:e1:2e: 04:72:da:27:ca:fa:7d:b4:22:3d:fe:93:a5:52:87:a2: ed:96:98:d4:3f:02:bb:3d:60:6d:67:de:bd:0e:e9:01: 22:fa:ba:e1:7a:c9:4c:d5:88:24:fb:d7:5d:0c:64:2f: 87:64:e4:d7:03:63:84:e3:5d:6b:74:1a:00:d5:47:e1: 87:39:4d:49:c1:fc:ef:88:65:51:74:e9:98:f3:e0:a4: 7f:17:3e:dd:0c:ad:4d:d3:fa:28:14:1a:89:49:39:b0: 11:27:8a:8b:c3:a4:a7:c8:9a:da:a5:ef:62:6c:09:16: c0:f5:6b:9e:ed:7e:04:6d:78:b6:ce:fa:00:94:9e:4d: 5c:5d:cf:b3:5a:b6:5e:c5:49:78:8c:7f:98:c0:dc:81: 15:bb:b3:90:15:33:d3:50:5b:43:b5:24:ba:00:ed:d6: 3e:bd:2a:ca:66:3f:ac:b2:e2:82:21:63:3b:bb:d0:62: 83:62:34:9a:21:25:e4:05:eb:0e:5c:19:3c:18:4c:f4: 53 Exponent: 65537 (0x10001) Signature Algorithm: SHA256withRSA Signature: 3c:ea:fa:8d:fa:bf:99:78:a5:a9:70:35:d4:24:f3:6b: af:58:75:de:1f:be:9e:aa:50:6e:3b:3d:e7:f3:42:a4: a6:62:da:54:ca:dc:19:44:b1:90:d4:81:51:95:87:97: c1:b6:b3:54:b9:11:98:b3:70:a5:b0:7c:0b:97:e1:f4: 53:e9:e7:92:42:a4:cf:ce:b6:00:96:da:ea:8b:90:2b: 64:40:c5:02:69:27:51:5f:f6:3e:f7:2a:58:85:d0:64: 48:db:f5:43:ed:d0:5e:2d:a3:9a:2e:50:32:ac:1e:ac: 0c:0d:99:e5:e6:1f:a0:19:b3:03:20:02:1b:a8:2d:2f: 4e:ac:8a:87:8c:5a:07:1a:85:ec:81:73:24:6c:ba:fa: 9b:a8:60:c8:5b:7c:65:b6:f0:2b:85:a9:55:c8:02:65: f8:6d:06:22:e2:94:22:4d:5e:bf:46:51:72:f7:16:a5: 1b:ee:c2:1a:60:a0:1a:82:1a:f6:85:aa:8a:84:5b:08: 1f:9e:d7:54:ad:c3:65:88:4e:90:b7:7d:b8:2f:13:2d: d9:76:7b:eb:7d:1d:cc:bd:ca:62:f0:88:81:8c:51:fb: 81:40:c3:fc:9d:5b:b7:8c:65:c0:43:93:78:55:5f:88: 65:f1:7c:51:a0:45:5b:cb:46:f8:cb:36:4d:e5:ba:f1 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Non-repudiation Key encipherment Data encipherment Key agreement Key certificate signing CRL signing Extended Key Usage: TLS Web Server Authentication Subject Alternative Name: DNS: example.com DNS: www.example.com`; // openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \ // -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ // -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ // -addext "basicConstraints = critical,CA:FALSE" \ // -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ // -addext "extendedKeyUsage = serverAuth" const IN_EXAMPLE_COM_EXTENDED_KEY_USAGE = `-----BEGIN CERTIFICATE REQUEST----- MIIDpzCCAo8CAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAMjQ/Bz+CzA/WaS+Nyp3ijWzYlKY7GmA/a2FuzNSPQlr WuGyZJcfb0CpLIpRF8qcDllAe+hFQnVGnk3svQIhfEOD7qwzBRMHVhe59jkv2kER s+u88KBCNfIAS6m5d45y4xH338aXq4lZexiEASWHS7SsWAR3kL3c9p14U9EHOaym ZWPO/SCfCJyhxszDLM2eG5S2rviuu9nY+rk0Oo7z8x8PZF9Wl1NamLl1tWPqsznS 3bfjdJYeUlm7XvTzC6EMAT6K/5ker0chl7Hg0mcEO9w4c2cSTAHvZ2b2sRYbxNQZ 49byQsRAXW8TNnOaK9Phmvwy/irEXU9PEl3u7KvSnNcCAwEAAaCB7zCB7AYJKoZI hvcNAQkOMYHeMIHbMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29tgg93d3cuZXhhbXBs ZS5jb20wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwgZEGA1UdJQSBiTCB hgYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUF BwMIBgorBgEEAYI3AgEVBgorBgEEAYI3AgEWBgorBgEEAYI3CgMBBgorBgEEAYI3 CgMDBgorBgEEAYI3CgMEBgorBgEEAYI3FAICBgorBgEEAYI3CgMDMA0GCSqGSIb3 DQEBCwUAA4IBAQCcYWj1eIxj/FUEhhm2lZr06Pq4GEtIVsMWw5IrUn2FIFb/yY8x GHuB5v7XNA/8zhRWvIAXGaa8Bnajk4mR0rkxy1MXpd2YevdrF/XFa2Totv4E4/I6 pvrFefYTSGpmCu5zQTuoanM7JjE81vvbTLFdaHMdLOekpuK5v5kbuNdtDpEiAkd0 vmV4BQ0BV3b3zhIRQqBB60pSBHYvMhHNn/80RhVUQxaPTS7/AMHRZGRc1lD9/bjA pMBis9CL4AbXtTcztU5qy4VpB1/Ej3AbAjuJIbpbPH6XtxIEtqdM4Seqi44w9oX4 rxQagXmvJPp+E4253EkeHwhfHh4SnJEtsibQ -----END CERTIFICATE REQUEST-----`; const OUT_EXAMPLE_COM_EXTENDED_KEY_USAGE = `Subject C = CH ST = Zurich L = Zurich O = Example RE OU = IT Department CN = example.com Public Key Algorithm: RSA Length: 2048 bits Modulus: 00:c8:d0:fc:1c:fe:0b:30:3f:59:a4:be:37:2a:77:8a: 35:b3:62:52:98:ec:69:80:fd:ad:85:bb:33:52:3d:09: 6b:5a:e1:b2:64:97:1f:6f:40:a9:2c:8a:51:17:ca:9c: 0e:59:40:7b:e8:45:42:75:46:9e:4d:ec:bd:02:21:7c: 43:83:ee:ac:33:05:13:07:56:17:b9:f6:39:2f:da:41: 11:b3:eb:bc:f0:a0:42:35:f2:00:4b:a9:b9:77:8e:72: e3:11:f7:df:c6:97:ab:89:59:7b:18:84:01:25:87:4b: b4:ac:58:04:77:90:bd:dc:f6:9d:78:53:d1:07:39:ac: a6:65:63:ce:fd:20:9f:08:9c:a1:c6:cc:c3:2c:cd:9e: 1b:94:b6:ae:f8:ae:bb:d9:d8:fa:b9:34:3a:8e:f3:f3: 1f:0f:64:5f:56:97:53:5a:98:b9:75:b5:63:ea:b3:39: d2:dd:b7:e3:74:96:1e:52:59:bb:5e:f4:f3:0b:a1:0c: 01:3e:8a:ff:99:1e:af:47:21:97:b1:e0:d2:67:04:3b: dc:38:73:67:12:4c:01:ef:67:66:f6:b1:16:1b:c4:d4: 19:e3:d6:f2:42:c4:40:5d:6f:13:36:73:9a:2b:d3:e1: 9a:fc:32:fe:2a:c4:5d:4f:4f:12:5d:ee:ec:ab:d2:9c: d7 Exponent: 65537 (0x10001) Signature Algorithm: SHA256withRSA Signature: 9c:61:68:f5:78:8c:63:fc:55:04:86:19:b6:95:9a:f4: e8:fa:b8:18:4b:48:56:c3:16:c3:92:2b:52:7d:85:20: 56:ff:c9:8f:31:18:7b:81:e6:fe:d7:34:0f:fc:ce:14: 56:bc:80:17:19:a6:bc:06:76:a3:93:89:91:d2:b9:31: cb:53:17:a5:dd:98:7a:f7:6b:17:f5:c5:6b:64:e8:b6: fe:04:e3:f2:3a:a6:fa:c5:79:f6:13:48:6a:66:0a:ee: 73:41:3b:a8:6a:73:3b:26:31:3c:d6:fb:db:4c:b1:5d: 68:73:1d:2c:e7:a4:a6:e2:b9:bf:99:1b:b8:d7:6d:0e: 91:22:02:47:74:be:65:78:05:0d:01:57:76:f7:ce:12: 11:42:a0:41:eb:4a:52:04:76:2f:32:11:cd:9f:ff:34: 46:15:54:43:16:8f:4d:2e:ff:00:c1:d1:64:64:5c:d6: 50:fd:fd:b8:c0:a4:c0:62:b3:d0:8b:e0:06:d7:b5:37: 33:b5:4e:6a:cb:85:69:07:5f:c4:8f:70:1b:02:3b:89: 21:ba:5b:3c:7e:97:b7:12:04:b6:a7:4c:e1:27:aa:8b: 8e:30:f6:85:f8:af:14:1a:81:79:af:24:fa:7e:13:8d: b9:dc:49:1e:1f:08:5f:1e:1e:12:9c:91:2d:b2:26:d0 Requested Extensions Basic Constraints: critical CA = false Key Usage: critical Digital Signature Key encipherment Extended Key Usage: TLS Web Server Authentication TLS Web Client Authentication Code signing E-mail Protection (S/MIME) Trusted Timestamping Microsoft Individual Code Signing Microsoft Commercial Code Signing Microsoft Trust List Signing Microsoft Server Gated Crypto Microsoft Encrypted File System Microsoft Smartcard Login Microsoft Server Gated Crypto Subject Alternative Name: DNS: example.com DNS: www.example.com`; TestRegister.addTests([ { name: "Parse CSR: Example Certificate Signing Request (CSR) with RSA 1024", input: IN_EXAMPLE_COM_RSA_1024, expectedOutput: OUT_EXAMPLE_COM_RSA_1024, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with RSA 2048", input: IN_EXAMPLE_COM_RSA_2048, expectedOutput: OUT_EXAMPLE_COM_RSA_2048, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with EC 256", input: IN_EXAMPLE_COM_EC_P256, expectedOutput: OUT_EXAMPLE_COM_EC_P256, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with EC 384", input: IN_EXAMPLE_COM_EC_P384, expectedOutput: OUT_EXAMPLE_COM_EC_P384, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with EC 521", input: IN_EXAMPLE_COM_EC_P521, expectedOutput: OUT_EXAMPLE_COM_EC_P521, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with DSA 1024", input: IN_EXAMPLE_COM_DSA_1024, expectedOutput: OUT_EXAMPLE_COM_DSA_1024, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with DSA 2048", input: IN_EXAMPLE_COM_DSA_2048, expectedOutput: OUT_EXAMPLE_COM_DSA_2048, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with DSA 2048", input: IN_EXAMPLE_COM_DSA_2048, expectedOutput: OUT_EXAMPLE_COM_DSA_2048, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with various SAN types", input: IN_EXAMPLE_COM_SAN, expectedOutput: OUT_EXAMPLE_COM_SAN, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with various Key Usages", input: IN_EXAMPLE_COM_KEY_USAGE, expectedOutput: OUT_EXAMPLE_COM_KEY_USAGE, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, { name: "Parse CSR: Example Certificate Signing Request (CSR) with various Extended Key Usages", input: IN_EXAMPLE_COM_EXTENDED_KEY_USAGE, expectedOutput: OUT_EXAMPLE_COM_EXTENDED_KEY_USAGE, recipeConfig: [ { "op": "Parse CSR", "args": ["PEM"] } ] }, ]); ================================================ FILE: tests/operations/tests/ParseIPRange.mjs ================================================ /** * Parse IP Range tests. * * @author Klaxon [klaxon@veyr.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Parse IPv4 CIDR", input: "10.0.0.0/30", 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", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "Parse IPv4 hyphenated", input: "10.0.0.0 - 10.0.0.3", 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", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "Parse IPv4 list", input: "10.0.0.8\n10.0.0.5/30\n10.0.0.1\n10.0.0.3", 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", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "Parse IPv6 CIDR - full", input: "2404:6800:4001:0000:0000:0000:0000:0000/48", 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", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "Parse IPv6 CIDR - collapsed", input: "2404:6800:4001::/48", 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", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "Parse IPv6 hyphenated", input: "2404:6800:4001:: - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff", 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", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "Parse IPv6 list", input: "2404:6800:4001:ffff:ffff:ffff:ffff:ffff\n2404:6800:4001::ffff\n2404:6800:4001:ffff:ffff::1111\n2404:6800:4001::/64", 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", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "IPv4 subnet out of range error", input: "10.1.1.1/34", expectedOutput: "IPv4 CIDR must be less than 32", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "invalid IPv4 address error", input: "444.1.1.1/30", expectedOutput: "Block out of range.", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "IPv6 subnet out of range error", input: "2404:6800:4001::/129", expectedOutput: "IPv6 CIDR must be less than 128", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, { name: "invalid IPv6 address error", input: "2404:6800:4001:/12", 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.", recipeConfig: [ { "op": "Parse IP range", "args": [true, true, false] }, ], }, ]); ================================================ FILE: tests/operations/tests/ParseObjectIDTimestamp.mjs ================================================ /** * Parse ObjectID timestamp tests * * @author dmfj [dominic@dmfj.io] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Parse ISO timestamp from ObjectId", input: "000000000000000000000000", expectedOutput: "1970-01-01T00:00:00.000Z", recipeConfig: [ { op: "Parse ObjectID timestamp", args: [], } ], } ]); ================================================ FILE: tests/operations/tests/ParseQRCode.mjs ================================================ /** * Parse QR Code tests * * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Parse QR Code : JPEG", input: "ffd8ffe000104a46494600010100004800480000ffe1008c4578696600004d4d002a000000080005011200030000000100010000011a0005000000010000004a011b0005000000010000005201280003000000010002000087690004000000010000005a00000000000000480000000100000048000000010003a00100030000000100010000a002000400000001000001e0a003000400000001000001e000000000ffed003850686f746f73686f7020332e30003842494d04040000000000003842494d0425000000000010d41d8cd98f00b204e9800998ecf8427effc000110801e001e003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffdb004300020202020202030202030403030304050404040405070505050505070807070707070708080808080808080a0a0a0a0a0a0b0b0b0b0b0d0d0d0d0d0d0d0d0d0dffdb004301020202030303060303060d0907090d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0dffdd0004001effda000c03010002110311003f00fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd0fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd1fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd2fdfca28a2800a28a2800a28a2803f3c7f6e8fdba2f3f636bcf06da5a78362f15ff00c25716a72b34ba99d3becdfd9c6d86062dae37effb47fb38dbdf3c7c07ff000fc4d5bfe88f5aff00e14adffcaea4ff0082e27fc85be0f7fd7af89bff0042d3abf03a803f7cbfe1f89ab7fd11eb5ffc295bff0095d47fc3f1356ffa23d6bff852b7ff002babf0505bc8c010539f59107e99a5fb349eb1ff00dfc4ff001a00fdeaff0087e26adff447ad7ff0a56ffe5751ff000fc4d5bfe88f5aff00e14adffcaeafc15fb349eb1ffdfc4ff1a3ecd27ac7ff007f13fc6803f7abfe1f89ab7fd11eb5ff00c295bff95d47fc3f1356ff00a23d6bff00852b7ff2babf057ecd27ac7ff7f13fc68fb349eb1ffdfc4ff1a00fdeaff87e26adff00447ad7ff000a56ff00e5751ff0fc4d5bfe88f5affe14adff00caeafc15fb349eb1ff00dfc4ff001a3ecd27ac7ff7f13fc6803f7abfe1f89ab7fd11eb5ffc295bff0095d5ec5f00bfe0adbaa7c6df8bfe13f85b27c30b6d223f12ea9169cd7cbaf35d35bf9aaedbc45f618c3e3674debd6bf9ad6528db4e3f0208fcc715f61fec0fff00276ff0b7fec67b4ffd153d007f66f4514500078afc2cf891ff000592d57e1f78efc41e0c5f8516b7aba26aba8e9ab727c42d0998585dcd6de614fb03eddfe56edbb8e338c9eb5fba47a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6ee803fa2afd8fbfe0a5da8fed51f17edbe175c7c3e83c371dc69f7f7bf6e8f596be606c9633b3ca36900f9bccebbf8c74afd60afe55ffe090bff002765a77fd80b5dff00d02dabfaa7cfd7f2a00fcaff00db27fe0a45a87eca1f1507c37b6f0041e268ce9561a97db24d61ac0e6f5ee5767962d271f2fd9f39ddceee9c73f39f813fe0b35ab78d7c61a37854fc26b5b41aaea36360671e22694c42f2e62b7de13ec0bbb6f99bb1919c6322be4fff0082c400ff00b51a4419439f0b686c159829204ba8824648ce0919fad7e797c0a4f27e2ff834c8d18dde21d11540914927fb46d8e00073d013401fdcd5140a2800a2933f5fca96803c9fe3afc4a93e0dfc1ef17fc54874f5d59fc2ba4dcea8b64d31b71706dd777966508e53774ddb5b1e86bf152ebfe0b7babdadccd6c7e0fda3186468c9ff008495c6769c7fd03abf56ff006e0ff9344f8b9ff629ea5ffa28d7f179ac7fc85af7febe65ff00d0cd007ef0ff00c3f1356ffa23d6bff852b7ff002baa7b7ff82deeaf70ec83e0fda8db1cb27fc8cae7fd5a33e3fe41ddf18afc0b485e4190547fbccabfcc8ad1d3a3f26791e578d57ecf723fd6275685c01d7a92702803fbd2d16fceaba4596a853cb3796d0ce501cedf3503e33c6719c56a5737e0eff914f45ffb075a7fe894ae92800afc9ffdb07fe0a5da8fecaff17ee7e175bfc3e83c491dbe9f617bf6e93596b1626f5643b3ca16938f97cbebbf9cf4afd5fcfd7f2afe567fe0af0bbff6b5d4630ca18e83a190198292025ce48c91d322803e9dff0087e26adff447ad7ff0a56ffe5751ff000fc4d5bfe88f5aff00e14adffcaeafc1436f2004929c7fd3443fd6a0a00febb3f61efdba2f7f6c4d4bc536177e0c87c283c39696174ad16a6750f3fedb2dc45b4e6dadf66cfb3e7f8b3bbb639fd10afe7cff00e0893ff21ef89bff00608d0fff004af51afe832803f15fe3effc15b754f825f17fc59f0b63f8616dabc7e1ad525d396f9b5e6b56b8f2951b798bec3204cefe9bdba578effc3f1356ff00a23d6bff00852b7ff2babf34bf6f8ff93b7f8a5ff633ddff00e8a82be3c552edb463f1200fccf1401fbe1ff0fc4d5bfe88f5affe14adff00caea3fe1f89ab7fd11eb5ffc295bff0095d5f82bf6693d63ff00bf89fe347d9a4f58ff00efe27f8d007ef57fc3f1356ffa23d6bff852b7ff002ba8ff0087e26adff447ad7ff0a56ffe5757e0afd9a4f58ffefe27f8d1f6693d63ff00bf89fe3401fbd5ff000fc4d5bfe88f5aff00e14adffcaea3fe1f89ab7fd11eb5ff00c295bff95d5f82bf6693d63ffbf89fe347d9a4f58ffefe27f8d007ef57fc3f1356ff00a23d6bff00852b7ff2ba8ff87e26adff00447ad7ff000a56ff00e5757e0afd9a4f58ff00efe27f8d21b7914124a71e9221fd33401fbd7ff0fc4d5bfe88f5affe14adff00caeafbf3f617fdba2f3f6c9bcf195a5df8362f0a7fc22916992ab45a99d47ed3fda26e460e6dadf66cfb3ffb59dddb1cff002235fbe3ff00043bff0090b7c61ffaf5f0cffe85a8d007f417451450014514500145145007ffd3fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf056dc06908233f2487f2535fbd5ff0005c4ff0090b7c1effaf5f137fe85a757e0b5b7fac3ff005ce4ff00d00d007f693f03be07fc17bdf82fe02bdbdf0178667b8b8f0ce912cb2c9a45a3c9248f6913333318892cc49249e49af53ff850bf03bfe89e785bff0004d67ffc6a93e027fc90df879ff62b68dffa47155ff8b7f16fc0bf03fc0b7df123e245f49a7681a7496f15c5c456f35d3abdd4c9044045023c8dba4751f2a9c67278a00a3ff0a17e077fd13cf0b7fe09acff00f8d51ff0a17e077fd13cf0b7fe09acff00f8d57c99ff000f4afd8b3fe872bfff00c27f55ff00e45a3fe1e95fb167fd0e57ff00f84feabffc8b401f59ff00c285f81dff0044f3c2dff826b3ff00e3547fc285f81dff0044f3c2dff826b3ff00e355f267fc3d2bf62cff00a1caff00ff0009fd57ff009168ff0087a57ec59ff4395fff00e13faaff00f22d007d67ff000a17e077fd13cf0b7fe09acfff008d521f80bf03b07fe2de785bff0004d67ffc6abcefe06fed83f013f68dd7751f0e7c24d76e356bfd2ed16faea39b4dbcb109033f961835cc31ab65b8c2926be9c3d0d007f1c5ff000511d0b44f0e7ed71f11349f0fe9f6ba658dbea96e90db59c296f0c6a74db1721238c2aa8dcc4e00ea49ef58dfb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd145140087a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6eebfb8935fcbe7c5ff00f8263fed71e37f8a3e2df14e93e17b06b0d535fd62f6cddf59b48d9edeeefee27899909254b2480e09c8ef8390003f2bbc27e34f177817545d6fc19adea5a0ea0a8f10bad2ef26b19c24980ea258191c2b606e19c1c0cf4af4eff869cfda1ffe8a778d7ff0a4d4bff922bdafe317fc13e7f693f81be04bbf88bf10342b3b2d1ace7b5b69658754b6b97125e4ab0c40469f310646009edd4d7c3f401fd5bffc13a3c29e18f8bdfb345878cfe2c69367e36f103eb3abd9b6ade2381357d41adadae5d6189ae6ec4b3347102422962172718c9afbc22f815f04e09a2b883e1ff85e396191258dd747b30c9246c1d19488b219580208e41008af8bff00e0949ff2687a77fd8c3af7fe95b57e92d0015f92ff00f0574f1c78d3c0bf07fc17a87827c41ab787aea7f11dc4734fa45fdc69f2c91a69b7520477b778d9977a2b6d271902bf5a2bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2803f0574efda5bf684b8d42da097e2778d4a4b3468c3fe124d4fa33007fe5e2bfae3fd8db57d5fc41fb2c7c2dd6f5ebeb9d4b51bef0c69f3dcddddcaf3dc4f2bc796792490b3bb31e4b3124d7f159a3ffc85acbfebe62ffd0c57f687fb0fff00c9a27c23ff00b14f4dff00d142800fdb83fe4d13e2e7fd8a7a97fe8a35fc5e6b1ff216bdff00af997ff4335fdb7fed49e08f12fc4afd9dbe21f803c1d6cb79adebfe1fbdb0b0b769121124f326d452f2154504f762057f37179ff04a2fdb22eaee6ba3e16d394cd23c981ae5a1037126803efdff008242fc35f877e31f837e34bcf16f85b45d6ae21f11db4714ba8e9f6f752221d32d1caab4a8c402cc588071924f5afd6b93e00fc0b951a297e1df851d1d4ab2b68b66432b0c10418b90475afca7fd92bc59a0ff00c1387c09ab7813f6b4b83e16d63c59aaff006b6910d8c336b493d95a5a5ada4923496114a232255c159029e463239afaa3fe1e95fb167fd0e57fff0084feabff00c8b401fa0d0c315bc490408b1c71a8444501555546000070001d057e7e7fc14e7c51e26f087ecaba86b3e12d6350d0f505d7745885e699773595c88e5b90aea2581d240194e0807914cff87a57ec59ff004395ff00fe13faafff0022d782fed29f1dbe1a7ededf09efbe027ecc3a94be25f1acb7763ad2595e59dd69109b2d32e236b87fb45ec314594f3106d04b1dc30a6803f9fdff00869bfda1f3ff00253bc6bff8526a5ffc915fd2affc13dfc1be12f8affb30e85e33f8a7a358f8cfc433ea5ad5bcdabf886dd355d46586d7509e386392eaec4b33ac4802a0672157818afc66ff00874dfed8ff00f42be9dff83cb4afd4ff00d9cff687f855fb0b7c25d2bf67dfda5f549bc37e39d36e350d4eeac2d2caef56852db55bc9ee2d985cd9432c2dbd0f4c86041c81401f767c4df819f052dbe1bf8aee6dbc01e188a68744d45e391347b456475b7908652220410790457f13d7aaab3285000f2613c7a98d49fccd7f573e3dff00829bfec6fadf81bc45a2e9fe30be7babfd26fad6053a0ea8a1a59a0744059ad40196206490077afe512ee449250c872047129faaa2a9fd45007ef3ff00c1127fe43df137fec11a1ffe95ea35fd0657f3e7ff000449ff0090f7c4dffb04687ffa57a8d7f419401fc647edf1ff00276ff14bfec67bbffd15056cff00c13bb42d13c47fb5c7c3bd27c41a7daea76371aa5c24d6d790a5c43228d36f9c078e40cac37283823a807b5637edf1ff00276ff14bfec67bbffd150575ff00f04d5ff93c8f86bff616b9ff00d35ea1401fd578f80bf03b03fe2de785bff04d67ff00c6a97fe142fc0eff00a279e16ffc1359ff00f1aaf571d057cc7f1cbf6c1f809fb396bba77873e2debb71a4dfea968d7d6b1c3a6de5f07815fcb2c5ada191570dc61883401e8bff000a17e077fd13cf0b7fe09acfff008d51ff000a17e077fd13cf0b7fe09acfff008d57c99ff0f4afd8b3fe872bff00fc27f55ffe45a3fe1e95fb167fd0e57fff0084feabff00c8b401f59ffc285f81dff44f3c2dff00826b3ffe3547fc285f81dff44f3c2dff00826b3ffe355f267fc3d2bf62cffa1cafff00f09fd57ff9168ff87a57ec59ff004395ff00fe13faafff0022d007d67ff0a17e077fd13cf0b7fe09acff00f8d57967c71f81ff0005ecbe0bf8f6f6cbc05e1982e2dfc33abcb14b1e91689247225a4acacac220432900823906bd87e127c5bf02fc70f02d8fc48f86f7d26a3a06a325c456f712dbcd6aecf6b33c128314e8922ed91187cca338c8e2a87c7bff00921bf10ffec56d67ff0048e5a00fe18ee005900031f2467f3515fbd5ff00043bff0090b7c61ffaf5f0cffe85a8d7e0b5cffac1ff005ce3ff00d0057ef4ff00c10eff00e42df187febd7c33ff00a16a3401fd05d14514005145140051451401ffd4fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054aff00932cf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe44dddf7b7cc7a9ef4df31ffbc7f3a1fefb7d4d32801fe63ff78fe74798ff00de3f9d328a00fdbcff008228127e2ef8e8939ff8a5a0ff00d2e35fd1f9e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd1457e55ff00c1467f6cef8b5fb296b1e0bb3f86b06892c1af58ea77578756b296ed835a4d6b14623f2ae6df6822762d9dd9c0e9401faa9457f2f51ffc163bf6a598b2c763e0edc2391c6747ba00ec52d8cff689eb8afe973c0fabddf883c17a06bfa86c175a9e976779388c6d4f327851df682490bb98e064e05007c35ff0547ff933df117fd863c3dffa73b7afe448f5afee6fe3a7c12f07fed09f0eaf3e1878ee4bd8b47beb8b4b995b4f985bdc6fb399678f6c855f68de833819c7420f35f0b7fc3a17f64eff009ede2bff00c1bfff006aa00e93fe0949ff002687a77fd8c3af7fe95b57e92d78c7c06f815e0bfd9d7e1f43f0d3c0325f49a44177757a87519c5c4fe65dbf9926640a991bba6467d49af67a002bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2bf646bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2803f9a5d1ffe42d65ff5f317fe862bfb43fd87ff00e4d13e11ff00d8a7a6ff00e8a15fc58dbcef6b7115cc78df13abae791953919afd32f85fff000552fda1fe147c3cf0efc35f0d597859b4af0d69f069b686ef4bb99a730c0bb54c922dfc6acd8ea42283e82803fac4a2bf977ff87c9fed45ff003e3e0eff00c135d7ff002ca8ff0087c9fed45ff3e3e0effc135d7ff2ca803dbbfe0b6848f197c3920e3fe243aaff00e96d9d7e10798ffde3f9d7f47ff00bc1da37fc1527c2179f10ff00696f32df53f056a0da2e98be1677d2a06b4bdb6b5bd733248f74cf2798c0021c2e147cb9e6bdc3fe1d0bfb270c7ef7c57d7fe82fff00da6803f950f31ffbc7f3afd6cff823ab16fda81b7127fe298d77affd75d36bf347e29f8734ef097c41f10f87349f33ec9a6eada959c3e6b6f7f2ad6f2781371c0c9d918c9ee726bf4b7fe08e9ff2740fff0062c6bbff00a374da00fea3abf957ff0082bc92bfb59ea3b4e3fe245a174ff72e6bfaa8af86fe3c7fc13ebe047ed17f10a7f897f10a6d786ad71696d64cba7df8b680456818261046c777ce72493ed8a00fe3a77bff0078fe74dafeacff00e1d0bfb277fcf6f15ffe0dff00fb551ff0e85fd93bfe7b78afff0006ff00fdaa803e3cff008224ff00c87be26ffd82343ffd2bd46bfa0caf937f66efd8d3e117ecb3a86bba97c329758793c436f6b6f76ba9de0bb50966f2bc7e5feed0a9cccd9e483e80e73f595007f191fb7c7fc9dbfc52ff00b19eefff0045415d7ffc1357fe4f23e1affd85ae7ff4d7a85721fb7c7fc9dbfc52ff00b19eefff0045415d7ffc1357fe4f23e1affd85ae7ff4d7a85007f60e3a0afe703fe0b5e48f8bbe0520e3fe2969ff00f4b857f47e3a0afe6fff00e0b61ff2577c0bff0062bcff00fa5c2803f11fcc7fef1fce8f31ff00bc7f3a651400ff0031ff00bc7f3a723bef5f98f51dea2a7a7df5fa8a00febbbfe096bff2659e0dff00affd7fff004eb775f597c7bff921bf10ff00ec56d67ff48e5af937fe0969ff002657e0dffaff00d7ff00f4eb775f597c7bff00921bf10ffec56d67ff0048e5a00fe192e7fd60ff00ae71ff00e802bf7a7fe0877ff216f8c3ff005ebe19ff00d0b51afc16b9ff00583feb9c7ffa00afde9ff821dffc85be30ff00d7af867ff42d46803fa0ba28a2800a28a2800a28a2803fffd5fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe445fefb7d4d329eff7dbea69940051451401fb77ff00044fff0092bbe3affb15e0ff00d2e35fd201e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd7e04ffc167f41d6f5df11fc328747d3eeef58693ae2b7d96da5b80a4dd5811bbca47db90a719f4afdf6a69556ea01a00fe11edfe1c78eadd9e59340d57688661ff20ebbead1b01d6103a9ef5fdbf7c2d0cbf0cfc228e0ab2e85a68208c1045b47c107a5773e5a7a0fc853e800a28a280336e759d26ce5f22f2f2de09300ec925446c1e870c41a80788741660aba8da1248000b88f249e83ef57f31dff0005859043fb5224ab1c6cff00f08b686a19e3572019751240dc0e33819fa0afcf3f819706e7e2ef839658e1f97c43a23295891483fda36c382003d091f4a00fee5abf1bbfe0b3dff244bc0fff006335d7fe9aaf2bf64057e37ffc167bfe489781ff00ec66baff00d355e5007f32945145005ed3b4dbed5af23b0d3a096e6e263b638a18da576382701103313804e002715d77fc2b2f1eff00d00355ff00c175dfff0019afabff00e09bff00f2781f0d7fec36dffa6ebfafec3446981f28e9e82803f1d3fe08faa7c33f06fc6f67e220da54ede25b7291df2b5a3baae996a85952608c5772919c60e38afd75ff00848b403c7f69d9ff00e0447ffc557f3fff00f05b02b178d7e1bc8123629a16ac577a2b004de5a0c80475c122bf0e6c2e9ae2592296280a9b7b93c431820ac2e41042e4104645007b87c6af01f8c755f8ade2ebcb1d13529a17d7f592922585cba3abea172eacae9132b2956041048e6bef9ff824ae83ac7853f69b6b8f12595d69907fc233ad0f3af6de5b588b3cb61b543cc91a963b18e01ce057f4a3e0f443e13d17e51ff20eb4edff004c52bf3f7fe0ab91c67f642d49591581f10e84082a0820dd2e4104720d007e877fc247a07fd04acfff000223ff00e2ab4ad6eed6f62f3ed258e78c9203c6e1d723af2b91c57f035fda137fcf383fefc47ffc4d7f5bbff04b6444fd8efc3891aaa28d5fc4202a80000353b8c0007000a00fd10a28a2800a28a2803f8c8fdbe3fe4edfe297fd8cf77ffa2a0aebff00e09abff2791f0d7fec2d73ff00a6bd42b90fdbe3fe4edfe297fd8cf77ffa2a0aebff00e09abff2791f0d7fec2d73ff00a6bd42803fb071d057f37fff0005b0ff0092bbe05ffb15e7ff00d2e15fd200e82bf9bfff0082d87fc95df02ffd8af3ff00e970a00fc44a28a2800a7a7df5fa8a653d3efafd45007f5ddff04b4ff932bf06ff00d7febfff00a75bbafacbe3dffc90df887ff62b6b3ffa472d7c9bff0004b4ff00932bf06ffd7febff00fa75bbafacbe3dff00c90df887ff0062b6b3ff00a472d007f0c973feb07fd738ff00f4015fbd3ff043bff90b7c61ff00af5f0cff00e85a8d7e0b5cff00ac1ff5ce3ffd0057ef4ffc10effe42df187febd7c33ffa16a3401fd05d14514005145140051451401fffd6fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa471579dfed83f0375dfda37e026bbf093c39a8da6957fab5c69b347757caed020b1bc86e58308fe63b9622063b9eb5e87f0108ff00851bf0f3fec55d1bff0048e2af5ac8a00fe6fcff00c1143e2e9249f1d785b9ff00a617dfe347fc393fe2effd0f5e16ff00bf17dfe35fd20645191401fcdfff00c393fe2eff00d0f5e16ffbf17dfe349ff0e4ff008bbff43d785bfefc5f7f8d7f483914645007e58fec15fb05f8f3f647f1e788bc53e29f1168facdaeb3a3c7a745169d1dc2491c897026dcde7020a9191c1f4e2bf534f434b914d2460d007f1f3ff052aff93c8f895ff616b6ff00d35e9f5c87ec0fff00276ff0b7fec67b4ffd153d75dff052aff93c8f895ff616b6ff00d35e9f5c8fec0fff00276ff0b7fec67b4ffd153d007f66f5f16fed59fb6e7807f64ad47c3da778d340d67596f11db5e5cc0fa59b50b1259490c6fe61b99e1e4b4ebb76e7bf4afb4abf9f3ff82db7fc87be197fd8235cff00d2bd3a803e81ff0087d07c04ff00a12bc59ff7de95ff00c9f4bff0fa0f809ff42578b3fefbd2bff93abf98dab42caf5806582520f20843cfe9401fd71fecdfff00051cf85dfb4cfc4cb7f85fe11f0cf8834cbeb9b2bbbd5b9d40d91b7096610ba9fb3dcccfb8ef5c7cb8f7afd0fafe56ff00e09150cb07ed67a68991a32741d771b815fe0b6f5afea8f23d6803f21bf6ddff0082757c44fdaa3e327fc2c5f0e789b43d234f1a369fa77d9f508ee5e732d9bdcb17cc3850a44f81c93c76eff2c785bfe090df163e1c788b4df1ddcf8d3c35716fe1fbcb5d5a782186f165962d3a78ee9a342f950ce22da0918c9e7d6bfa21eb5cdf8c7fe453d6bfec1d77ff00a25e803f268ffc1677e032852de08f172ef557019b4b076b80cbc1bec8c820e0f35c3fc46f1b695ff0565f0d45f0dfe0f4371e0dbbf03de47aededc789d62920b9b7bd82eac56384584f3b6f0c589de54003be715fcebeadff001f31ff00d7b5affe884afdceff0082257fc8e3f11bfec03a57fe96de5007327fe08a1f1740ff0091ebc2dff7e2fbfc6bf2afe3f7c1bd57e02fc55f107c2dd66fadb51bbf0fde0b396e6d15d61918c10cf94127cc06d980e79c83ed5fdca1e86bf8efff00828fff00c9e07c4aff00b0e2ff00e9bac2800ff82707fc9e07c35ffb0e37fe9bafebfb101d057f1dff00f04e0ff93c0f86bff61c6ffd375fd7f6203a0a00fe797fe0b6bff238fc39ff00b00eabff00a5b675f855633a5bcecefd1a29a3fa19236407e809e6bf757fe0b6bff238fc39ff00b00eabff00a5b675f83d401fd2ee85ff000591f80fa5e8ba7e9b27833c58cf6b6b040cc1f4bc168e3552466f81c64771f957cc3fb6a7fc14a3e137ed1df02eefe19785bc35e20d33509f54d36f96e2fdac0c012ce612ba9f22ea5932c0617e5c67a902bf12858de91916f2907fd86ff0a6496b7312ef9629117a659481fa8a008475afebb7fe0971ff00267be1dffb0c7887ff004e7715fc890eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2803f43a8a2932280168a4c834b401fc647edf1ff00276ff14bfec67bbffd150575ff00f04d5ff93c8f86bff616b9ff00d35ea15c87edf1ff00276ff14bfec67bbffd150575dff04d5ff93c8f86bff616b9ff00d35ea1401fd840e82bf2cbf6f5fd82fc79fb5c78f3c3be29f0b788b47d1ad746d1e4d3a58b518ee1e49247b8336e5f2400140c0e4faf15fa9808c0a7645007f37dff000e4ff8bbff0043d785bfefc5f7f8d2ff00c393fe2eff00d0f5e16ffbf17dfe35fd20645191401fcdff00fc393fe2effd0f5e16ff00bf17dfe340ff0082287c5d0411e3af0b71ff004c2fbfc6bfa40c8a322803e62fd8fbe06ebbfb397c04d0be12788f51b4d56ff49b8d4a692eac55d60717d7935ca85127cc36aca01cf71d6bd13e3dff00c90df887ff0062b6b3ff00a472d7ace45792fc7b23fe146fc43ffb15759ffd2396803f865b9ff583feb9c7ff00a00afde9ff00821dff00c85be30ffd7af867ff0042d46bf05ae7fd60ff00ae71ff00e802bf7a7fe0877ff216f8c3ff005ebe19ff00d0b51a00fe82e8a28a0028a28a0028a28a00ffd7fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf04158a1cafa11f81183fa57f4a3ff00056df805f183e36ea9f0c24f85be14d4fc4b1e8f6daf2df369d12cbe435d358f941f73a63788df18feed7e38ff00c303fed6dff44b7c4fff008091ff00f1fa00d9d0bfe0a23fb5c787344d3fc3da4fc44d52dec74bb586ceda14b7d34ac70dba08e3405ac59b0aaa00cb13ea4d6aff00c3cabf6c8ffa295ab7fe03697ffc815c8ffc303fed6dff0044b7c4ff00f8091fff001fa3fe181ff6b6ff00a25be27ffc048fff008fd0075dff000f2afdb23fe8a56adff80da5ff00f2051ff0f2afdb23fe8a56adff0080da5fff00205723ff000c0ffb5b7fd12df13ffe0247ff00c7e8ff008607fdadbfe896f89fff000123ff00e3f401d77fc3cabf6c8ffa295ab7fe03697ffc8147fc3cabf6c8ff00a295ab7fe03697ff00c815c8ff00c303fed6dff44b7c4fff008091ff00f1fa3fe181ff006b6ffa25be27ff00c048ff00f8fd0075dff0f2afdb23fe8a56adff0080da5fff0020527fc3cabf6c8ffa295ab7fe03697ffc815c97fc303fed6dff0044b7c4ff00f8091fff001fa3fe181ff6b6ff00a25be27ffc048fff008fd007cebf123e2478c3e2cf8c351f1df8ef519755d6b559566bbbb99634795d23484332c291c6088e345f9514614719c93f457ec0ff00f276ff000b7fec67b4ff00d153d1ff000c0ffb5b7fd12df13ffe0247ff00c7ebe98fd8f3f63cfda5be1f7ed2df0e7c57e2bf875e20d3748d37c416d737979736c890c10a24aa5dcacac4005864e2803fa9fafe7cff00e0b6dff21ef865ff00608d73ff004af4eafe832bf9f3ff0082db7fc87be197fd8235cffd2bd3a803f066cd16499838c8114ac3eab1b107f022bfaf9f879fb057ec81ab7807c35aaea3f0bf459aeaf347b0b89e5612e5e596046763fbcea58926bf907b26559cee38cc5328f72d1b003f12715fd90fc39fdb1bf655d37e1ef8634ebff8b3e1082e6d746d3e19a29357b7578e48edd1595817c82a41047ad007ce1fb5bfc16f85bfb227c0ed63e357ecdbe1db3f0178dec6f34bd3edf5ad3503dc476ba8dec305cc616e3ce888923623e68db0704722bf157fe1e53fb647fd14ad5bff0001b4bffe40afdbcfdb5be2a7c36fda5ff67bd6fe15fecfde27d27e2178c6eafb48bd8343f0f5ec37b7f2dbd8df4335c48b1abfdd8e352cc4900773c8afc15ff8607fdadbfe896f89ff00f0123ffe3f401fd28ffc13bbe28f8ffe30fecd965e35f895accdaf6b72eb5abdabde5c2431bb436d7063894ac11c51fcaa3b28cd7d85e31ff914f5affb07ddff00e897af8bff00e09bdf0fbc6ff0cbf663b1f0a7c41d12f3c3fac47adeb170f657e82399629ee0bc6c402c30ca72082457da1e31ff00914f5aff00b07ddffe897a00fe0eb56ff8f98ffebdad7ff44257ee77fc112bfe471f88dff601d2bff4b6f2bf0c756ff8f98ffebdad7ff44257ec37fc123be317c2af84be27f1ddefc4ef16691e1682fb46d360b57d5aee3b459a58aeee9dd10c840665575240e8181a00fe988f435fc77ffc147ffe4f03e257fd8717ff004dd615fd399fdb4ff64ac1ff008bbde0dffc1c5b7ff175fcb57edebe2ef0b78e7f6a3f1f78a3c1bab59eb7a46a1ab89ad2fac6659ede78fec3671ee8e4525586f465c8e32a476a00e8bfe09c1ff2781f0d7fec38dffa6ebfafec407415fc66fec15e2ef0b781bf6a3f00f8a3c65ab59e89a469fab99aeefafa6582de08fec3791ee924621546f755c9e32c077afea547eda7fb25607fc5def06ffe0e2dbff8ba00fc82ff0082daff00c8e3f0e7fec03aaffe96d9d7e16e9d124b70cb20c8582e1c76f99227653f8100d7ef97fc1483c33ac7ed87aef8375ffd98adcfc4dd2b43d3751d3b53bdf0cbc57f059ddcd716b3c714ae24550ed1a16db9ce3071820d7e665afec21fb5b5a3c92ffc2abf13b930cd181f648c64c91b20ff0096c7b9f43401fd24f863f603fd8eef7c39a55ddcfc2dd1249a6b1b69247226cb3bc4a589fde7524e6be31ff828bfec8dfb37fc25fd9a2ffc61f0e7c07a6685acc7ad68f6cb796a24122c3717212551b9c8c3a9c1e3a57e81e8bfb5e7ecbda069165a16b7f153c2763a8e9d6d0da5ddadc6ab04734171022a491488cc0aba3a95652320820f35f29fedc9f12be1ff00ed41f002ff00e177ecefe23d2fe22f8b9f54d2f515d17c3b790df5e9b3b3b9579e6f2d1f848c119624004819c91401fcb00eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2bf9d6ff8607fdadbfe896f89ff00f0123ffe3f5fbd5fb14fc54f86dfb347ecf7a27c2bfda07c4fa4fc3df18dadf6af7b3e87e21bd86cafe2b7bebe9a6b791a367fbb246c1948241ec783401fa39f10f50bdd27c03e25d574d99adeeecb47bfb8825500b472c503b230c8232ac01190457f2572ff00c1493f6ca80a46df13355663144cc45ae96012e8ac703ec070327d6bfa35f88dfb637ecaba97c3df13e9d61f167c213dcdd68da8430c51eaf6ecf24925bbaaaa80f92589000f5afe37af595a71b4e71142a7d8ac6a08fc08c5007f4b7ff04aff00da53e35fc7ed77c7d17c59f155df88a0d2b4dd227b18eea1b58fc892e2e2f2394a9b6820cee5853ef6718e3ad7ecb57f3e7ff0449ff90f7c4dff00b04687ff00a57a8d7f419401fc647edf1ff276ff0014bfec67bbff00d15057cebf0dfe2478c3e1378c34ef1df81351974ad6b4a95a6b4bb85637789de3784b2acc9246498e475f9918618f19c11fa45fb61fec79fb4b7c41fda5be2378afc29f0ebc41a9691a97882e6e6cef2dad91e19e1748943a16954904a9c1c57ccfff000c0ffb5b7fd12df13ffe0247ff00c7e803adff0087957ed91ff452b56ffc06d2ff00f90297fe1e55fb647fd14ad5bff01b4bff00e40ae47fe181ff006b6ffa25be27ff00c048ff00f8fd1ff0c0ff00b5b7fd12df13ff00e0247ffc7e803aeff87957ed91ff00452b56ff00c06d2fff009028ff0087957ed91ff452b56ffc06d2ff00f902b91ff8607fdadbfe896f89ff00f0123ffe3f47fc303fed6dff0044b7c4ff00f8091fff001fa00ebbfe1e55fb647fd14ad5bff01b4bff00e40a3fe1e55fb647fd14ad5bff0001b4bffe40ae47fe181ff6b6ff00a25be27ffc048fff008fd1ff000c0ffb5b7fd12df13ffe0247ff00c7e803aeff0087957ed91ff452b56ffc06d2ff00f902b2b5dff8288fed71e23d1350f0f6adf11354b8b1d52d66b3b985edf4d0b2437086391095b156c32b1070c0fa1158dff0c0ff00b5b7fd12df13ff00e0247ffc7e8ff8607fdadbfe896f89ff00f0123ffe3f401f1e3317396f403f00303f4afdefff00821dff00c85be30ffd7af867ff0042d46bf34ffe181ff6b6ff00a25be27ffc048fff008fd7ec77fc124be017c60f825aa7c4f93e29785353f0d47ac5b682b62da8c4b179ed6ad7de684daef9d8244ce7fbd401fb53451450014514500145145007ffd0fdfca28a2800a28a2800a28a2800a28a2800a2bf0abe25ff00c161b5ef87df117c55e051f0e34bb94f0e6b9a9e8e93c9ad5cc6f32e9f7525b891916c64552fe5eeda19b19c64d713ff000fb6d7bfe899691ff83dbbff00e575007f417457f3e9ff000fb6d7bfe899691ff83dbbff00e5757eacfec69fb48ea3fb537c239be276a5a15bf87a44d62f34c5b4b7ba7bc42b6823fde798f14272c5cf1b78007738001f5951451400514507819a0028afc69fda53fe0aa1aefc00f8d7e2af84d0f8074ed520f0eddc56b1df4fabdc5bc93f996b05c9631476532ae3cfdbf7ce719e2b1be037fc15975ff8d1f183c23f0bdbe1ee99a7c5e25d561d3a4bc8758b89dedd65576de237b28836021e370e6803f6bebf9f3ff82db7fc87be197fd8235cff00d2bd3abfa0caf85ff6befd877c39fb5dea7e1ad4b5ff0014df787bfe11bb5bdb548eced60b813adec9048c5fcece369817181dfad007f1d757d755d4d542addce00180048d8007e35fd1d7fc394be198ff009a91ad751ff30bb1ff00e26bf9f8f8abe0fb7f00fc43f11783ad2e1eea1d1b57d4b4e8e67508d22d8de4d6cae557805c441881c027038a00fd19ff0082465d5cdd7ed69a6b5ccaf295d075e00bb16c7c96deb5fd50d7f153fb23fed2573fb2e7c56b7f89969a2c1aec9058df590b5b8b992d2322f563058c91c533657cbe06c39cf518afd4bff87db6bdff0044cb48ff00c1eddfff002ba803fa0bae6fc63ff229eb5ff60fbbff00d12f5e09fb21fed037ff00b4cfc1bb7f8a7a968b068134fa95fd81b2b7b97bb8c0b297cade2578e263bf19c6c18afa3b57d3c6ada5de696ce621776f2c05c0c95f350a671c6719cd007f04fab7fc7cc7ff005ed6bffa212a9c175736a49b695e22c304a315c8fc2bfa433ff0454f86ee13cdf895adbb2a2264e996478450a3a827a0ee49f7af863f6eeff827df847f650f00787fc5da078b2ff5d9b58d5a6d3de1bbb3b7b758d23b39ae770308049262db8391839eb401f95bfdadaaff00cfe4ff00f7f1bfc6a94b2c933996676776e4b31c93f526a3a280248a5921712c2ec8ebc8653823e8455dfed6d57fe7f27ffbf8dfe35eeffb2cfc1cd37e3cfc6ff0a7c2ed5b509b4bb5d7efcd9c97504692c91016d713ee5493e4273005c1e3049ea057ee00ff0082297c33233ff0b235affc15d8ff00f13401d37fc11964925f82be3a92562eede26b52598e493fd9567d49afd8d35f865e2bf1ec7ff048fb1b3f873e14b31f112d7c74f71afcd79ab5c7f653d9b592dad888512d6de7575652a724291839ce45705ff0fb6d7bfe899691ff0083dbbffe575007e3c7c74bfbeb7f8b9e308edee258d3fe121d6ced472a32752baec0d7e85ffc11eeeae6ebf6a22d732bca57c2fae805d8b6079ba77ad7e5cf8f7c527c6be2fd5fc52d6eb6a755bfbdbe30ab17119bcb896e0a86201214c9b4120120670338afd3ff00f823a7fc9d03ff00d8b1aeff00e8dd36803fa8eafe57bfe0ae775736bfb5a6a4d6d2bc45b41d04128c573f25cfa57f5435f9a9fb507fc136fc21fb4f7c54b9f8a3aef8d352d167b8b1b3b1fb1dad95b4f105b30e15b74d96c9f30e7a7e3401fc9eb6aba9b2956bb9c82304191b041fc6a857f44de30ff8235fc37f0e784b5bf1043f113589a4d334ebbbc48db4db250ed6f13481490b90095c64735fcf05c4421902039ca46fff007da86c7e19a00fde9ff8224ffc87be26ff00d82343ff00d2bd46bfa0cafe7cff00e0893ff21ef89bff00608d0fff004af51afe832800a2bf143e3cff00c15975ff0082ff00183c5df0bd7e1ee99a845e1ad566d3a3bc9b58b881ee162546de634b2942e438e371e6bc8ffe1f6daf7fd132d23ff07b77ff00caea00fe82e8afe7d3fe1f6daf7fd132d23ff07b77ff00caeafd39fd88ff006acd47f6b5f00ebfe34d47c3b6de1c6d1f591a5a416b7925e24aa6d60b9126f9218083fbedbb76f6eb401f69514d63b549f415f819af7fc1683c47a0eb7a868f2fc33d249b2bbb9b5dcdae5d02df6795e2dd81a7b01bb667193d6803f7d68afcabfd8c7fe0a33ac7ed5df16a6f86b79e0bb1d0608b45bdd585e5aea735e396b496da2f2cc725ac0006fb46770638dbd39afd54a0028a28a0028a28a0028a28a0028a28a0028a28a00fffd1fdfca28a2800a28a2800a28a2800a0d141a00fe1e3f69cff009388f89fff0063af893ff4e5715e155eebfb4e7fc9c47c4fff00b1d7c49ffa72b8af0aa002bfab3ff82427fc9a74dff635eaff00fb46bf94cafeacff00e0909ff269d37fd8d7abff00ed1a00fd49a4cfd7f2ac8f10b32685a8ba31565b49c865382088db9047435fc4a4ff00b467c73b63144be3df14bfee6262cde20d5324b20249c5d81c93d801401fdc167ebf95213c1ebf957f0eff00f0d27f1d3fe87bf147fe143aafff0025d4f6bfb47fc729ae6289fc77e28daeeaa7fe2a1d57a138ff009fba00f7ff00f829242d3fed95f13111e30cbaada92af2221c369761838620e0e0f35c8fec1b09b7fdadfe16798f165bc4d6980b2a39e239bb2927b8fcebfa4bfd887c2be18f1bfeca7f0e3c57e34d22c75fd6f53d27cfbdd4b54b68ef6f6ea532c837cd3ccaf2c8d80065989c003a015f59d9fc38f87ba75dc37fa7786346b5bab771243343a7dbc7246e3a32b2a02a47620e6803b4a4cd2d7e137fc1623e2278ebc07e21f86ede0df10eafa22cfa56b524c9a6ea37560b33c77360a8d20b6962de543b05dd9c64d007eeb93f5ea2bf877fda5011f1d7c780f1ff001547887ff4eb794a3f694f8ea0e478f3c51c7fd4c3aaff00f25d78cea3a8deead7b3ea3a8cf25cdcdcc8f2cb2caed248f248c599999896666624b3124b124924926802951451401fd6bffc1293fe4d0f4eff00b1875eff00d2b6afd25afcdaff0082527fc9a1e9dff630ebdffa56d5fa07e2e778fc2dac491b3232d85d156524302227208239045007439fafe55f8ddff059e23fe149f817732a6ef13dca82ec1172da5de0032d81c9381ef5f82377fb457c72b478e14f1ef8a587d9edd8b3788354c96789189e2ec0e493d062bf5fff00e0929e22d6fe3178b7c7b6ff00152faefc5f6f65a369b716906bf733eaf15b4d25d5d46f242b7b24fe5b3a22ab15c640e6803f047ec13ff7a1ff00bff17ff1555648da26d8f8cfa82181fa11907f0afeef0fc2bf86183ff148683ff82cb6ff00e375fc8eff00c143f4cd3748fdadbe2358695690595b43ad2ac70dbc6b1468bfd9f6270aa80003249e07527d6802f7fc1383fe4f03e1affd871bff004dd7f5fd880e82bf8eff00f82707fc9e07c35ffb0e37fe9bafebfb101d05007f3cff00f05b101fc6bf0de20e8acfa16aa143baa648bcb438058819c027f0afc29fb04ffde87feffc5ffc557f799ad783bc25e249a2b8f11e89a76ab2c0a5227bdb48ae19158e4aa991588048c903bd629f857f0c4608f08e83ff0082cb6ffe37401fc20b2953b4f515fadbff000474ff0093a07ffb1635dffd1ba6d7e757c73b7b7b5f8b5e2d82da248624d7b595548d42aaaaea372a000300000000760315fa2bff000474ff0093a07ffb1635dffd1ba6d007f51d4514500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afeecfe2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b401fbc7ff0449ff90f7c4dff00b04687ff00a57a8d7f4195fcf9ff00c1127fe43df137fec11a1ffe95ea35fd065007f191fb7c7fc9dbfc52ff00b19eefff0045415f1d57d8bfb7c7fc9dbfc52ffb19eeff00f45415f1d50015fd397fc117ff00e48278d7fec6d4ff00d355857f31b5fd397fc117ff00e48278d7fec6d4ff00d35585007ec3cbfeadbe87f957f085f137fe47dd7ffec2ba8ffe95cd5fddecbfeadbe87f957f085f137fe47dd7ff00ec2ba8ff00e95cd401fa71ff00046bff0093a3bdff00b13f58ff00d2ad36bfa88afe5dff00e08d7ff27477bff627eb1ffa55a757f51140099a33f5fcabf97cff00829cfc5ff8a1e08fdae3c51a4f85bc5bafe9760b61a33a5a596b17d696e8d25a02ecb1413c68a58804e07279ea493f9f3ff0d27f1d3fe87bf147fe143aafff0025d007f7139fafe54b9afe1d7fe1a4fe3a7fd0f7e28ffc28755ffe4bafdb7ff8236fc49f1dfc41d57e2baf8d3c41aaeb6b636de1e36eba96a3777e213335fef31fda669766ed8bbb6e33819e82803f74e8a28a0028a28a0028a28a00ffd2fdfca28a2800a28a2800a28a2800a28a2803f9a0f8c9ff0004b1fda8fc77f16bc6de32d22c3447d3f5df126b1aa5a33eb29139b7bebc9a78b72181b6b6c719193835e6dff0e85fdacffe81da17fe0f53ff0091abfaa8a2803f956ff8742fed67ff0040ed0bff0007a9ff00c8d5fa2bfb337c5ef007fc13cfe1c37c01fda5f50934af17cda85cf8892df48b3bbd62dffb3f516d90399eda02818bc12295201f9738c115fb295fcbbffc164ffe4e8ecbfec4fd1fff004ab51a00fd71bcff00829a7ec8fad5acba2d87887566bad414d9dbab683a8a2b4d71fba8d4b340146e76032481cd7e38bffc122ff6b5b811bc9a7683b8471a12baea00762819c7d98e338f535f9c9f0cbfe47dd03fec2ba77fe95c35fddec7f717e83f95007f2b5ff0e85fdacffe81da17fe0f53ff0091aa487fe0915fb59c12a4c34dd0498d8363fb753f84e7fe7dabfaa4a43d0d007e517c16fdadfe077ec89f0b7c3bfb36fc6ad5ef2c7c6fe02b34d375ab7d3f4bbdd42d63b871f68511dcc10b4720314c8dc1c8dd820115ef9e03ff008288fecbbf123c67a2f807c29aeea771acebf789636314da25fdba493c8090a64961545e149c93d057f3b3ff00052aff0093c8f895ff00616b6ffd35e9f5c87ec0ff00f276ff000b7fec67b4ff00d153d007f66f5fcf9ffc16dbfe43df0cbfec11ae7fe95e9d5fd0657f3e7ff05b6ff90f7c32ff00b046b9ff00a57a75007e07d145140051451401fd6bff00c1293fe4d0f4effb1875effd2b6afd0bf11d9cfa8e81a969f6a034d7367710c609c02f246caa33db93d6bf3d3fe0949ff2687a77fd8c3af7fe95b57e92d007f2b937fc1237f6b7ba31c92e9ba08758a28ce35d40331a2a671f6738ce338c9fad7e99ff00c136bf63af8cff00b2ef8a3c65a8fc50b4d3aded359d2ec2d2cdacb505bd6692dee2795f7811c7b462518e0f4afd70a28010f435fcf67ed75ff04defda47e367ed0de36f88de11b0d1e4d135bd4d6eaca4b8d592de668fec96d09df19864dbf34271f374ed5fd0a51401fcf5fec8bff04defda47e09fed0de09f88de2eb0d1e3d1344d4daeaf64b7d592e2658fec97308d918863ddf34c33f374ed5fd098e82968a00f9a3e3b7ed6ff0004ff00670d5349d1fe2aea77b6175addb4d7566b69a6dd5f878addd6390936f1becc33a8f9b19c8af076ff0082a47ec76aacede23d602a02cc4f87b53002a8c924fd9f8000c935f9e7ff0005b4ff0091c7e1cffd80755ffd2db3afc31d2bfe3e64ff00af6baffd10f401fadbe3bff825bfed47f107c61ad78d348d3f443a7eb7a95f6a566d26b291486dafee65ba88ba181b6bec94061b8e0f7afaff00fe09f3fb07fc7bfd9b3e3a1f1d7c45b2d2e1d18e85a9d8f9967a9ade4bf68bb7b4641b0451e1710373cf26bf667c1dff00229e8bff0060fb4ffd1295d250014514500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afeecfe2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b401fbc7ff0449ff90f7c4dff00b04687ff00a57a8d7f4195fcf9ff00c1127fe43df137fec11a1ffe95ea35fd065007f3a5fb507fc1343f698f8bdf1f7c75f113c33a7e8cfa3ebdaddc5f58b4daba412986448d4178cc2fb4fc99c67bd7c83f15bfe099dfb47fc1ff00879ae7c4bf175968f168fe1fb6fb55e3c1aba5c4a23dcabf2c62042c72dd370afebaebe37ff82837fc99a7c55ffb021ffd1d1d007f19d246d148d13fde462a7ea38afe9bff00e08bff00f2413c6bff00636a7fe9aac2bf997beff8feb8ff00aeafff00a11afe9a3fe08bff00f2413c6bff0063627fe9aac2803f621c165207706bf978f157fc1277f6b0d7fc49aa6b11e9ba108ef2faeee23ff89da29d93cf24ab91f676c101b07935fd44d1401fcf5fecbdf00bc67ff04e6f8852fc77fda58d9e93e0fb9d2aebc3b1dc6957126b170750d466b69605305bc01c215b671bb07e62a38cd7e837fc3d1ff63dff00a18b58ff00c27b53ff00e47ae0ff00e0af7ff269d0ff00d8d7a47fed6afe54109debf51401fb93fb477eca7f137f6f6f8a1a87ed23fb3f269fa9781b5f82d6cec2e751bc6d2ae9a6d255ad2e43db4f019102ce8ea37004edcf422be68f157fc129bf6a4f09786757f156aba7e88b65a358dcea170535a4918436b1b4ae557ece371daa703233eb5fb79ff04b4ff932bf06ff00d7febfff00a75bbafacbe3dffc90df887ff62aeb3ffa472d007f0bce850e1bd01fc08c8fd0d7ef7ffc10effe42df187febd7c33ffa16a35f82d73feb07fd738fff004015fbd3ff00043cff0090b7c61ffaf5f0cffe85a8d007f417451450014514500145145007ffd3fdfca28a2800a28a2800a28a2803f2b7fe0a43fb657c54fd94350f005bfc371a518fc4d06b125e7f69583de9dd60d6823d9b6e6df6e44ed9ceecf1d3bfe62ffc3e27f6a370e224f0b9708eca1b4394025549c1235138ce3ae0d7d03ff05c4ff90b7c1eff00af5f137fe85a757e0adb902439fee49ffa01a00feedbe14f88753f16fc30f08f8af5b3136a3ace85a6ea176604314467b9b78e590a21662abb98e14b3607193d6bbfaf06f813e2cf0bc1f047e1f4336b1608e9e17d19595aea2041167164105f822bd57fe131f09ffd0674ff00fc0b87ff008ba00e92be3ef8e7fb0dfc04fda27c6b1f8ffe255a6ab3eaf1d841a687b2d4e7b38fecf6ef2c91829110090d339c9e79afa77fe131f09ffd0674ff00fc0b87ff008ba3fe131f09ff00d0674fff00c0b87ff8ba00f81ecbfe095ffb2569f7b6da85b69fe20135acd15c445b5cba602485c3a1209c1c3283cd7e8d8014003b715ce7fc263e13ff00a0ce9fff008170ff00f1747fc263e13ffa0ce9ff00f8170fff0017401d2515cdff00c263e13ffa0ce9ff00f8170fff001747fc263e13ff00a0ce9fff008170ff00f17401f1efc51ff82777ecd9f183c7dacfc4af1ad96b52eb7af4c97178f6babdc5b42cf1c31c0a5628c855fddc4a38eb8af09f895fb0e7c00fd97fe1f788ff00688f85d61aa278bbe1d69779e22d15b51d52e6f2cc5ed8c2ef1f9d03b01221e430c82413820f35fa71ff00098f84ff00e833a7ff00e05c3ffc5d7ce7fb5e6b5a46bffb2f7c54d1342bdb6d4751bef09eab6f6b6969324f713cd240ca91c71a333bbb1202aa8249e073401f85375ff0584fda86d6e65b665f0bb189d9091a14b83b4e3fe8255f1efed3dfb60fc4dfdaaae743baf88aba609341b7bab6b63a758b590d977243249bc35c5c6e3ba04c1057033c1ce479aea1f033e2e5c5f5c5c47e10f106c925775ce89a90386248ff00975ae13c53e03f17f828dbaf8a748bfd28dd2b3422facae2ccc8a84062a2e228cb004804ae402467191401ccda46b2ca55fa08e571f544661fa8afea8fc07ff04b8fd93b5cf03f87b5bbfd3f5f373a86956375314d72e957cc9a0476c28380324e076afe57ec48133678fdccff00fa2dabfb92f85fe2ef0b45f0d3c251c9ac69eac9a169aac0ddc20822da3c83f3d007e3cfedd1fb00fecebf03ff00674d67e21780acb588758b4d4748b689eef55b8ba8847797b1412e63909527639c1c641e457f3cf5fd6a7fc14efc47e1fd43f642f10dbd8ea7677129d5fc3e4245711bb1035280938562781cfd2bf92b3d6803fad7ff0082527fc9a1e9dff630ebdffa56d5fa4b5f9b5ff04a4ff9343d3bfec61d7bff004adabf496800a28a2800a28a2803c3bf696f881aff00c29f807e3cf893e16101d5fc37a15e6a3662ea332c0668137289103296538e4061f5afe7aaebfe0b09fb50dadd4d6acbe17630c8d1923429704a9233ff00212afde8fdb46c2ff54fd947e2ae9ba5db4d797775e17d42282dede3696596478f0aa8880b3313d00049afe42f50f819f172e2fee6e23f08788364b348eb9d1352070cc48ff975a00fde7fd9bfc33e1fff00829bf84350f887fb4ddaf9fa9f837513a36927c3d2cfa3442cef2d6daf64f3512698bb99180c97c61460039cfd0adff04a3fd90d9190e9de21c3ab29ff0089edd1c861823ae0820f43c578effc1242ceebc01f083c69a6f8e2197c3f753788ede4861d5a37d3e59634d36d633224774b13b26f465dc1704835fac9ff00098f8487275ad387fdbdc3ff00c5d006be9d630699636fa75b6ef26d618e08f71cb6c8d42ae4f7381cd5da623ac8a1d0865600820e4107a1069f40057e21feddff00f0506f8e7fb35fc7bbdf875e061a11d1a1d2f4cbc8fedda6497771e6de2ca5f2eb7700da3cbe06d3d6bf6f2bf98dff0082aafc37f1ef8bff006aad4af7c37e1dd5f51b5fec3d1505c59e9b77750978d2e372f9904322ee5dcb919ef401c76bdff0574fda5bc41a1ea3a0de2786bc8d4ad27b497668922b6c9d1a36c31d45b070dc1c1c7a1afcab9a5699f7b765551f4450a3f415ea971f037e2cdadbcb753f84b5e48a14691d9b46d455555064925ad400001c92401dcd793b295386e0d007d67fb30fed83f137f655b9d72ebe1d2e9864d7aded6dae4ea362d7a365a493491ec0b716fb4ee9df2496c8c703193f5dff00c3e2ff0069ff00ee7863ff000452ff00f2cabf2428a00fd6ff00f87c5fed3ffdcf0c7fe08a5ffe59579bfc5eff00829f7ed01f197e1b6bdf0c3c529a07f64f886dbec977f65d224b79bcbdcac76486fa50872bd7637d2bf3628a007cb234b23caff79d8b1c7a939afb83f66afdbd3e30fecbde11d4bc1df0f9347365aa6a03529cea1a73de49e70822b7f9596eadf6aec8578da79c9cf381f0e5779e19f867e3bf18d949a8786741d5354b6864f25e5b2d3eeeed164da1b6b35bc32aab6d20e0907041c6083401fa67ff000f8bfda7ff00b9e18ffc114bff00cb2a3fe1f17fb4ff00f73c31ff0082297ff9655f9dff00f0a17e2fff00d09fe20ffc126a5ffc8b5e4d3c12db4af04e8d1c91b156560559594e0820f2083c107906803f7b3f671fda0fc57ff0526f1d4bf007f68ab7d3e7f08dbe9973e2355d0ede6d22f3edfa6cd6d1404ce2ea7263c5d392a029dc14e78c57dcbff0ea4fd90ffe81de21ff00c1f5dff8d7e4e7fc11affe4e8ef7fec4fd63ff004ab4eafea22803cb7e0d7c1ef057c07f87f61f0cfe1f45730e87a6cb7334097770d73307bb99ee252d2bfccd992463cf4cd769e29f0e69be30f0ceade13d64486c35ab1b9d3ee844e6390c1751b4526c71cab6d63861c83cd3eefc4be1eb0b86b5bed4ecede64c6e8e5b88e3719191956604645431f8b3c2f34890c3abd83c92305545ba88b331380000d9249e82803f3e57fe0945fb2122aa269be200aa028035dbb0001c00066be8efd9f7f649f83bfb325c6bb75f0aedb51b793c4496b1df1bfbf96f772d9194c5b7cd27660ccf9c75cfb57d3345001451450014514500145145007fffd4fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf03ba722bf7c7fe0b89ff216f83dff005ebe26ff00d0b4eafc0ea00d04d4ee514204b721400336d093c7a92849fa9a7ff6b5d7fcf3b6ff00c0583ff8dd6657d21fb2bfecf97dfb4cfc5ed33e1569fab5b68d36a36f7b3add5d4325c44a2ce1f3482913c6c4b740430c1eb9a00f02fed6baff009e76dff80b07ff001ba3fb5aebfe79db7fe02c1ffc6ebf73bfe1c97e31ff00a28da0ff00e0aaf7ff00932bf33bf6bffd98350fd94be265bfc39d4b5ab4d7269b48b4d57ed1676f2dbc616ee5b88c26c96495b2bf67249dd83b870307201f317f6b5d7fcf3b6ffc0583ff008dd1fdad75ff003cedbff0160ffe37599450069ff6b5d7fcf3b6ff00c0583ff8dd1fdad75ff3cedbff000160ff00e3759952c3119a68e10706460b9f4c9c5005efed6baff9e76dff0080b07ff1bafaeff610ba7bafdadbe15f9b1c20a789ed30638638cf31cddd154f61f957d6bf017fe0941e26f8e1f083c2ff0015ec7c73a3e9d07892cbed896971a75d4b2c20bb2ed674b98d58fcbd4281ed5efde19ff826febbfb1e6b36ff00b4eebfe32d375bd2be19193c4d7ba669fa75c437779058452178a2927ba78d5cab1dbb86338c9033401fbf58afe7d7fe0b6dff0021ef865ff608d77ff4af4eaf7c93fe0b37f056291a293c0de2657425581bad2b208ea3fe3f2bcffc7be15b1ff82b84767e2bf87378fe04b5f8762e349bc875fb75bd6bc7d57ecf748f09b1ba0aab1ac001258e4b630319a00fe74c120823823a1ad05d52e514284b7200039b6849e3dca64d7ee87fc392fc639e7e23683ff82abdff00e4cafc55f88fe0e97c01e37d73c1b35c25d3e8ba9dfe9cd346a51246b1ba96d99d5589203988b0049201009279a00e5a5d46e26428cb0a83de38228dbfefa55079efcf35468a2803fad7ff0082527fc9a1e9dff630ebdffa56d5fa4b5f9b5ff04a4ff9343d3bfec61d7bff004adabf496800a28a2800a283c0cd7e617c6cff0082a37c2df81df147c45f0b3c41e10d7efaf7c3b76b6735d5b4fa7c704aed0c53e6313dcc72602ccb9caf5a00fd3da4c57e61fc13ff0082a37c2df8e3f147c3bf0b3c3fe10d7ec6f7c4576d670dd5ccfa7c9044eb0cb3e64105cc9260ac2d8c2f5afd3d1c8cd007f3cfff0005af7f27c6df0de6548dd9341d58af991ac8013796632038233824671debf0eac6fa5b99658668adca9b6b93c5b42a41585c820840410464115fb85ff05b5ff91c7e1cff00d80755ff00d2db3afc31d27fe3e64ffaf6baff00d10f401fde2f83b8f0968a076d3ad3ff0044a57495cdf83bfe453d17fec1f69ffa252bc9ff00692f8fda0fecd5f0c66f8a1e24d2ef758b282facec4db69ed12ce5ef64f2d0833bc6980c4672c38a00f7ca315f8ddff0f9ff00825ff423f897ff0002b4affe4ca3fe1f3ff04bfe847f12ff00e05695ff00c99401faa9f153fe498f8bff00ec03a9ff00e93495fc26dfff00af5ffae307fe8a5afe9853fe0abbf097e2d67e17e8de0ef10da5ef8bca787edee679f4e921b79b5665b38a59562ba790c6924ca5b6a938ce057ce8dff044ef1ab85f33e24684eca889b8e937809d8a14138bc03381ce00a00fc1ca2bf787fe1c97e31ffa28da0ffe0aaf7ff9328ff8725f8c7fe8a3683ff82abdff00e4ca00fc1ea2bd7be3b7c2ab8f829f15fc4ff0c6eafa2d4a5f0e6a52e9ef75046f1472b44a8db951d9d947cf8c16278eb5adfb387c17bafda03e2ff873e14d9ea3069537882ea4b64bbb889e68a231db4f739648dd19811015c061cb039c02080785d7f4e5ff000460e7e01f8d7fec6d4ffd355857cddff0e4bf18ff00d146d07ff0557bff00c995fa89fb0efecabacfec99f0ff005ff05eb3af59f8824d635a1aa47359db4b6a9120b482d846565924627f71bb3bbbf4a00fb4e4fb8dc9e87f957f087f137fe47dd7ff00ec2ba8ff00e95cd5fddecbfeadbe87f957f085f137fe47dd7ffec2ba8ffe95cd401fa71ff046bff93a3bdffb13f58ffd2ad3abfa88afe5dffe08d7ff0027477bff00627eb1ff00a55a757f511401fc9cff00c156a6fb1fed97e2b9218a1dd269da1ee2f0c72127ec6067e753ce0019f403d057c95fb355f4b73fb42fc308e48e003fe135f0df290448c3fe2676fd1950115fbf1fb5c7fc132fc55fb4c7c70d67e2cd8f8d74ad22d352b6b0b78ecaeb4fb99e58fec70088b1922b8894ee6c9e9c0c77ce7e79d1ff00e0945e27f811aad8fc6cd43c73a4ea569f0feeedfc55716369a6dcc5717716892adf3c113cb76d1a3cab09456705416c9e2803fa091457e362ff00c1683e0932abff00c20de270180601ae74b5386191906f011c1e879afb0bf64dfdb5fc11fb5c5d789ed3c1fa0ea9a33785e3b192e0ea32dac82517e6709b3ecd34b8dbe4367763a8c679c007da3451450014514500145145007fffd5fdfca28a2800a28a2800a28a2803f9fbff0082de5add5d6aff0007c5b4324c56d7c4b911a96c65f4eeb806bf063fb2356ff9f2b9ff00bf4ffe15fdc9fc4bf815f07be3249a7cdf14fc21a478a5f4a5996c8ea96cb706dc5c143288f77ddde517763aed1e95e5ff00f0c3ff00b227fd123f09ff00e0b62a00fe2f3fb2356ff9f2b9ff00bf4ffe15fa51ff0004a2b4bbb5fdb23c2c2e619212da76b84798857205a7b815fd0dff00c30ffec89ff448fc27ff0082d8abacf03fecb7fb3bfc34f125b78c7c01f0f3c3fa06b766b22dbdfd859243711acc863902baf23723153ec6803df2bf97eff82c65add5d7ed4966b6b0c9315f0768d911a96c7fa56a5d700d7f5035e21f10bf66cf80bf1635e4f147c4af02687e25d5a3b74b45bcd4acd2e265b789999230cdc855676207a93401fc41ff00646adff3e573ff007e9ffc28fec8d5bfe7cae7fefd3ff857f687ff000c3ffb227fd123f09ffe0b62a3fe187ff644ff00a247e13ffc16c5401fc5e7f646adff003e573ff7e9ff00c2ad59693aaadedb9365700095092627f51ed5fd9cff00c30ffec89ff448fc27ff0082d8a90fec3ffb22019ff8547e13ff00c16c5fe1401cff00fc13e881fb1a7c2ac9ff009820ff00d1d25757fb6991ff000c95f17b9ff993758ffd267afc1efdad3f6a9f8e3fb36fc7cf17fc1cf831e2abdf09f827c357b05a68da1e9715947676303d8dadc324625b595c0696776c6ec0ce0002b0ff00669fdb03f683f8f3f1c7c19f093e2978cb50f117843c55abc5a56b7a3dfc764f6b7f63711cbe641288ed227d8fb40601864641eb401f97daae93aab6a776cb6770419e420889882371f6afe8b3fe08ad14b0fc36f8931cc8d1baeb5a582ac0820fd857a835fa11ff000c3ffb227fd123f09ffe0b62ff000af5bf86bf067e15fc1db5beb1f85be16d33c2f6fa948935dc5a65badba4d2460aab385e0900900d007a61e9f88afe20bf68dd3efee7e38f8f24b7b69a54ff0084a7c4237246cc32355bbee01afedfabe65d53f632fd9575bd4ef35ad5fe16785ef2ff0050b896eaeae26d3e379669e762f248ec7966662493dc9a00fe2bbfb2356ff9f2b9ff00bf4ffe147f646adff3e573ff007e9ffc2bfb43ff00861ffd913fe891f84fff0005b151ff000c3ffb227fd123f09ffe0b62ff000a00f03ff8252ab2fec8ba72b0208f10ebc083d47fa5b57e925715e00f871e05f859e1e4f09fc3bd0ecbc3da3472c93a58e9f10860592639760838058f26bb5a0028c81d68afcc2ff82a37c6cf8a3f03fe16f843c41f0b3c4577e1dbdbed7e7b6ba9acd61679608f4fb99c467cf8a6503cc8d4e76e6803f4ec9183cd7f1f5ff0518b2bcbafdaff00e25b5ac12cc175c504c68580ff008975875c034597fc1463f6bfbabc82d9be25eb8a269523242e9dc6e2067fe3c3debf7f3f67afd9ebe077c79f81de05f8c5f187c0ba0f8b3c69e2cd06c752d6b5ad4ac2296eefaee58943492300067000000000000000c5007e01ff00c139ecaf6d7f6bff00868d7304b086d718032232e7fe25d7fd32057f60c3a0af9e7c27fb267ecd7e03f11d8f8bfc19f0dfc3ba2eb5a64865b3bfb2b1486e2072a509475e412ac41f635f43d007f3d3ff0005ae827b9f1a7c398ede3795ce83aa9da8a58f17b67d866bf0e74ed33528669659ad27445b5ba259a36007ee5fa922bfb7ef893f00be0c7c62bbb2bff8a5e0dd1fc5371a6c6f0da49a9db2dc3411c8c19d537740c40271d715e69ff0c3ff00b227fd123f09ff00e0b62a00fa13c1c40f09e8bcff00cc3ad3ff0044a57e7f7fc156c83fb21ea3ff00630e83ff00a56b5f89ff00137f6f7fdab7c17e3ef10f85f44f88bac5be9da5eafa9d959dbc4b62120b6b4bc9ade18d77d9bb9091c6a32cc49ea4d7817c4ffdb3bf68af8c1e1397c15f107c69a9eb5a3cd3c372f6b742d04665b76df1b7ee6d617ca3608f9b191c83401f2b51451401ee5fb367fc974f01ff00d8d1e1ff00fd3ada57f71008f5ee6bf821f0df88b56f09eb963e22d0ae1ed6ff004eb982eeda64c6e8e7b6916689c6e0cb94911586411903208c83f679ff0082907ed804e7fe1656b7ff007ce9dffc81401fd88641a5afc64ff8255fed1df19fe3d6bde3f8fe2af8aafbc450697a6e913d94778b6e3c892e2e2f5252a60821cee5893ef038c57ecdd007f1a5fb78d8dedd7ed6bf14dadade5940f13dd826346600f9507a035d77fc1372caf2d7f6c6f868d7504b086d5ae403221504ff0065ea1d32057f4e7e25fd913f664f196bf7fe2af157c33f0deababea93b5cde5eddd824b3cf33fde7776e4935f347ed41f01be0c7ecf1f00bc6bf1a3e09f82742f0778e3c2ba6b5e68dae699610c777653b3ac6cf1b32b0f9a3764604105588ef401fa4208c0e69d907a57f1e971ff051bfdb0219e4847c4bd7088dd973b74ee7071ff3e15fbabff04b9f8d7f13fe38fc21f16788fe29f886efc457f65e245b3b59ef161578adce9f6937963c88a1523cc958e76e79eb401fa672ff00ab6fa1fe55fc217c4dff0091f75fff00b0aea3ff00a57357f77b2ffab6fa1fe55fc217c4dff91f75ff00fb0aea3ffa5735007e9bff00c11b3fe4e8ef7fec4fd63ff4ab4eafea2323d6bf85bf843f1afe23fc0cf12c9e2df865addd685aa4b692d8b5cda7926436f3b46ee9fbf8a64c334484fcb9ca8c11ce7e955ff828f7ed80cc07fc2cad7393fddd3bff00902803fb10eb5e4df1effe486fc43ffb15759ffd2396bc17fe09f5f123c6df16bf659f0c78ebe21ead3eb9aedf5e6b31cf7b702359244b6d46e21881112469f2c68abc28ce2bdebe3dff00c90df887ff0062b6b3ff00a472d007f0c973feb07fd738ff00f4015fbd3ff043cff90b7c61ff00af5f0cff00e85a8d7e0b5cff00ac1ff5ce3ffd0057ef4ffc10effe42df187febd7c33ffa16a3401fd05d14514005145140051451401fffd6fdfca28a2800a28a2800a28a2800a28a2800a28a2800af10f885fb49fc05f84faf2785fe2578ef43f0d6ad25ba5dad9ea57896f335bcacca92056e4ab323007d41af6fafe5fbfe0b1977756bfb52599b59a484b783b46c98d8ae7175a975c11401fbb3ff000dc1fb227fd15cf09ffe0ca2a3fe1b83f644ff00a2b9e13ffc19455fc5e7f6beadff003fb73ff7f5ff00c68fed7d5bfe7f6e7fefebff008d007f687ff0dc1fb227fd15cf09ff00e0ca2a43fb707ec88463fe16e784ff00f06517f8d7f17bfdafab7fcfedcffdfd7ff1ab565ab6aad7b6e0dedc1065404195fd47bd007eb17ed69fb2b7c71fda4be3e78bfe31fc18f0adef8b3c13e25bd82ef46d734b96ca4b3be812c6d6dd9e332dd44e42cb03ae76e0e320914cfd91ff0061cfda87e1afed1ff0f3c65e2af87dab58e8fa56bf6d757d773b59797040892a97223bb95c805c670a78cd7edcff00c13e803fb1a7c2ac8ff9820ffd1d257d9181e9400b5e47f12be3d7c1bf83b716169f14bc63a4785a6d512592cd354b95b73709095590a6efbdb4b2e71d322bd72bf9f8ff0082d84f3dbf887e18c96f23c4e347d746e462a79bbd3bb8a00fd5ff00f86e0fd913fe8ae784ff00f065151ff0dc1fb227fd15cf09ff00e0ca2afe2f3fb5f56ff9fdb9ff00bfaffe347f6beadff3fb73ff007f5ffc6803fb76f017ed39fb3ffc52f1147e12f875e3fd07c45accb1493a5969d7893ce628865df6af6507935eeb5fcaf7fc1232eeeaebf6b4d35aea69262ba0ebc01918b606cb5e9926bfaa1a00f0ef881fb4b7c03f853aff00fc22df123c79a1786f573047742cf52bc4826304a5824815baab15600fb1ae4ac3f6d2fd94755bfb6d2f4df8abe17babbbc9a3b7b7822d423792596560888aa392ccc4003d6bf05ffe0b0775756bfb5106b59a484b785f4204c6c5491e6ea5e8457e7a7c0bd42fee3e2e783e3b8b99a54ff8487443b5e466191a95af626803fb91afc6eff82cf7fc912f03ff00d8cd75ff00a6abcafd9015f8dfff00059eff009225e07ffb19aebff4d579401fcd16972245a95a4b2b05449e36663d000c0935fd5dfec8dfb5cfecc9e0bfd993e197853c55f133c35a56b1a5786ac2d6f6caeafe38a7b79e38f0f1c88795653c106bf936abd1ea7a9448b145773a228c2aac8c001ec01a00fed9fc27fb59fecd7e3cf11d8f843c19f123c3bad6b5a9c862b3b0b2be49ae2770a5c8445e490aa49f615f43d7f1f1ff0004e7bdbdbafdaffe1a2dccf2cc175c62048ecd8ff8975ff4c935fd830e82801690d2d1401fc97fc55fd803f6b4f147c47f136bba77c37d664b4bdd6b55b8b7911ac0ac90dc5ecf346e37dea300c8e0e0a822be7cf89ffb18fed15f07fc272f8d7e20f82f53d174786786d9eeae8da18c4b70db235fdcdd4cf976c01f2e327922bfb52c0f4afcdaff0082ad803f643d47fec61d07ff004ad6803f929a28a2800a28a2803f683fe0923f1afe12fc1fd5fe20dcfc50f16e93e178b51d334786d1b54b95b713c905cdf3c8a85bef145910b01d030f5afdb4ff0086e0fd913fe8ae784fff00065157f1676f77776a58dacd2425baf96e5738f5c11567fb5f56ff009fdb9ffbfaff00e3401fda1ffc3707ec89ff004573c27ff8328abc13f6a0f8f3f063f687f805e35f82ff0004fc6da178c7c71e2ad35acf46d0f4cbf864bbbd9d5d64648d59947cb1a33b12400aa4f6afe4dffb5f56ff009fdb9ffbfaff00e35f767fc1372f6f2ebf6c6f868b753cb305d5ae481239600ff65ea1d324d0053b8ff82727ed8134f24c3e1a6b8048ecd8dda771939ff9ff00afd73fd80b58d07f62df867e21f027ed47aad87c35d735bd746aba658ebf796f0cf7562b636b6c674114b2a14f3a175fbd918e40c8cfecb003038afe717fe0b53713dafc60f02c96d23c4dff0008b5c0dc8c54e0df0ee2803f645ff6dffd91594a8f8b9e13e41ff98945fe35fc707c41bcb4d43c67ad5ed8cab3c13ea37d2c7221caba49732ba303dc32b023d8d73dfdafab7fcfedcffdfd7ff1ace249393401eaff00087e0a7c47f8e7e2593c25f0cb44bad77548ad25be6b6b4f24482de068d1dff7f2c298569501f9b39618079c7d2abff04e1fdb01581ff856bae707fbda77ff0027d7d0ff00f046cff93a3bdffb13f58ffd2ad3abfa88c0f4a00f8b7fe09f5f0dfc6df097f659f0c7817e21e933e87aed8de6b324f65706369234b9d46e26889313c89f346eadc31c66be89f8cda56a7aefc21f1be89a25ac97da86a1e1dd56d6d2d622a249e79ad6448e34de55773b1006e60327920735e95d28a00fe3c1bfe09c9fb61ca11dfe19eb6adb10300fa7b005540383f6e19191e82bf5ebfe0947fb377c64f801a97c4b97e2b7862fbc3a9ae5be84962d786dcf9ed68d7be685fb3cf3fdd12a6776debc679c7eca607a52e00e9400514514005145140051451401ffd7fdfca28a2800a28a2800a28a2803f39ff6effdb835cfd8f6f7c176ba4786ac3c40be2a87549646bdbb9ad7c83a7b5b0017c9866ddbfed0739c636f7cd7e7e7fc3ed3c63ff44e741ffc1adeff00f21d687fc1713fe42df07bfebd7c4dff00a169d5f81dd781401fbc3ff0fb4f18ff00d139d07ff06b7bff00c875f4c7ec8fff000534f157ed31f1c346f84d7de0ad2b48b4d4adafee24bdb5d42e67963fb1c0650a2396de253b9b03af033df19fe65934cb9750e1edc06008cdcc20f3ea0b823e86bf49ff00e0949035afed95e158e5788b3e9dae60472a487fe3cffd866f43f91f43401fd62d7e7cfed3dff04f1f875fb51fc478be24f8afc4dad6937716956ba48b6b08aca480c56b24f22bff00a4c12b07267607040c01c57e82e696803f1bbfe1cc1f04bfe878f12ffe02e95ffc8747fc3983e097fd0f1e25ff00c05d2bff0090ebf6468cd007e377fc3983e097fd0f1e25ff00c05d2bff0090e957fe08c7f0523612278e7c4c194e4116ba57047fdb9d7ec8521e86803f02fc5bfb7ceadfb086b771fb28785fc2763e23d1be1c98b4ab2d5b55d42682f2ee39208af374b1dbdabc4a57ed3b3e5233b73819aeebe017fc158bc5ff001a7e31f843e184de03d1ac20f12ead0e9d2ddc1a95d4b240b2abb175492da356384200ddd4d7e5cffc1486d9eeff006c9f89ab1490864d56d4959268e36c1d2ec3070eca71c1e7dab92fd842d5ed7f6b6f857e6c9092fe27b4c08e68e43c473764663dc7e7401fd9757c53fb57fec47e08fdad753f0eea5e2ff10eada29f0e5b5e5ac31e9d1dac8b32dec90c8e641730cbca98176edc77afb5734b401f8dbff0e60f823919f1c7897aff00cfae95ff00c875fcee7c5bf08d97813e247893c21a74d25c5b68fac6a7a7452ca1448f1d8de4f6cacc10050ccb1066c00324e001815fdda1e9f88afe203f68ab47bbf8e5e3d7865b7c2f8a7c40ac1a789181fed4bb3c867047041e9d0d007db1ff000485ff0093b2d3bfec05aeff00e816d5fd5457f2b9ff00048d85adbf6b6d36291e2673a0eba711ca926014b6c6763363383d7d2bfaa2cd007f2e5ff058bff93a04ff00b16342ff00d1ba957e787c05ff0092bfe0ff00fb18344ffd395ad7e87ffc162c1ff86a04ff00b16342ff00d1ba957e787c04cffc2dff0007e3fe860d13ff004e56b401fdce8afc6eff0082cf7fc912f03ffd8cd75ffa6abcafd9006bf1bffe0b3dff00244bc0e7fea65baffd355e5007f3294515a09a65cba86dd02e7b3dc448c3eaace083ec45007b07ecedf196ebe027c5bf0e7c52b3d3e1d526f0fde9bc4b5b895e18a56304f06d678d5d946272d90adc8031824d7eb8ff00c3ed3c63ff0044e741ff00c1adefff0021d7e18ff64dd7fcf4b6ff00c0a83ff8e51fd9375ff3d2dbff0002a0ff00e39401fb9dff000fb4f18ffd139d07ff0006b7bffc8747fc3ed3c63ff44e741ffc1adeff00f21d7e13dc5a4d6d8f30c6c0f78e44900fa946600fd6abaab310aa0924e001d49a00fde0ff0087da78c7fe89ce83ff00835bdffe43af9dbf6a3ff829bf88bf694f84d73f0bb52f06e95a3c3717f637df6ab4bfb9b8903594a250bb25b78970c4609dd903a035f97034bb9600892d8679e6e611ff00b3d47369f71026f668587a47347237e48c4e3f0a00a55fb93fb1d7fc133fe16fed0df02349f89fe22f14eb9a65fdf5f6a96af6f670583c216c6ee4b756067b7964cb2c619b2d8c938006057e1b8eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2803e57f197fc11e7e0c7877c1faef882dfc6be24925d334dbbbc446b6d2c2b3410b48012b681b04af3820fa1afe706ea258650abd0a46ff4de81b1f866bfbb7f8aa40f85fe2f2c400341d4c927803fd1a4afe16ae74f9ae1a396292df69861eb710a9c88d410417041078208a00fd15ff827a7ec6de08fdacb50f17d978bf5bd4b471a058e9d750369d15ac8646bc9aea3657fb4c5280144008da01cb1c93c0afd3cff0087307c12ff00a1e3c4bff80ba57ff21d7847fc113a3f2bc45f13e12d1bb2691a106f2e4590026eb5138250919c10715fd03e6803f87cfda5fe17e93f06be37f8c7e1a687773df58f877579f4f82e2e96359a448d6360ce2255404ef23e55038e951fece1f1a2ebf67ff8bfe1cf8ad67a741aacde1fba92e52d2e25786294c96d3db619e34765004e5b214f2a0630491ea3fb7c67fe1adfe29678ff008a9aefff0045415f1fc30bcee238f6e4f76608a3eacc401f89a00fddcff87da78c7fe89ce83ff835bdff00e43af5af04fc39f0d7fc15974b9be30fc4796f3c0d77e0db86f0c5bd96852417b6f730491417e6691afad4b07dd385c050005ef935fceb7f64dd7fcf4b6ffc0a83ff008e57f4c3ff000462017e03f8dd032395f172a928eaeb91a5d88382a4838231c1a00aff00f0e60f825ff43c7897ff000174affe43a3fe1cc1f04bfe878f12ff00e02e95ff00c875fb23466803f3e7f661ff008278fc3afd973e23cbf127c29e26d6b56bb974abad24db5fc5651c022ba920919ffd1a0898b830281924609e2bf41a8a2803f1f3f6b8ff00829a78abf667f8e1acfc26b1f05695abda69b6d61711dedd6a17304b27db20129531c56f2a8dad91d7918ef9c7ccff00f0fb4f18ff00d139d07ff06b7bff00c875f2a7fc156a1fb67ed97e2b8e1961dd1e9da1ee0f3471907ec60e3e761ce0838f423d457e6cbe99728a5cbdb90a0938b9849e3d00724fd05007ee8ffc3ed3c63ff44e741ffc1adeff00f21d7e81fec21fb706b9fb615ef8d2d757f0d58787d7c2b0e972c6d657735d79e7506b904379d0c3b767d9c6319ceeed8afe46fa706bf7c7fe0877ff00216f8c3ff5ebe19ffd0b51a00fe82e8a28a0028a28a0028a28a00fffd0fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf056dc0321cff724ff00d00d7f547ff0521fd8d7e2a7ed5fa87802e3e1b9d2847e1983588ef3fb4afdec8eebf6b431ecdb6d71bb0206ce76e38ebdbf317fe1cedfb51a07313f85c3947552dae4a402ca46481a70ce33d322803fa13f813e13f0bcff00047e1f4d368f60eefe17d19999ad622493671649253926be67ff0082935bdbf837f643f167883c251268ba9dbdf686b15ee9c3ec7731acba9db472049a0d9226f462adb5865491d09afb4be14f87b53f097c30f08f8535b112ea3a3685a6e9f7620732c427b6b78e290239552cbb94e18aae47381d2be33ff82a5ffc995f8cbfebff0040ff00d3ada5007f2dcdf1e7e2f0623fe130f10704ff00cc6f52ff00e4aafe9a7fe093fafebbe24fd976e354f116a579aa5db78a7554f3efae65ba97620842aef99ddb6a8e833fa939fe4e5fefb7d4d7ef57fc13c7f6ebfd9fff0067ef8012780fe235e6ad0eacdafea17fb2cf4b9eee210dc797b3f79182bbbe5391d45007efb7884b2e83a8b292a45a5c1041c107cb6ef5fc415c7c73f8bb6e628d7c63e216fdcc4c59b5bd4724b2024f1740753d862bfa60d5ff00e0aa5fb23dee937b690ea3e20f326b79a35ce857606e742064edf535fca1de4892caa53a2c51a1faaa007f51401fbbdff0476f1e78d3c67f173c6c9e28d7754d56287c330bc715f5fdd5e468e6f769655b89640ac400323b7e35fd0a57f27fff0004cbfda5fe16fecd9f10bc55e20f8a1717d05a6a9a145636e6c6ca4bd73325d79a772c592a369ea78cf15fb37ff0f5bfd90ffe823e21ff00c10ddff85007e845cf863c397b3bdd5e697653cd21cbc925b44eec718c962a49381debe6cfdaf345d2340fd97be2a6b7a1595b69da8d8f84f55b8b5bbb48520b88268e066492391155d1d4805594820f239af0aff87adfec87ff00411f10ff00e086effc2b88f895fb71fc00fda83e1ff88ff677f85d7faa3f8bbe22e9779e1dd15751d2ee6ceccdedf42e91f9d3ba911a0e4b1c12003804f1401fce4dff00c73f8b96f7d71047e2ff00106c8e5745ceb7a91385240ff97aafdfff00f823a78afc4de2df87df116f7c4dab5fead347ace9ab135fde4f78d1ab59862a8d3c923282c49c035f065d7fc11eff006a1bab996e59bc2ea65767206bb2e06e39ff00a06d7d95fb346a5a3ffc130341d63c21fb4edd086f3c77771ea5a30f0ea4faca1b7d3618ada7f3996084c6c1e44c65704370720d007ede573afe11f0b48ed249a3d833b92cccd6b1124939249dbc926bf3f3fe1eb7fb21ff00d047c43ff821bbff000a3fe1eb7fb21ffd047c43ff00821bbff0a00bff00f0525b5b7f087eca1afeb7e138d745d462d57428d2f34d1f62b9449b508239156683cb9143a3156dac32091debf978ff0085f3f17ffe870f107fe0eb52ff00e4aafe87be37fed11f0c3f6fdf871a87ecddfb3e5e5d4fe32d566b2d56dd35cb3b8d2ac8db691770dcdc16b878e4c1d80050118ee61c63247e78ff00c39d3f69ff00eff863ff0007b2ff00f2b6803f2cfc4be2df1178c2f86a7e25d46ef52ba08b1f9d797335d49b133b57cc9de47da3270376064e00c9ce2d9de5cd85cc7796723c3342eaf1c91b146564219486520820804104104641079af7cfda3ff670f1cfeccbe395f0178f5ac5b516b1b5bfff0040ba3771795766658ff78d0c0776607c8d9c71c9cf1f3d5007afff00c2faf8be79ff0084c3c41ff83bd4bff92ab9af137c4bf1d78c6d22b1f13ebba9ea9042e648e3bdd42eaed11c82a5956e269554ed2464007048ce0915c2d1401a1a400755b204641b88b20ffbe2bfb27fd8a3c2fe1bbcfd92be135cdde956334b278534d6791eda26662631c9254927debf8d4d3e64b6bfb6b8973b229a376c7270ac09afe953f661ff008290fecc5f0c7f67bf87df0fbc537fada6afe1fd02cac2f56df47b99e213c09b5c248836bae7a30e0d007ebc7fc21de13ffa0369ff00f8090fff001147fc21de13ff00a02e9dff008090ff00f115f1cfc32ff828a7ecd7f173c79a2fc37f065eeb52eb5afdc35ad9a5ce91716d099163794869640157e48d8fe15f755007f3a9ff0005a5d274bd2fc5ff000ed34db3b7b40fa16a858410a45922f6cc0276819c03fe735f87fa5806e5f233fe8d7279f685ebf743fe0b6bff00238fc39ffb00eabffa5b675f863a4ffc7cc9ff005ed75ffa21e803fba4f09784bc2b278574677d1b4f2c74fb4249b4879fdca7fb15f037fc153fc3ba069dfb24ea37161a6d9dbcbff090684bbe2b78e36c1ba5c8caa83835fa35e0eff914f45ffb07da7fe894af97ff006e8f81fe37fda17e01ddfc36f87df61fed79f56d32f57fb42e1ada0f2ace612c999163948240c0f90f26803f8c81d6bfaedff825c7fc99ef877fec31e21ffd39dc57e407fc39d3f69ffeff00863ff07b2fff002b6bf43be087ed11f0c3f602f871a7fecddfb41de5d41e32d2a6bdd56e1343b3b8d56c85b6af7735cdb95b848e3c9d84860514ee53c6304807ebdcb147346d14aa1d1d4ab2b004329e0820f04115cfff00c21de12ffa02e9dff8090fff00115f0bf877fe0a7bfb2a78a7c41a5f86748bfd79efb58beb5d3ed449a25cc6867bc95208833b00aa0bba8249c0cd7e865007e2cffc15ef51bef879e00f87ba87816e26f0f4f73abea31cf26912be9cf3469665d5247b5689d94300c0138cd7e047fc2f9f8bff00f4387883ff00075a97ff002557f4fdff000519fd963e26fed4be12f06e83f0d8e9825d0f52bdbabcfed3bc6b3531dc5b185446cb04f96dc7272bd07bf1f923ff000e74fda7ff00bfe18ffc1ecbff00cada00fca3d6358d4f5fd467d5b58b99af2f2e9cc934f712bcd2c8e7a9692466763eecc4fbd7db3ff04deb4b5bdfdb0be1bdbde431cf13eab72192540eac3fb32fce086041e403f515f41ffc39d3f69ffeff00863ff07b2fff002b6be94fd917fe09a9fb40fc0afda17c17f137c54de1f3a36857d3dcde7d975692e6e363d95d5ba88e336508277cc09cb8e01a00fdd41e0ef09e07fc4974effc0487ff0088ad6d3f4ad374a8da2d32d20b4476dccb044b1066c632420009c77abc3a0af94be3efed9df047f66bf11e9be16f8a375a9dbdfead64d7f6cb63a74d7a8d02c86224b440ed218743ea3d6803eab93ee37d0ff2afe24fc7bf1abe2be99e33d72ced7c5fe201147a9dfaa0fed9d4142aa5d4aaaa02dca8015400302bfa486ff82ad7ec86ca47f68f8879047fc806efff0089afe553c6faa5a6b7e2bd5b55b02cd6f777f793c4597692935c4922e4763b5864763c5007ebff00fc124be23f8efc63fb4dde59789bc41ab6a76abe12d5a416f7ba8ddddc3e625ce9e15f64f348a1943b00400704d7f4a75fc86ffc137be3f7c3afd9dbe3a5d78e3e25cf7906972787351d3d5acad24bc90cf733d9ba0f2e305b6ed85f2dd0703b8afdcdff0087adfec87ff411f10ffe086eff00c2803f42aefc35e1ebfb86babed32cee267c6e925b78e4738181966524e05790fc76f09f85e0f823f10668747b0474f0beb2cacb6b10208b39704109c115d3fc1af8c3e0af8f1f0fec3e267c3e96e66d0f5296e6181eeeddada62f6933dbca1a27f9971246c39eb8a83e3dff00c90df887ff0062b6b3ff00a472d007f0c770009063fb91ff00e802bf7abfe0877ff216f8c3ff005ebe19ff00d0b51afc16b9ff00583feb9c7ffa00afde9ff821dffc85be30ff00d7af867ff42d46803fa0ba28a2800a28a2800a28a2803fffd1fdfca28a2800a28a2800a28a2800a2bf27bfe0a5dfb60fc5ff00d95f51f87b6ff0bae74fb78fc4906b325f7db6c16f496b16b4116ccc91ede267cf5cf15f971ff0f7afdacffe823a17fe0893ff00926803faa8afcf4ff82a5ffc995f8cbfebff0040ff00d3ada57e317fc3debf6b3ffa08e85ff8224ffe49af26f8ddff000517fda0fe3dfc38d4be1878eaf3499b46d4e5b59664b5d292d65dd673a5c478944ef8c491ae7e5391c719a00f81dfefb7d4d3294924927bd7efdffc13a7f621fd9ebe3cfecf9278e7e24691a85e6aebe20d46c7cdb6d5af2ce3f22dc47b079704a89919396c64f73401f80983460d7f5dbff0eb8fd8f7fe85dd63ff000a1d4fff009228ff00875c7ec7bff42eeb1ff850ea7ffc91401fc88d15fb9dff000531fd8dfe03fecf3f08fc33e27f861a4df58ea1a8f885ac6e1eeb54bbbe5300b0ba9f012e257553e644a77000e0119c135f8716b1acb750c4fcabc8aa7e8481401060d7d8bfb03ffc9dbfc2dffb19ed3ff454f5fb2dfb1fff00c13e3f662f8a9fb357807e2178c743d4ee35ad734b1737b2c1ad5fdb46f2995c65628a648d0600e14015ea9f153f629fd9eff667f86de27fda07e15e8b7d6be31f87ba4def887439ef757bebdb78afeca17689a4b79a668e45ea0ab0e41238eb401fa9b5fcf9ff00c16dbfe43df0cbfec11ae7fe95e9d5f355d7fc15cff6b4b5b996d9b52d04989d9091a1260ed38ff9f9afb9bf652d3b4aff008297f8775bf177ed4f6a357bef03de45a6e8ada34b71a22c76da9431dcce245b59ff007859e34fbcc40da30064d007f37f83495fd777fc3ae7f63d183ff08eeb1d47fccc3a9fff002457f2cdf1bbc37a4f847e2b78b3c35a146d0d8699aeeaf656c8eed232c3697f710440bb659888e350589258f24e4d007dfdff000485ff0093b2d3bfec05aeff00e816d5fd54d7f0e9f007e3f78eff00672f1dc5f10be1ecb6b0eab0dadcda23dddb0bb8c47761049fbb2e992762e0eee39e0e6bee2ff87bd7ed67ff00411d0bff000449ff00c93401b7ff00058bff0093a05ffb16342ffd1ba957e48d7bd7ed09fb447c40fda4fc6a3c79f1166b59b531656d61bad2d45a47e4da195a31e5879390667c9ddce470315e0b40051457eb1ffc12fbf665f847fb46788fc69a77c53d3aeefe1d2349b0bab516b7f71625659ee6e2372cd6ef19605625c06240e48e49a00fc9dc1a4afebb4ffc12e3f63d033ff08eeb1ff850ea7ffc915fce0fedadf0d7c25f097f68ef1b780bc0f6d2dae8da36a82dad229a792e6458cd9da4d869652d239df2b9cb127040ce00a00ef3fe09c1ff002781f0d7fec36dff00a6ebfafec407415fc29fc1ef8b1e28f827f10747f891e0d7823d5b44b93756ad7108b8884862961cb4659370d933f1b873839e39fd04ff0087bd7ed658c7f68e85ff008224ff00e49a00fa3bfe0b69ff00238fc39ffb00eabffa5b675f863a4ffc7cc9ff005ed75ffa21ebe8efda47f6b3f8a9fb515fe8fa8fc4cb8b19e6d16da7b4b636564b64045712472b860249371dd1ae0e4606477af9c749ff008f993febdaebff00443d007f78be0eff00914f45ff00b07da7fe894ae92b9bf077fc8a7a2ffd83ed3ff44a57caff00b78fc6bf1dfc00fd9faefe22fc399ed6df598357d2ecd1ef2dc5d43e55dce239331965c9c1c8f985007d995fcab7fc15ebfe4ecb51ff00b01685ff00a05cd1ff000f7afdacff00e823a17fe0893ff926be1df8fdf1fbc77fb46f8ee5f885f10a5b59b559ad6dad1ded2d85a4663b40e23fdd877c11bdb27773c7031400bfb367fc974f01ff00d8d1e1ff00fd3ada57f7123a7e26bf82af06f8af54f04789f4bf15e8c516f749bdb5bfb732209104d673c7711165246e0248d4919191c646735fa5dff0f7afdacbfe823a17fe0893ff00926803faa8a2bf957ff87bd7ed67ff00411d0bff000449ff00c9347fc3debf6b3ffa08e85ff8224ffe49a00feaa28afe55ff00e1ef5fb59ffd04742ffc1127ff0024d7d35fb1effc147bf690f8e1fb457827e1b78c2ff479344d6efa7b7bd4b7d252da6289657570bb24133edf9e15cf1d3bd007f41d5fcdf7fc16c3fe4aef817fec569fff004b857f4803a0af97fe39fec79f033f68cf10e9de28f8a9a5df5fea1a5d93585ac96ba9ddd8aa40d2194a95b7910312c7249c9e07a0a00fe2970692bfaec7ff00825cfec7aaa4ff00c23bac700ffccc3a9fff002457f27fe39d32cf46f16eafa5d8294b7b5bfbc8225662c4243712468093c92154649e4f5a00e4e9e80ef5fa8afd0fff00826afc08f86ffb40fc78bbf057c4eb2b9bed2a3f0dea5a82c76b7935938b8b79ec9233e640c8f80b33e573839048e057eee7fc3ae3f63dff00a17758ff00c28753ff00e48a007ffc12d3fe4cafc1bff5ff00afff00e9d6eebeb2f8f7ff002437e21ffd8adacffe91cb5f851fb477ed59f137f609f8a1a87ecddfb3f3e9fa6f81b4082d6f2c2db51b36d56e966d595aeee4bdccf3891c34eeec371246ec7402bcd7c03ff052efda43e3478e3c3df08fc637ba3cde1ff1b6afa77873568adf4916b33e9fabdcc76772b1ce970cd1486295b6b804ab608a00fc85b907cc1ff5ce3ffd0057ef4ffc10f3fe42df187febd7c33ffa16a35f7da7fc12dbf63a44548fc37abaa280aaa3c41a98000e0003ed1c002be83f80ff00b2a7c1afd9b67d6ae7e1469b7961278812da3be377a8dd5fef5b33218b6fda647d9832be76e339e7a0a00fa368a28a0028a28a0028a28a00ffd2fdfca28a2800a28a2800a28a2803f0b3fe0b25f0dbc77f10755f850de0bf0feabadad8db78845c369ba75ddf884ccd61b049f66865d9bb636ddd8ce0e3a1afc48ff866cf8e9ff42278a3ff0009ed57ff00912bfb8ac5263ebf9d007f0edff0cd9f1d3fe844f147fe13daafff00225617897e097c56f0868f36bfe26f09ebda669d6e5165babdd1efed2043230440d2cf6f1c6a59880016049200c938afee9b1f5fcebf3d3fe0a9591fb15f8cc0cf37da07fe9dad2803f90fafeacffe0909ff00269d37fd8d7abffed1afe539fefb7d4d7f563ff0484ff934e9bfec6bd5ff00f68d007ea33ba468649085550496270001c924d70a3e2afc302323c5fa0ffe0cedbff8e5747e23ff00917f52ff00af3b8ffd16d5fc19dddd3c2d1c71a42079309e618c9c9452492572493401fd1bff00c161fc67e10f117c0af06db681ae69ba9cb1f8addde3b3bc8677553a5df2e488d988192067a64815fcde5910b7b6ecc7004a8493f514d96ea59976b88c0ff623443f9a819aaf401fd81fec13f10fc01a4fec81f0bf4ed57c4ba3d9ddc3a285960b8bf822950f9b270c8ce181f622ba9fdb1be237c3dd4bf655f8b361a7789f46bab99fc21abc71430ea16f24923b5bb00aaaae4b127a00326bf8dd4bc9a3508162207768a363f892a49a56bd9d94ae2219eeb0c6a47d085c8fc28024d5595f53bb6520833c8411c82371afe85ffe08d7e30f097873e1c7c4487c41ade9da64936b1a6b469797715bb385b2504a89194900f048ef5fcecd4f15c490821021cff7e357fcb7038fc2803fbb83f153e1863fe46fd07ff0676dff00c72bf90bf8e9f02fe2ff0089fe2ff8d35bd13c19e23bbb0bbf11eb93db5cc1a1ea33c13c13ea3732c52c52c56cf1c91c91bab2b2b10c0820d7c936b7524cef1c8909530cc7886307223620821720835fdd57c2b1ff0016c7c21d7fe403a677ff00a768e803f895f127c11f8ade11d264d77c4be13d774cb089911ee6f747bfb4855a46da80cb3dbc7182cc40505b2c4e064f15e555fd777fc151b8fd8f7c458cff00c863c3dffa73b7afe448f5a00f51f0cfc15f8a7e32d2975bf0c78535cd52c5dde35b8b1d22fef212f19c32896dede48f2a78237641e0806b765fd9c3e384313cd2f817c4ea91ab3b31f0fea80055192493680000773c0afe9a3fe0949cfec87a775ff91875effd2b6afd01f1871e13d648cffc83eefbff00d317a00fe0a9d1e3628e30460fe07907dc11d0f7afdc1ff8232f8a3c37e1bf16fc4397c43ab58e9892e87a5a46d79731db876179764853232e48041207622bf13b56ff008f98ff00ebdad7ff004425528a7921cec0a73d9d15c7e0181c5007f7727e2a7c31c1c78bb41ffc19db7ff1cafe5c3f6ebf84bf127e21fed47f103c51e0af0c6b7ade8d7fab24f67a8e9ba4df5f595d446c6cd37c3716d04b148bbe3652558e0a915f9d3a65dc936a56914b1c0c8f3c6ac0c116082c011f76bfb39fd87c67f644f8479ffa14f4dffd142803f91eff00866cf8e9ff0042278a3ff09ed57ff9128ff866cf8e9ff42278a3ff0009ed57ff00912bfb89c7d7f3a31f5fce803f83bf18fc38f1bf8025b787c65a1ea7a2bdd23490aea3617562d22210acc8b731445802402541009009c915cde93ff1f327fd7b5d7fe887afdceff82da7fc8e3f0e47fd40755ffd2db3afc31d27fe3e64ff00af6baffd10f401fde2f83bfe453d17fec1f69ffa252be17ff829ef877c41e29fd952ff0048f0ce977dac5f3ebda2482d74fb596f27291dc867611408ee42a82490a702bee8f077fc8a7a2ffd83ed3ff44a5749401fc3affc3367c74ffa113c51ff0084f6abff00c895e67e2bf06f89fc11aa1d1bc57a5dee937aa8921b7bfb59ece609202558c77091c80360e095c1c1c13835fdeae3ebf9d7f2b1ff00057aff0093b2d47fec05a17fe8175401f95745145001451450015f72ff00c139b54d3747fdaf7e1cdfead770595b43aa5c34935cc8b14680e997eb96672140c90393d4815f0d53e391a270e98c8ecc0303f504106803fbbc1f153e18607fc55fa0ff00e0cedbff008e52ff00c2d4f861ff00437e83ff00833b6ffe395fc267dbe7feec3ff7e22ffe268fb7cffdd87fefc45ffc4d007f76127c53f8625180f17683d0ff00cc4edbd3feba57f0f5f12258a6f1cebb2c2eb2236a9a832b210ca41ba988208e0820e47b5729f6f9ff00bb0ffdf88bff0089aa6492726803f5cffe08d7ff0027477bff00627eb1ff00a55a757f5115fcbbff00c11aff00e4e8ef7fec4fd63ff4ab4eafea22803f934ff82b27fc9e3f8a3fec1da1ff00e9257c87fb31ff00c9c47c30ff00b1d7c37ffa72b7afaf3fe0ac9ff278fe28ff00b07687ff00a495f21fecc7ff002711f0c3fec75f0dff00e9cade803fb87145028a0028a28a0028a28a0028a28a00ffd3fdfca28a2800a28a2800a28a2800a28a2800af9c3f6b0f81579fb48fc0fd6be11586af168536ad71a74e2fa7b76ba8e3fb0ddc374418924899b7f95b78718ce6be8fa2803f9f43ff00044ad7c9c9f89ba473ff00502bbffe58d7eacfec69fb376a3fb2cfc239be18ea5aedbf88647d62f3535bbb7b57b340b7623fddf96f2cc72a50f3bb90477193f59514014354b56bfd3aeac55821b882488311900ba95ce3db35f8087fe0899e22709e6fc4fd25d9111371d0aeb24228504e351033c7602bfa0aa2803f9f4ff8724ebdff00453748ff00c115dfff002c68ff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f4ff008724ebdff453748ffc115dff00f2c68ff8724ebdff00453748ff00c115dfff002c6bfa0ba2803f9f4ff8724ebdff00453748ff00c115dfff002c6be01fdb5bf629bdfd90ef7c336779e26b4f113788ad2fae95ad6c65b2108b296de22ac25b8b8dc5bed00820ae369e0e411fd84d7f3f1ff05b0826b8f10fc318ede3691ce8fae9da80b1ff008fbd3bb0a00fc11b7944321723394913fefb52b9fc335fd0ff0083ff00e0b29f0dfc39e12d13c3f37c3bd626934cd3ad2cde45d4ac943b5bc4b19600b640257383cd7f3d5fd93aaffcf9cfff007edbfc28fec9d57fe7ce7ffbf6dfe1401fd17ea1fb56f87bfe0a5fa55d7ecb1e10d12f3c0f7dab88b585d6b52961d4ada35d12e20ba68cc16d246ec64f957efae01272718af30ff8724ebdff00453748ff00c115dfff002c6be69ff82465adcdafed69a6adcc4f116d075e203a95cfc96deb5fd50d007cc1fb21fecfd7ff00b337c1bb7f859a96b506bf341a95fdf9bdb7b67b48c8bd97cdd822792561b338cef39afa2b5cb07d5347bed311c46d796d3401c8c8532a1404818ce335ab45007f3ecfff00044ef11cbb0cdf13f487748e38f71d0aeb2446a10138d440ce073803e94dff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f883fe089fe21b69a3b88fe26e8e1e26575ce8577d54e47fcc46bd5c7fc1417c1ffb1359d97ecade21f09ea5e26bdf8676969a04dacda5d5b59dbdf3c36d14be6a4333978c159572a4b60f426bf6bcf435fc77ff00c147ff00e4f03e257fd8717ff4dd61401fabdff0faef867ff44df5affc1a58ff00f1547fc3ebbe19ff00d137d6bff06963ff00c557f36d45007e8a7edf7fb65786ff006b7d6fc2daa787bc3b79a02e85a75e5948977730dc191ae678660ca61c801445820f3935f9ed65702da6672321a2963fa7988c99fc3766920b2bcba52f6d0492aa9c12885803f80a9ffb2755ff009f39ff00efdb7f85007f449a27fc167be1b695a35869aff0e75976b4b58202c353b10098e3552402d9e48ef5f547ecb1ff000519f097ed4bf137fe15ae83e0dd4b439469979a9fdb2eaf6dae232b66d02b4616125b71f3d4e4e07d7b7f267fd93aaffcf9cfff007edbfc2bf593fe08f96b736bfb5095b989e22de17d7480ea5723cdd3bd6803fa87afc99fdaff00fe09b1ad7ed47f192efe28daf8dec343b7b8d3ec2c858dce973ddba9b25906ff00323bb807cde61e369c60735facd45007f3ade29ff82316b9e1cf0ceafe217f893a4caba5d8dcde98d744ba56716f1b49b413a83004edc64838f435f865711793204ce7288e3e8ea1b1f866bfbbaf8a9ff24c3c5fff00601d4fff0049a4afe132ff00fd7aff00d7183ff452d007dcdfb14fec537bfb5e5ef89acecfc4d69e1d6f0eda58dd335d58cb7a2617b2dc4415445716fb4afd9c92496cee1c0c127efeff008724ebdff453748ffc115dff00f2c693fe0893ff0021ef89bff608d0ff00f4af51afe832803f9f4ff8724ebdff00453748ff00c115dfff002c68ff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f4ff008724ebdff453748ffc115dff00f2c6bf39bf6d2fd902f3f645f16e85e18bcf11daf885b59d2df5112dad9cb66b1049fc9d85659e72c4e7390c0638c57f6495fcdf7fc16c3fe4aef817fec579ff00f4b85007e2251451401fae9ff046bff93a3bdffb13f58ffd2ad3abfa88afe5dffe08d7ff0027477bff00627eb1ff00a55a757f511401fc9a7fc1593fe4f1fc51ff0060ed0fff00492be12f84be31b6f87df13bc25e39bcb77bb83c3dafe95abcb046ca8f2a585d4770c8acdf282c13009e013cf15f7a7fc1576d2eaebf6c8f148b685e52ba76864ec52d8cda7b57e6b3e97a922977b49955412498d8000773c5007f475ff0fadf867ff44df5affc1a58ff00f155f687ec7bfb6ff863f6bfbcf15da787bc337de1f3e158b4f9666bcba82e04c350338409e4938dbf6739cfa8afe39abf7c7fe0879ff216f8c3ff005ebe19ff00d0b51a00fe82e8a28a0028a28a0028a28a00ffd4fdfca28a2800a28a2800a28a2803f15bfe0adbf1f7e307c12d53e1847f0b7c57a9f86a3d62db5e6be5d3a558bcf6b56b1f28bee47cec123e31fdeafc71ff0086f8fdadbfe8a9789fff0002e3ff00e315fa59ff0005c4ff0090b7c1effaf5f137fe85a757e082a97385f427f00327f4a00fb0ff00e1be3f6b6ffa2a5e27ff00c0b8ff00f8c51ff0df1fb5b7fd152f13ff00e05c7ffc62b6742ff82777ed71e23d134ff10e93f0ef54b8b1d52d61bcb6992e34d0b243708248dc06be56c32b0232a0fa815abff0ed5fdb23fe89aeadff00813a5fff0027d00723ff000df1fb5b7fd152f13ffe05c7ff00c628ff0086f8fdadbfe8a9789fff0002e3ff00e315d77fc3b57f6c8ffa26bab7fe04e97ffc9f47fc3b57f6c8ff00a26bab7fe04e97ff00c9f401c8ff00c37c7ed6dff454bc4fff008171ff00f18a3fe1be3f6b6ffa2a5e27ff00c0b8ff00f8c575dff0ed5fdb23fe89aeadff00813a5fff0027d1ff000ed5fdb23fe89aeadff813a5ff00f27d00723ff0df1fb5b7fd152f13ff00e05c7ffc628ff86f8fdadbfe8a9789ff00f02e3ffe315d77fc3b57f6c8ff00a26bab7fe04e97ff00c9f49ff0ed5fdb23fe89aeadff00813a5fff0027d00725ff000df1fb5b7fd152f13ffe05c7ff00c62be98fd8f3f6c3fda5be20fed2df0e7c29e2bf88be20d4b48d4bc416d6d796773728f0cf0ba4ac51c2c4a482546466bf377e247c37f187c26f186a3e04f1de9d2e95ad6952ac377693346ef13bc693056685e48c931c88df2bb0c30e73903e8afd81ff00e4edfe16ff00d8cf69ff00a2a7a00fecdebcabe22fc0df83ff001767b1b9f8a1e0dd17c552e98924766fab5947766dd662a6411f980ed0e5549c75c0af55a2803e62ff00862cfd92bfe890f837ff0004f6dffc451ff0c59fb257fd121f06ff00e09edbff0088afa76be48d63f6effd91b40d5efb41d67e2668f69a869975359dddbcbe70786e2ddda39118797d55d483f4a00f9a7f6d6f857f0dbf668fd9ef5bf8a9fb3f786349f87be31b5bed22ca0d73c3d650d95fc56f7d7d0c3711ac8a9f7648d8ab02083dc702bf057fe1be3f6b6ffa2a5e27ff00c0b8ff00f8c57ecb7fc141bf6c0fd9abe2a7ecc5ae783be1ef8fb4bd735ab8d4f459e2b2b632195e3b6bf8659580280612352c79e82bf9a43401f62ffc37c7ed6dff00454bc4ff00f8171fff0018a3fe1be3f6b6ff00a2a5e27ffc0b8fff008c564fc2efd8a7f68ff8c9e128bc6ff0f3c157fad68d34f3db25ddbcd628865b76d922e27bb864cab020fc98f42457a27fc3b57f6c8ffa26bab7fe04e97ffc9f401c8ffc37c7ed6dff00454bc4ff00f8171fff0018a3fe1be3f6b6ff00a2a5e27ffc0b8fff008c575dff000ed5fdb23fe89aeadff813a5ff00f27d790fc62fd923e3d7c07d0ecbc45f147c277ba0586a172d696f35ccb6722c93244d31402dee67604468cdf30030a79cf0403b0ff86f8fdadbfe8a9789bff02e2ffe315f34f8e7c77e2bf891e26bef1878d353b9d5f57d4a5f3aeaf2edc3cd3481123dce40504ec455e00e14572914524f2a4110dcf230451ea58e00fcebec9f067ec09fb5478ffc29a4f8dbc27e02d4b51d1b5bb48ef6c6ee19f4f58e78261947512dec7200c39c322b7a81401f19515f66f8cff604fdaa3c01e14d5bc6de2cf016a5a768da25a497b7d7734fa7b4704108cbbb08af6490851ce1519bd01af8da58a482578251b5e3628c3d0a9c11f9d007f415ff0004a1f803f04be2bfc21f17ea9f12bc0da0f89ef2cbc416f6f6f71aad845752450b69b6b29446914955323b36071b989afd51ff00862cfd92bfe890f837ff0004f6dffc457c17ff000461ff009225e38ffb19ad7ff4d5675fb23401f317fc3167ec95ff004487c1bff827b6ff00e22bb4f017ece5f01fe176ba7c4ff0e7c03e1ff0d6ac6092d4dee97a7c56b398252a5e32f1a82558aa923a702bcb352fdbd3f642d1f52bbd2354f89da3db5ed8cf2dadcc1279caf14d03949118797d55948aed7e197ed5ff00b3c7c64f137fc21df0c3c6fa778875916d2de1b4b4f30b8b784aabb92c814052ea393ce78a00fa1e8a2be74f893fb5afece7f07fc512782be25f8e74df0feb714115cbd9ddf9a24114e098db2a8548600f43f5a00f4ff8a9ff0024c3c5ff00f601d4ff00f49a4afe132fff00d7affd7183ff00452d7f5edf10ff006f5fd90356f00f8974ad3be2868b35d5e68f7f6f044a65cbcb2c0ea8a3f77d4b10057f20d78eb24ca50e408a253f558d411f811401fbcdff000449ff0090f7c4dffb04687ffa57a8d7f4195fcf9ffc1127fe43df137fec11a1ff00e95ea35fd065007f2c1fb61fed87fb4b7c3efda5be237853c29f117c41a6e91a6f882e6dacecedae5121821448982206898800b1c0cd74bfb09fed71fb477c4cfda9be1ff84bc69f10b5ed5b46bfd4ae22bbb1bbb94782741a7de4aa1c2c484e1e35239c7147ed71fb09fed4ff00133f68ef885e34f097c3fd4aff0046d5b5eb9bbb1bb8ae34f093c0e912870b2de46e3250f054715d27ec41fb107ed3bf09bf69df01f8e3c71e03d474bd0b4bd46e27bdbd9ee2c1a38236b0bb8549586ee590e6495470a7ae4d007f4aa3a0afe6ff00fe0b61ff002577c0bff62bcfff00a5c2bfa401d057f37fff0005b0ff0092bbe05ffb15e7ff00d2e1401f88945145007a47c30f8b7f10fe0e7881fc51f0db5ebef0fea925acb66d7561208e5304cc8cf1e595c6d668d09e3f84735efcbfb7bfed6ccc17fe169789f938ff008fb8bff8c57897c1ef823f12be3bf89e4f087c2fd12e35dd562b39afdadadde08dc5bc0d1a3be6e268530ad2a023767e618079c7d36bff0004d6fdb21581ff00856bab7073ff001f3a5fff0027d007eddfec4bf097e17fed2bfb397877e2f7c7cf09e8de3ef1aeab75ab417baf6bd6105e5fdc47657f3db5bac92b2024450c688a30000a38af65f8cffb1ffecb5a2fc1ff001ceb1a4fc29f08da5ed8f87356b8b6b88749b749219a2b59591d182e559580208e845783fec87f1c3e137ec8ff0001741f817fb46789ac7c0de3bd16e353b9bed13517df71043a85f4f756cccd6fe6c444904a8e36c8d8ce0e0822bd9be227eda5fb2efc47f007897e1e781be21e93ac788fc51a45f68da3e9d6ed209af350d4217b7b6823de8abbe595d517730193c9039a00fe412e0059001c6510fe25413fad7ef57fc10eff00e42df187febd7c33ff00a16a35f01bff00c1367f6ca936b3fc33d555b622b0175a59195500e0fdbc6471e95faedff04a6fd9a3e34fecf9a97c4b9be2d785eefc3b1eb96fa125835d4b6b279ed68d7a660bf66b89f1b04a9f7b6e73c6707001fb23451450014514500145145007ffd5fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6be0201ff0a37e1e7fd8aba37fe91c55eb5815e4df013fe486fc3cff00b15b46ff00d238abceff006c1f8e5aefece5f0135df8b7e1cd3ad355bfd26e34d863b5be67581c5f5e436cc58c7f30dab29231dc74a00fa77028c0afe6fcff00c16bfe2e8241f02f85b8ff00a6f7dfe147fc3ec3e2effd08be16ff00bff7dfe1401fd206051815fcdfff00c3ec3e2eff00d08be16ffbff007dfe149ff0fb0f8bbff422f85bfeff00df7f85007f483814d2060d7e59fec15fb7a78f3f6b8f1e788bc2de29f0ee8fa35ae8da3c7a8c52e9d25c3c9248f7021dade710028193c0f4e6bf534f43401fc7bffc14abfe4f23e257fd85adbff4d7a7d723fb03ff00c9dbfc2dff00b19ed3ff00454f5d7ffc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f401fd9bd145140087a7e22bf87dfda3eeae62f8e5e3c58a69117fe128f109c2b103fe42b77e86bfb823d3f115fc3b7ed27ff0025d3c79ff6347883ff004eb77401e2925d5d4abb259a4753d99891fa9a828a2803fad6ff00825201ff000c87a77fd8c3af7fe95b57e936057f27ff00b347fc14d3e217ecd9f0b6dfe17f87fc2ba16a76905f5edf0b8be96e9262f7b219597116570a4e077c75afab7c07ff000589f8b9e33f1a685e177f04f86618b55d52c2c659126bd2e91de5cc56ecca1b00b289323247f4a00fe84f02bf1bbfe0b3c07fc292f03ffd8cd75ffa6abcafd91afc6eff0082cf7fc912f03ffd8cd75ffa6abca00fe69747ff0090b597fd7cc5ff00a18afed0bf61f03fe1913e11ff00d8a7a6ff00e8a15fc59daced6b7315d2005a1916400f425483cd7eb6fc1fff0082b4fc4cf841f0bfc2ff000c349f077876f2cfc31a5dbe990dc5ccd7826952dd768770836863dc0e2803f77ff6e003fe1913e2e7fd8a7a97fe8a35fc5eeb1ff216bdff00af997ff4335fbc9f0eff00e0a0de3afdb63c4f6dfb2ef8cbc35a3687e1ff00890b73a06a3a8e9335c35fdb413dacf297805c0316ff00dce01756033f74d7bfc9ff000464f80f348d34be36f16b3c84b3129a56493c93ff001e14015bfe08c3ff00244bc71ff6335aff00e9aacebf640d7cbdfb2cfecabe10fd947c2dacf84fc1dac6a9acdb6b5a8a6a32cbaa0b71224890476e113ecd1429b3644bd5739cf35f511a00fe1a7e3bdd5cc5f17bc60914d222ff00c241adf0ac40ff009095d7a1afd0dff823ccd34dfb50b19a46908f0c6ba01624ff00cb5d3bd6bf3afe3d7fc95ff187fd8c1adffe9caeabf43ffe08e9ff002740ff00f62c6bbffa374da00fea3abf95bff82bb4d343fb59ea46191a32741d0812a48cfc973e95fd5257f2afff00057aff0093b2d47fec05a17fe8173401f96a6f6f581569e520f04173cfeb5568a2803f7c3fe0893ff21ef89bff00608d0fff004af51afe832bf8dbfd903f6d2f16fec8b79e22bcf0c685a5eb2de21b5b3b5946a2f3a0896ce59e5529e4e7258cec0e78c018afb8ff00e1f61f177fe845f0b7fdff00beff000a00fe903028c0f4afe703fe1f61f177fe845f0b7fdffbeff0a3fe1f61f177fe845f0b7fdffbeff0a00fe902bf9beff82d87fc95df02ff00d8af3ffe970a3fe1f61f177fe845f0b7fdff00beff000af817f6bcfdaf3c51fb5bf8a345f13f89f45d37469b46d35f4e8e3d39e6747479bcedcde772181e38e31401f1f51566d6159a4656e8b1c8ff00528a5b1f8e2bfa5ad2ff00e08d5f01afb4cb4bd7f1a78b15a782294809a5e01740c719b1cf7a00f83ffe08d9ff0027477bff00627eb1ff00a55a757f511815f007ecc9ff0004f2f863fb2dfc4597e24783fc49af6ab7b2e9775a51b7d4859080457524123b816d6d0b6f0605032c4633c57e805007f271ff00055f9a683f6c8f14986468f3a76879da4ae7fd13dabe46fd99eeae65fda1be18a4b348ea7c6be1bc866247fc84edfd4d7d6bff000564ff0093c7f147fd83b43ffd24af90ff00663ff9388f861ff63af86fff004e56f401fdc3003d2970074a051400514514005145140051451401ffd6fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe445fefb7d4d329eff7dbea69940051451401fb77ff00044fff0092bbe3affb15e0ff00d2e35fd201e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd145140087a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6eebfb893d3f115fc3b7ed27ff25d3c79ff006347883ff4eb77401e25144d336d52a3dd9828fcce055afece9bfe7a41ff007fe3ff00e2abf4f7fe091d696b79fb5769f0ddc31cf19d0b5d25644575384b6c70c08afea5bfe11cd03fe81b67ff0080f1ff00f13401fc167f674dff003d20ff00bff1ff00f155eaff00032dcdb7c5df0734b243f3788744550b2a3127fb46d8f00127a027e95fdbeffc239a07fd036cff00f01e3ffe2681e1ed055832e9d680820822de3c823a1fbb401b22bf1bbfe0b3dff244bc0fff006335d7fe9aaf2bf646bf1bbfe0b3dff244bc0fff006335d7fe9aaf2803f994a28a2803eebff82707fc9e07c35ffb0e37fe9bafebfb101d057f1dff00f04e0ff93c0f86bff61c6ffd375fd7f6203a0a005a0d141a00fe18be3d7fc95ff187fd8c1adffe9caeabf43ffe08e9ff002740ff00f62c6bbffa374dafcf0f8f5ff257fc61ff006306b7ff00a72baafd0fff00823a7fc9d03ffd8b1aeffe8dd36803fa8eafe55ffe0af5ff002765a8ff00d80b42ff00d02e6bfaa8afe55ffe0af5ff002765a8ff00d80b42ff00d02e6803f2b554bb051d4d5dfecf9bfe7a41ff007fe3ff00e2abd87f66f4493e39781639155d5bc4fe1f04300410755b40410783915fdb97fc23ba01e4e9b67d4ffcbbc7ff00c4d007f04b35b3c001668db3fdc757c7d76938aaf5fd1dff00c168b4cd3ac3e1a7c396b2b582dcb6b3a9ee3144a84e2c5b192a066bf9d4d2403aa5983c8f3e3ffd08500469632ba860f0ae7b34a8a47d41208a24b296342e5e26c7659518fe001cd7f67dfb19687a2dc7ec9ff08e69ec2d6491fc1da396668236624db272495c935cafedfda268f6bfb1cfc539edac2d62917443b5d204561fbe8fa10b91401fc6fd156af862f6e07fd357ff00d08d55a00bda7ffad93feb84dffa2dabfbd5f0e7fc8034dffaf3b7ff00d16b5fc1569ffeb64ffae137fe8b6afef57c39ff00200d37febcedff00f45ad006c160a324e29be627f787e62bf317fe0ad9a8ea1a57ecaf05e699753d9cebe2ad2544b6d2bc326d613023746cad823debf97bff00859be3dffa0feabff831bbff00e3d401f7affc158c83fb63f8a31cff00c4bb43ff00d24af913f663ff009388f861ff0063af86ff00f4e56f5e37aaeafa96b778da86ab7335ddc3801a59e5799c851800bc8ccc703a64f15ec9fb31ff00c9c47c30ff00b1d7c37ffa72b7a00fee1c5140a2800a28a2800a28a2800a28a2803fffd7fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054aff932cf197fd7fe81ff00a75b4a00fe445fefb7d4d32a5747dedf29ea7b537cb7fee9fca8019453fcb7fee9fca8f2dffba7f2a00fdb8ff8227ffc95df1d7fd8af07fe971afe900f435fce07fc114011f177c7408c7fc52d07fe971afe8fcf43401fc7c7fc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f5d7ffc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f401fd9bd7c9bfb48fed97f08bf659d4342d37e26c5ac3c9e21b7bab8b46d32cc5da84b378924f33f78854e665c7041f50719facabf9f3ff82db7fc87be197fd8235cff00d2bd3a803ec23ff057afd93b04987c5785058ffc4a3b2f27fe5b0ed5f9dde3aff82597ed1bf173c5dac7c4dd02ebc2b1e95e2ad46fb5cd3d6eb55b98ae05a6ab732dec2268d6c245491639c0755760181c3115f8d761febdbfeb8cff00fa29abfbb2f857ff0024c3c21ff601d33ff49a3a00fc85fd843fe09e7f1cff00669f8f167f11bc7973e1d9747834bd4acdd74dd427bab832de2c4130925a40bb47967277679e95fb6f451400552d4afe1d2f4fb9d4ae0318ad6192770a32db6252c7038e7038e6aed737e31ff914f5affb07ddff00e897a00fcd85ff0082bdfeca0c88ed6fe2c42e8afb5b4819018023a4c41e0f50483d8e2bf3c7fe0a33fb72fc13fda6be1a7867c2ff000dd35a5bdd2b5a9efae3fb46c45ac7e4c963716e36b798d96df2ae47a64d7e37eadff1f31ffd7b5aff00e884acd0a5bee827e94009453fcb7fee9fca98411c1a00fa83f63bf8afe15f82bfb41f837e23f8cc5d1d2343d48dd5d7d8e2f3e6f2cda5d43f226e5dc77ccbc67a64f6afe8487fc15ebf64ec7fa9f15ffe0a3ffb757f29a013c0a7f96ffdd3f95007f6dffb38fed3df0e3f6a2f0eeade28f86b16a715968d7eba75c7f6a5b0b591a668639c1450ee4aec917938e7a0c609fa28d7e377fc11878f827e38ff00b19ad7ff004d5675fb206803f863f8f5ff00257fc61ff6306b7ffa72baafa67fe09e7fb447c3ff00d9b7e36b78efe22aea0da61d1353b1ff008975b8b99bcebb7b468fe4dcbf2e207c9cf1c7ad7cd1f1e558fc5ff181009ff8a835bedff512baaf202ac39208fc2803fab0ff0087bd7ec9dff3c7c57ff828ff00edb5f1a7c7dfd97be217fc1463c663f695f81136956de0fd5acedf4ab78fc4575369da80b8d1e49ede72d0456d74a10bb1db970d81c819afc191d6bfaedff825c7fc99ef877fec31e21ffd39dc5007e597817fe0965fb46fc23f1768ff001375fbaf0ac9a5785751b1d73505b5d56e65b8369a55cc57b308636b08d5e468e021159d416232c057e888ff0082bd7ec9d80443e2bc300c3fe251d9b91ff2d8f6afd0ef8a9ff24c3c5fff00601d4fff0049a4afe132ff00fd7aff00d7183ff452d007ebff00fc148ff6d9f833fb4ff833c1da17c344d656e745d46f6eae7fb4ac85aa7973db185761f31b71dc791d857e42691ff215b3ff00aef1ff00e842b3c2b37dd04fd2b4b49471aad9fca7fd7c7dbfda1401fda67ec59ff2695f087fec4dd1ff00f4992b94ff0082837fc99a7c55ff00b021ff00d1d1d757fb167fc9a57c21ff00b13747ff00d264ae53fe0a0dff002669f157fec087ff004747401fc6b5ff00fc7f5c7fd757ff00d08d54abb7c8ff006db8f94ffad7edfed1aabe5bff0074fe54016f4fff005b27fd709bff0045b57f7abe1cff0090069bff005e76ff00fa2d6bf82cb04712c84a91fb89bb7fd336afef4fc39ff200d37febcedfff0045ad007e67ff00c15eff00e4d3a1ff00b1af48ff00dad5fca657f567ff00057aff00934e87fec6bd23ff006b57f29fe5bff74fe5400caf4ff82be2bd2bc0bf173c13e33d6fcd3a7e85e24d1f54bbf2137cbf67b2bc8a79762e46e6d88768cf278af33f2dff00ba7f2a3cb7fee9fca803fab0ff0087bd7ec9dda2f15ffe0a3ffb757d3bfb367ed8df08ff006a9b9f105a7c304d5964f0d476725eff0069d9fd97e5be3308b67cedbb981f3d31c57f165e5bff0074fe55fbdfff00043d046adf184118ff0045f0cffe85a8d007f415451450014514500145145007ffd0fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc15b72164249c7c920fcd4d007f739f013fe486fc3cff00b15b46ff00d238aaff00c5bf849e05f8e1e05bef86ff00122c64d4740d464b796e2de2b89ad5d9ed6649e222581d245db2229f95867183c578f7c0ef8e1f05ecbe0bf80acaf7c7be1982e2dfc33a4452c526af689247225a44acaca650432904107906bd4ffe17d7c0effa287e16ff00c1cd9fff001da00f933fe1d6bfb167fd09b7ff00f8506abffc9547fc3ad7f62cff00a136ff00ff000a0d57ff0092abeb3ff85f5f03bfe8a1f85bff0007367ffc768ff85f5f03bfe8a1f85bff0007367ffc76803e4cff00875afec59ff426dfff00e141aaff00f2551ff0eb5fd8b3fe84dbff00fc28355ffe4aafacff00e17d7c0eff00a287e16ffc1cd9ff00f1da3fe17d7c0eff00a287e16ffc1cd9ff00f1da00f3af81bfb1f7c04fd9cb5dd47c47f09342b8d26ff54b45b1ba926d4af2f83c0afe6050b73348ab86e72a01afa70f435e51ff000bebe077fd143f0b7fe0e6cfff008ed21f8f5f03b07fe2e1f85bff0007367ffc76803f950ff82957fc9e47c4affb0b5b7fe9af4fae43f607ff0093b7f85bff00633da7fe8a9eb67fe0a23aee89e23fdae3e226ade1fd42d753b1b8d52dde1b9b3992e2191469b6284a49196561b948c83d411dab1bf607ff0093b7f85bff00633da7fe8a9e803fb37afe7cff00e0b6dff21ef865ff00608d73ff004af4eafe832bf0abfe0b0df0d3e227c40d7be1c1f02f8575cf11a5b697ad453be8fa65d6a0b0bc97362c82436f1c9b0b2a315dd8ced38a00fe772391e26dc8704ab29fa302a7f435fa35a2ff00c153ff006bcd0b47b1d134ff001369e96ba7db4369029d16d18ac702045058f24800727ad7cac3f663fda209c7fc2b0f1ae4ff00d4b7a9ff00f23d78bea3a6dfe917b3e9baa5b4d67756d2490cd05c46d14b1c913147474601959594ab020104107906803fa0aff827c7edd5fb467ed0bfb42d8f813e24ebd697da14ba4eab74f6f06996f68e66b5584c67cc8c16c0f30f008f7cf6fde5afe4e7fe094be29f0cf84bf6a4d3f55f156af63a3592e89ad235c6a1731db421a44b7daa5e4655cb60e0679c1afe9bbfe17d7c0eff00a287e16ffc1cd9ff00f1da00f58ae6fc63ff00229eb5ff0060fbbffd12f5c5ff00c2faf81dff00450fc2dff839b3ff00e3b5cf78b3e3afc129bc2fabc30fc40f0bbbbd85d2aaaeb3664926170001e6f24d007f11fab7fc7cc7ff005ed6bffa212bf547fe097bfb327c1afda3bc47e34d3be2de8d3ead0e93a4d85d5a086feeac4a4b3dd5cc6e49b69622c0ac6a30d903191c935f957a9babdc2142180b7b65c839e5614047e07835fba3ff00044aff0091c7e237fd8074affd2dbca00fd0f3ff0004b5fd8af07fe28cbfff00c28355ff00e4aafe6fbf6d6f869e0df847fb4778dbc03e02b27b0d1347d505b59dbc93cb72c911b3b49b064999e46f9e573966279c670063fb4c3d0d7f289fb7e7c0cf8cfe2ffdabbe22eb3e19f00f8ab55d3ee7584960bcb0d0efeeada64363669ba39a281a371b9194e18e0822803e6efd8a7e1a7837e2e7ed1de09f00f8f6c9eff44d635436d796f1cf2db33c42ceee6c092164917e7890e5581e319c139fe9047fc12d7f62b23fe44cbfff00c28355ff00e4aafc3efd86be11fc51f863fb4df813c69f113c1fe21f0c787b49d524bbd4357d6b48bcd3b4eb2b74b1bc432dc5d5cc51c3126e915773b819615fd3a0f8f3f0380c1f885e16ff00c1cd9fff001da00c9f81bfb3bfc28fd9cf42d47c39f09b4b9b4ab0d56f05f5d473dedcdf33ceb1242183dcc92380234518040e3a57b7d793ff00c2faf81dff00450fc2dff839b3ff00e3b4c93e3f7c0b891a597e2278511114b3336b56602aa8c92499780075a00f98b5bff8266fec79e21d62fb5ed5bc237d35eea5753de5c38d77548c34d73234b23044ba0aa19dc9c280067815f0a7fc1403f614fd9a3e077ece97be3df86fe1abad3b5a8b58d26d1279756bfbb510dd5c08e51e5cf3c919254e01db91d4106bf77a19a2b8892781d648e450e8ea432b2b0c8208e0823a1afcfcff00829cf85fc4de2ffd95750d1bc25a3ea1ae6a0daee8b28b3d32d26bdb931c57219d845023c842a8c920702803f90c1d6bfaedff00825c7fc99ef877fec31e21ff00d39dc57f30dff0cc9fb43e7fe498f8d7ff0009bd4bff0091ebfa93ff00826bf86fc47e13fd93f40d17c55a4dfe89a8c7aaebb23d9ea56b2d95ca24da84ef1b3433aa48a1d1830ca8c839a00fae3e2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b5fdd9fc54ff9261e2fff00b00ea7ff00a4d257f0997ffebd7feb8c1ffa296803f58bfe0977fb2ffc17fda3b55f1cdafc5cd1a7d5a3d1b4ed2ae2cc437f75626392e6e2f2390936d2c45b2b0a0c3640c71824d7ec1aff00c12dbf62d460cbe0ebf041c823c41ab0208ffb7bafcd2ff8237f8f3c0fe0bd6fe23bf8c3c43a5686b71a568a909d4af61b412325d5f9609e6b2ee2a1d490338047ad7eef8f8f5f038f4f885e16ff00c1cd9fff001da00eb7c0be0af0efc37f0768de02f08dbb5a68be1fb2874ed3edda4799a2b6b75091a99242cef8500658927b9aa5f123e1d7853e2cf81f57f875e39b57bed075c83ecd7d6e9349034916e0d81244caebca8e54835d3e91abe95afe996dad687796fa8e9f7b124f6d776b2acd04f138cabc72212aeac390c090474a8f5bd7346f0de9771adf886fedb4cd3ad177dc5dde4c96f6f0ae40cbc9210aa3240c92393401f049ff00825b7ec584927c1b7e49e49ff848356ffe4ba3fe1d6bfb167fd09b7fff008506abff00c955f59ffc2faf81dff450bc2dff00839b3ffe3b47fc2faf81dff450fc2dff00839b3ffe3b401f24bffc12dbf62b08d9f065f9054823fe120d57904723fe3eba115f8bde26ff0082a2fed79e16d7f51f0f69fe27d3d6d34dbcb9b3b756d16d1d961b699e18c16206e3b5064e3935fd2a49f1e7e0714603e21785ba1ff98cd9ff00f1dafe253e234f05d78df5cb8b691258a4d4efd91d1832b2b5d4a4104704104107b83401fb41fb25fc71f1e7fc1437e245c7c0cfda8aea0f1278320d1eef5f8ecacedc69128d474f9ed63824fb459ba4a42adcc9f2ee009c120e057e8dff00c3ad7f62bffa136fff00f07faaff00f2557e417fc11aff00e4e8ef7fec4fd63ff4ab4eafea22803f3d3fe1d6bfb167fd09b7ff00f8506abffc9547fc3ad7f62cff00a136ff00ff000a0d57ff0092abf42eaadeded9e9b673ea1a84f1db5adb46f34f3cce238e28e31b99dd9880aaa012492001401f9fdff0eb5fd8b3fe84dbff00fc28355ffe4aaf7ff811fb297c11fd9ae7d6ae7e1068b3e912f8852d63d41a7d42eefbcd5b3329840fb54d2ecda667fbb8ce79e82bb31f1efe069191f10fc2b83c8235ab3ffe3b5d57863e20f813c6cd709e0df11e93af3598437034cbd86ecc224cec32089db6eeda719c67071d2803afa28a2800a28a2800a28a2803ffd1fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf03abfaeefdba3f617bcfdb26f3c1b7769e328bc29ff0008a45a9c4cb2e98751fb4ff689b63918b9b7d9b3ecff00ed67776c73f01ffc38ef56ff00a2c36bff0084d37ff2c6803f0505c48a000138f58d0feb8a5fb4c9e91ffdfb4ff0afdeaff871deadff004586d7ff0009a6ff00e58d1ff0e3bd5bfe8b0daffe134dff00cb1a00fc15fb4c9e91ff00dfb4ff000a3ed327a47ff7ed3fc2bf7abfe1c77ab7fd161b5ffc269bff0096347fc38ef56ffa2c36bff84d37ff002c6803f057ed327a47ff007ed3fc28fb4c9e91ff00dfb4ff000afdeaff00871deadff4586d7ff09a6ffe58d1ff000e3bd5bfe8b0daff00e134dffcb1a00fc15fb4c9e91ffdfb4ff0a3ed327a47ff007ed3fc2bf7abfe1c77ab7fd161b5ff00c269bff96347fc38ef56ff00a2c36bff0084d37ff2c6803f03d98bb6e38fc0003f21c57d87fb03ff00c9dbfc2dff00b19ed3ff00454f5fa5bff0e3bd5bfe8b0daffe134dff00cb1af62f805ff0492d53e097c5ff0009fc5293e27db6af1f86b548b516b15d05ad5ae3ca575d825fb748133bfaec6e9401fb514628a2801a47d7a8afe1dff694ff0092e9e3cffb1a3c41ff00a75bbafee28f35f859f123fe08dbaafc41f1df883c66bf15ed6c975bd5751d496d8f879a6308bfbb9ae7cb2ff6f4ddb3cddbbb68ce3381d2803f9d84728772e3f1008fc8f1537da64f48ff00efda7f857ef57fc38ef56ffa2c36bff84d37ff002c68ff00871deadff4586d7ff09a6ffe58d007e0afda64f48ffefda7f8521b8908c613fefda7f857ef5ffc38ef56ff00a2c36bff0084d37ff2c68ff871deadff004586d7ff0009a6ff00e58d007e06f5e4d7ef0ffc112bfe471f88dff601d2bff4b6f2b47fe1c77ab7fd161b5ffc269bff009635f79fec3ffb085f7ec79ae789757bbf1a43e2a5f10585a592c7169874f307d9669a6dc49b99f7eef3718c0c63be7800fd17a4c52d1401f2b7edc031fb227c5cff00b14f52ff00d146bf8c2d525316a77714691aa24f22aa88930006200e95fdc77c75f86b27c64f83de2ff8570ea0ba4bf8ab49b9d2d6f5a13702dcdc2edf30c41d0bedebb772e7d457e2a5d7fc110b57bab99ae4fc60b453348d211ff08d39c6e39ffa08d007e077da64f48ffefda7f855ed3a4f3a7912548d97ecf727fd5a75585c83d3a82322bf773fe1c77ab7fd161b5ffc269bff0096353dbffc110b57b77671f182d4ee8e58ff00e45a71feb11933ff00211ed9cd007eeff83bfe453d17fec1d69ffa252ba4acbd16c0e95a4596965fcc3676d0c05c0c6ef2902671ce338cd6a500263ebf9d2d14500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afef3bc5ba2b7897c2dac78712616e755d3eeac84c57788cdc44d1eedb919dbbb38c8cd7e0d49ff000440d62560cdf186d321113fe45a7e88a147fcc47d05007e06a48d1fddda73fde50dfcc1ad1d32532ea36b14891b23cd1ab03126082c323a57eeeffc38ef56ff00a2c36bff0084d37ff2c6a7b6ff0082216af6d7315cafc60b426275700f869f9da73ff411a00fd66fd8b3fe4d2be10ffd89ba3ffe9325727ff0506e7f633f8ab9ff00a021ff00d1d1d7bbfc11f87327c21f843e0ff85d2df8d51fc2ba2d9692d7ab11805c1b48c47e608cb3ecdd8ceddcd8f53591fb447c2697e3afc16f167c2487535d19fc4d63f6317ed01b9107ceafb8c41e32ff007718debf5a00fe1f2f2764bb9d11630ab23800469c004fb557fb4c9e91ff00dfb4ff000afdee97fe087fac4d2bcadf186d32ec58e3c34fd49cff00d046a3ff00871deadff4586d7ff09a6ffe58d007e0afda64f48ffefda7f8557afdf2ff00871deadff4586d7ff09a6ffe58d1ff000e3bd5bfe8b0daff00e134dffcb1a00f01ff008235ff00c9d1deff00d89fac7fe9569d5fd4457e587ec63ff04dfd43f64cf8af3fc4ab9f1fc1e278e6d16f349165168ed60c0ddcb6d2f986537738217ecf8dbb0677673c60fea7d0015e4df1ec03f033e2183c83e15d6baffd79cb5eb35c87c41f0c3f8dbc09e23f0647702cdb5ed26f74d17053cc109bb85e2de532bbb6eece32338c645007f07f34cd1b2aaac607971ffcb343d547a8afde6ff821f36fd5fe3092147fa2f867eea851f7f51ec00a56ff00821eeaee416f8c369c2aaffc8b4ffc200ffa08fb57df5fb0bfec2f79fb1b5e78caeeefc65178affe12b8b4c89562d30e9df66fece37272737371bf7fda3fd9c6def9e003f43a8a28a0028a28a0028a28a00fffd2fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd3fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd4fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd9", expectedOutput: "http://en.m.wikipedia.org", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "Parse QR Code", "args": [false] } ] }, { name: "Parse QR Code : PNG", 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", expectedOutput: "Hello world!", recipeConfig: [ { "op": "From Hex", "args": ["Space"] }, { "op": "Parse QR Code", "args": [false] }, ], }, { name: "Parse QR Code : Transparent PNG", input: "89504e470d0a1a0a0000000d49484452000000e8000000e80806000000e5a9a878000000017352474200aece1ce900000a49494441547801edddcb6e6db90d04d09ba0ffff97936b0499b20e4ccbb51fab8120035a22b5740a7b2024fdaf3f7ffefce7ef7f9efccfbf96874b3e69ffedfae5f8ebfb4de74bf3a5f3a7f5a9ff76ffd4bf5aff77b5bbe604088c02023af22812e80a0868d75f7702a380808e3c8a04ba0202daf5d79dc02820a0238f2281ae808076fd7527300afc3356ff574cef501f6c71f44fdaef60a9ffd6eff4feedf9d2f9b63f9eedf9b6fdd3faf1fcbea0894f9d405140408bf85a134802029a84d409140504b488af35812420a049489d405140408bf85a134802029a84d40914053e79074de38def3869f107f5f63b56ea7ffafc1f108d7f727afed3fb8f87fb81e2e9fb4b3ee3117c41471e45025d0101edfaeb4e601410d091479140574040bbfeba1318050474e45124d01510d0aebfee044601011d79140974057ee21db47b827df7f40e96deb1527d3fe1bc439a7f5efde74f9afff4fe69be57d77d415f7dfd0e7f750101bdfa0d99efd50202faeaeb77f8ab0b08e8d56fc87caf1610d0575fbfc35f5d4040af7e43e67bb58080befafa1dfeea02de41f33be0f60ed33b627a87dcf64ffb6fe7dbaedf9eefd1eb7d411f7dbd0e77770101bdfb0d9affd10202fae8eb75b8bb0b08e8dd6fd0fc8f1610d0475fafc3dd5d4040ef7e83e67fb480803efa7a1deeee023ff10e9aded9ae6e94def1d2fce9fca99efa6fd7a7f9533dcd97d65fbd9e7cabf3fb8256f93527300b08e8eca34aa02a20a0557ecd09cc02023afba812a80a0868955f7302b38080ce3eaa04aa02025ae5d79cc02cf0c93be8d3dfc166a1fcbf174d3e977e674b87ff5b4ff3a7f3a716697deabfdd3fadafd67d41abfc9a1398050474f65125501510d02abfe6046601019d7d5409540504b4caaf3981594040671f5502550101adf26b4e6016d8be31cdbbdfa39adee1d229b686dbfe69be544ff3a7f9b6ebb7f3a5f5b7aefb82defafa0cff7401017dfa0d3bdfad0504f4d6d767f8a70b08e8d36fd8f96e2d20a0b7be3ec33f5d40409f7ec3ce776b0101bdf5f519fee902e90debebfce91d2c197dd223ed31d5d37cdbfe69ff69b6afdad5fb5ffd7cc9eff4fc69ff345ffa7d8c755fd091479140574040bbfeba1318050474e45124d01510d0aebfee044601011d791409740504b4ebaf3b81514040471e45025d814ffe7f714f4f98de9952ffed3bd4b67f9a2fd553ff74beb43ef53fbd7feafff4faea7e7c419ffef370be5b0b08e8adafcff04f1710d0a7dfb0f3dd5a40406f7d7d867fba80803efd869defd602027aebeb33fcd30504f4e937ec7cb716486f603f71b8f40eb49d61bb7f5abf35d89e6fdb3fad4fe73f3dffe9fe57df7f9ccf1734fd7cd509140504b488af35812420a049489d405140408bf85a134802029a84d409140504b488af35812420a049489d4051e0eb8d6b7c87f95b3ffd0e76faf8e97ca97f3a7fda3fad4ffdb7f5f67ca9fff67cedf547efd717b47dbdfa1318040474c05122d01610d0f60de84f601010d0014789405b4040db37a03f81414040071c25026d01016ddf80fe040681af379cb7bf539d3effd177b2e16e3f2da5f39f9e3ff5fff41cdffdbb74be34dfd1f5bea0dfbd56eb08fc828080fe02b21604be2b20a0df95b38ec02f0808e82f206b41e0bb0202fa5d39eb08fc828080fe02b21604be2b20a0df95b38ec02f08fcc4bf1f74fb0eb43de6b6ff76fd76feedfaed3bdde9fe69ffe49fd6b7cf9fe64be71be7f7054dbcea048a02025ac4d79a401210d024a44ea02820a0457cad092401014d42ea048a02025ac4d79a401210d024a44ea02890de688aa37ddc7a7c47fabbcbddcfd83e5feafff1457df30fb7f797e64ffba7f5e958abfd7d4113af3a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280aa4379a9f186dfb8e9466d89e613b5fea9ff6dfae3fed93f64fe74beb4fd76fedeb0b7afae7617f020b01015de0594ae0b480809e16b63f818580802ef02c25705a40404f0bdb9fc042404017789612382d20a0a785ed4f6021f0f546b47dc74aef4c8bf13e5a9ae6dfceb7ddfff4fa8f908a7f94fcdb3e69be2d5d3adfb8bf2fe8c8a348a02b20a05d7fdd098c02023af22812e80a0868d75f7702a380808e3c8a04ba0202daf5d79dc02820a0238f2281aec0d71b507aa749ef44edf549703bdf76ffb47e5b4ff793f6dffa6cd76fe74bebdb3e69bed1cf1734f1a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2c0f68de86bf4f11da778b6ffb74e67dcce7f7affff9fe3bbffbd9d2fad4f7325dfedfea97faaa7f9d2fa544fe71bfbfb82265e75024501012de26b4d20090868125227501410d022bed604928080262175024501012de26b4d200908681252275014f87aa319df617e60b6f40e945aa4f9d2fea7d7a7f9b7f5edf9eedefff4fc69ffd3bf9ff17e7d41d3f5a813280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2c0f806f3e15ca7df893e1ce3d89f25a3f6f9d37c09663bff76ffb4feeaf5e4bff2f505bdfaf59befd50202faeaeb77f8ab0b08e8d56fc87caf1610d0575fbfc35f5d4040af7e43e67bb58080befafa1dfeea02027af51b32dfab05d21bce1b70d23b5532681b6ee74fe74bf5edf9d3fcdbfdd3fca97f5a9fe65bedef0b9af8d509140504b488af35812420a049489d405140408bf85a134802029a84d409140504b488af35812420a049489d4051e09fbfbd57ef34c5d93f6d9ddea9523df5b9bbdff6fcc9e7743df99f3edfb6ffb8de17f4f4cfc7fe04160202bac0b394c06901013d2d6c7f020b01015de0594ae0b480809e16b63f818580802ef02c25705a40404f0bdb9fc042e0eb1d34fd73fa1d29f54ff5f11d292dfea0bedd7feb97faa7fdd3fa4490d69fee9fe64bfdd3fa6d3df55ff9f9826eafc77a02070504f420aead096c0504742b683d818302027a10d7d604b60202ba15b49ec04101013d886b6b025b0101dd0a5a4fe0a0c027efa0a97d7ae749eb533dbd33a5f5ed7af2d99eeff4fe57f73b3ddff67e56f3f982aef82c2670564040cffada9dc04a4040577c1613382b20a0677ded4e602520a02b3e8b099c1510d0b3be7627b01210d0159fc504ce0afcc43be8d909afbf7bf59decfa3c71c2e4b77de74debe380e10f8eeeef0b1af49509340504b4a9af37812020a0014899405340409bfa7a130802021a809409340504b4a9af37812020a00148994053c03b68fef7a3a677baedfd1d7d47fb3b5cda7f7bbeedfe69fdd6f7eaebc7f3fb825efdfaccf76a01017df5f53bfcd50504f4ea3764be570b08e8abafdfe1af2e20a057bf21f3bd5a40405f7dfd0e7f750101bdfa0d99efd5023ff10eba7d47bbfa058cef547f873f7dfeb4ffe9f9b6fdb7f79bfaa7fdb7eb4ffb8ef3fb828e3c8a04ba0202daf5d79dc02820a0238f2281ae808076fd7527300a08e8c8a348a02b20a05d7fdd098c02023af22812e80a7cbd11a5779eee84fbeea7dfc1f613ce3bb4e7dff69f4fb7fffda5f94effbe53ffd5f97d41139f3a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280afc1753e7a9d79c5c60f40000000049454e44ae426082", expectedOutput: "http://en.m.wikipedia.org", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "Parse QR Code", "args": [true] }, ], }, { name: "Parse QR Code : Angled code", input: "89504e470d0a1a0a0000000d49484452000000d9000001010806000000f5a4008000000c45694343504943432050726f66696c65000048899557075853c9169e5b52496881084809bd89d2ab94105aa44a156c84249050624c082276655905d72e2260435744145d5d01592bf6b228f6fe5017959575b16043e54d0aaceb7eefbdef9d7c73ef7fcf9cf94fc9dcb93300e8d4f2a4d27c5417800249a12c31329435213d83457a0468f087003fe0c9e3cba5ec8484180065e8fe77797b035a42b9eaa2e4fa67ff7f153d8150ce07004980384b20e71740fc330078295f2a2b0480e80bf5d6330aa54a3c096203190c1062a912e7a871a91267a97195ca26399103f12e00c8341e4f960380762bd4b38af8399047fb16c4ae12815802800e19e220be882780380ae2510505d39418da0187acaf7872fec69935ccc9e3e50c63752e2a218789e5d27cdeccffb31cff5b0af215433eec60a389645189ca9c61dd6ee54d8b56621ac4bd92acb87888f5217e2f16a8ec2146a92245548ada1e35e5cb39b0668009b1ab8017160db129c41192fcb8188d3e2b5b1cc18518ce10b4585cc84dd68c5d2c94872769386b65d312e38770b68cc3d68c6de2c9547e95f6271579296c0dff2d91903bc4ffa644949ca68e19a3168953e320d6869829cf4b8a56db603625224edc908d4c91a88cdf06627fa1243254cd8f4dc99645246aec6505f2a17cb1c52231374e83ab0b45c9511a9e5d7c9e2a7e23885b851276ca108f503e2166281781302c5c9d3b76592849d1e48b75490b431335635f49f31334f63855981fa9d45b416c2a2f4ad28cc5830ae18454f3e371d2c28464759c78562e6f5c823a1ebc18c4000e08032ca0802d0b4c03b940dcd1dbd20b9fd43d11800764200708818b463334224dd52381d7245002fe804808e4c3e34255bd425004f59f87b5eaab0bc856f516a946e4812710178068900f9f15aa5192616fa9e037a811ffc33b1fc69a0f9bb2ef9f3a36d4c468348a215e96ce9025319c18468c2246101d71133c080fc063e035043677dc17f71b8af62f7bc2134227e111e13aa18b707baa78a1ec9b7c58201674410f119a9cb3bece19b783ac5e78281e08f92137cec44d800bee093db1f160e8db0b6a399ac895d97fcbfdb71cbeaabac68ee24a412923282114876f476a3b697b0db3286bfa7585d4b1660dd79533dcf3ad7fce579516c07bf4b796d8626c3f76063b8e9dc30e612d80851dc55ab18bd861251e9e45bfa966d190b744553c799047fc0f7f3c8d4f6525e5ae8dae3dae9fd47d85c262e5fa0838d3a43365e21c51218b0d577e218b2be18f1ec572777583abb6f23ba25ea65e3355df078479fe2fdda20b00049e1b1c1cfce52f5d743600fb4e0040fdcace7e9b7a2d3e5bcf57c88ad43a5c7921002ad0816f94313007d6c001e6e30ebc41000801e1601c8807c9201d4c815516c1f92c0333c06cb00094810ab002ac05d56013d80a7680dd601f680187c071701a5c0097c1757017ce9e6ef01cf481b7600041101242471888316281d822ce883be28b0421e1480c9288a42399480e224114c86c64115281ac42aa912d4803f2137210398e9c433a91dbc843a40779857c443194861aa066a81d3a06f545d968349a8c4e4673d0e968095a8a2e43abd03a7417da8c1e472fa0d7d12ef439da8f014c0b636296980be68b71b0782c03cbc664d85cac1cabc4eab026ac0dfecf57b12eac17fb80137106cec25de00c8ec253703e3e1d9f8b2fc5abf11d78337e12bf8a3fc4fbf02f043ac194e04cf0277009130839841984324225613be100e1147c9bba096f89442293684ff4816f633a3197388bb894b881b887788cd8497c4cec279148c624675220299ec4231592ca48eb49bb4847495748dda4f7642db205d99d1c41ce204bc80bc995e49de423e42be4a7e4018a2ec596e24f89a708283329cb29db286d944b946eca00558f6a4f0da4265373a90ba855d426ea29ea3dea6b2d2d2d2b2d3fadf15a62adf95a555a7bb5ce6a3dd4fa40d3a739d138b44934056d19ad9e768c769bf69a4ea7dbd143e819f442fa327a03fd04fd01fdbd36437bb436575ba03d4fbb46bb59fb8af60b1d8a8ead0e5b678a4e894ea5ce7e9d4b3abdba145d3b5d8e2e4f77ae6e8dee41dd9bbafd7a0c3d37bd78bd02bda57a3bf5cee93dd327e9dbe987eb0bf44bf5b7ea9fd07fccc018d60c0e83cf58c4d8c638c5e836201ad81b700d720d2a0c761b7418f419ea1b7a1aa61a161bd6181e36ec62624c3b269799cf5ccedcc7bcc1fc38c26c047b8470c492114d23ae8c786734d228c44868546eb4c7e8bad147639671b8719ef14ae316e3fb26b88993c9789319261b4d4e99f48e34181930923fb27ce4be91774c515327d344d359a65b4d2f9af69b999b459a49cdd69b9d30eb35679a8798e79aaf313f62de63c1b008b2105bacb1386af13bcb90c566e5b3aa5827597d96a69651960acb2d961d960356f65629560badf658ddb7a65afb5a675bafb16eb7eeb3b1b089b5996dd36873c79662eb6b2bb25d677bc6f69d9dbd5d9addf7762d76cfec8decb9f625f68df6f71ce80ec10ed31dea1cae39121d7d1df31c37385e76429dbc9c444e354e979c51676f67b1f306e7ce5184517ea324a3ea46dd74a1b9b05d8a5c1a5d1e8e668e8e19bd7074cbe817636cc6648c5939e6cc982fae5eaef9aedb5cefbae9bb8d735be8d6e6f6caddc99def5ee37ecd83ee11e131cfa3d5e3a5a7b3a7d073a3e72d2f8657acd7f75eed5e9fbd7dbc65de4dde3d3e363e993eb53e377d0d7c137c97fa9ef523f885facdf33be4f7c1dfdbbfd07f9fff9f012e0179013b039e8db51f2b1cbb6dece340ab405ee096c0ae20565066d0e6a0ae60cb605e705df0a310eb1041c8f690a76c47762e7b17fb45a86ba82cf440e83b8e3f670ee75818161619561ed611ae1f9e125e1dfe20c22a2227a231a22fd22b7256e4b128425474d4caa89b5c332e9fdbc0ed1be7336eceb893d1b4e8a4e8eae847314e31b298b65834765cecead87b71b67192b8967810cf8d5f1d7f3fc13e617ac22fe389e313c6d78c7f92e896383bf14c1223696ad2cea4b7c9a1c9cb93efa638a42852da53755227a536a4be4b0b4b5b95d63561cc8439132ea49ba48bd35b334819a919db33fa27864f5c3bb17b92d7a4b2493726db4f2e9e7c6e8ac994fc2987a7ea4ce54ddd9f49c84ccbdc99f98917cfabe3f56771b36ab3faf81cfe3afe734188608da04718285c257c9a1d98bd2afb594e60ceea9c1e51b0a852d42be688abc52f73a37237e5becb8bcfabcf1bcc4fcbdf53402ec82c3828d197e4494e4e339f563cad53ea2c2d93764df79fbe767a9f2c5ab65d8ec827cb5b0b0de086fda2c241f19de2615150514dd1fb19a933f617eb154b8a2fce749ab964e6d39288921f67e1b3f8b3da675bce5e30fbe11cf69c2d7391b95973dbe759cf2b9dd73d3f72fe8e05d405790b7e5de8ba70d5c2378bd216b5959a95ce2f7dfc5de4778d65da65b2b29bdf077cbf6931be58bcb86389c792f54bbe940bcacf57b85654567c5aca5f7afe07b71faa7e185c96bdac63b9f7f28d2b882b242b6eac0c5eb96395deaa92558f57c7ae6e5ec35a53bee6cddaa96bcf557a566e5a475da758d7551553d5bade66fd8af59faa45d5d76b426bf6d49ad62ea97db741b0e1cac6908d4d9bcc36556cfab859bcf9d696c82dcd757675955b895b8bb63ed996baedcc8fbe3f366c37d95eb1fd73bda4be6b47e28e930d3e0d0d3b4d772e6f441b158d3dbb26edbabc3b6c776b934bd3963dcc3d157bc15ec5dedf7fcafce9c6bee87dedfb7df737fd6cfb73ed01c681f266a47966735f8ba8a5ab35bdb5f3e0b883ed6d016d077e19fd4bfd21cb4335870d0f2f3f423d527a64f068c9d1fe63d263bdc7738e3f6e9fda7ef7c48413d74e8e3fd9712afad4d9d311a74f9c619f397a36f0eca173fee70e9ef73ddf72c1fb42f345af8b077ef5faf540877747f3259f4bad97fd2eb7758eed3c7225f8caf1ab61574f5fe35ebb703dee7ae78d941bb76e4ebad9754b70ebd9edfcdb2fef14dd19b83bff1ee15ef97dddfb950f4c1fd4fdcbf15f7bbabcbb0e3f0c7b78f151d2a3bb8ff98f9fff26ffed5377e913fa93caa7164f1b9eb93f3bd413d173f9f789bf773f973e1fe82dfb43ef8fda170e2f7efe33e4cf8b7d13faba5fca5e0ebe5afadaf875fd1bcf37edfd09fd0fde16bc1d7857fedef8fd8e0fbe1fce7c4cfbf87460c627d2a7aacf8e9fdbbe447fb93758303828e5c978aaad00061b9a0df70dafea01a0a703c0b80cf70f13d5e73c9520eab3a90a81ff84d56741957803d0046fcaed3ae718007b61b30b814712d8945bf5e410807a780c378dc8b33ddcd55c3478e221bc1f1c7c6d0600a90d80cfb2c1c1810d83839fe13e06bb0dc0b1e9eaf3a55288f06cb0394889ae1b09e6836fe4df597b805de7ee9c9f0000000970485973000016250000162501495224f00000020469545874584d4c3a636f6d2e61646f62652e786d7000000000003c783a786d706d65746120786d6c6e733a783d2261646f62653a6e733a6d6574612f2220783a786d70746b3d22584d5020436f726520352e342e30223e0a2020203c7264663a52444620786d6c6e733a7264663d22687474703a2f2f7777772e77332e6f72672f313939392f30322f32322d7264662d73796e7461782d6e7323223e0a2020202020203c7264663a4465736372697074696f6e207264663a61626f75743d22220a202020202020202020202020786d6c6e733a657869663d22687474703a2f2f6e732e61646f62652e636f6d2f657869662f312e302f220a202020202020202020202020786d6c6e733a746966663d22687474703a2f2f6e732e61646f62652e636f6d2f746966662f312e302f223e0a2020202020202020203c657869663a506978656c5944696d656e73696f6e3e3531343c2f657869663a506978656c5944696d656e73696f6e3e0a2020202020202020203c657869663a506978656c5844696d656e73696f6e3e3433343c2f657869663a506978656c5844696d656e73696f6e3e0a2020202020202020203c746966663a4f7269656e746174696f6e3e313c2f746966663a4f7269656e746174696f6e3e0a2020202020203c2f7264663a4465736372697074696f6e3e0a2020203c2f7264663a5244463e0a3c2f783a786d706d6574613e0adc0adfa8000040004944415478019cbdf9975dc775df7b7ac24080004180a448511249c99235d8966d65bdacf592b59265ffd779ebe5b767bf48721249912c5103e70100310f0da0877c3fdfefde75ea369a92e2eabee754edda73cd75eadcbbf5ebdffcea78d93a5e8e8f75d3dfb21c2fcbd696d2a4880f9060cef2dd70650d60c5c34544fe175fc375114ff3260aec9400ee966527937882734cde38cdcb3a8154a8a4c9b32d86152d602319c1a941642d5b5665816d5dc44b59e11bd88a019cfcd0c6872634bcf19acf69e9a6ed3ceecdb3ef336cc6fb6371680933ffd360338f39bfe39d9fda30fbae732443605597e7bcdb856f5a5cd44e1466d50c9753b2567d2d7b2a7bd30fdac8ed3201171b3bbd6af5c7636ddf5c365f166f4e737ecbedbce6b7a689510f97651bd3d09f44ecda56ba5d4ae32363f54f5ca1ab329ca76c3bd319182b647b3d98e1cb3569d04b9aef51c35849836654f88786dbf1f151f8926dc08aa75882e016ef4b039bf79c4e3cb80de73e841b38cbb75d6048467f409a1d1fdb632bbce77cf2fad370e3072d57f4970adb2e8df0eeece7703b63bab7be806659a4c96b1e8d27d0149240efc63dc9a34c9a682a3a32a8375350a22186b753d0456881e175c92e5cb4b09ead5ceb78c27f9dbddad4729ba0d3cfdfdbbe9339ed17e073bcf166d82a77aea7916d3cd5d7e3c383e5e0c9e365d7a3556a75f342827aa63802320cb27f6c19de60a4239f5c5cf57c0136b3129ba4e98bc43c424d66f02a2d9670c790964dc327173da24651b40a834303cc6140c36b430a621d36f31a7afa7dc6b50fa4103042a7d1d38e2e9833ebd2387defbc4eb7f63436bb56b7d6ba71bfecde7a9c963fe7b56ec18beeed5b6065ce6033db3c8015712382850ac58da5e12e287497f6cac71ef39d990beef2c55f8d37d313177eebfba7fc6091ae27c5e4cfb835ef3f0375e8b1892bcd514c8deae8480debe870397af66c79f6787f79f6e8e1f2ecdedd65d71e282f1859c66e0d8fc439669a4c7b05a77fb9e39168afc3c9b1a1d4e4603bd7685c4211bcb872ae14cc459ad47445614a5f22b325870f054ca8ab6f9b186d52f3063b7611db0ca71546ebd8b6741acaf0c1976b2569f87c274e9869ad651947e53bc92314ffb62bbc36f895f214f99f533f5b4ffcd17c28e594a22d4961dab991451934aeb56edbe05166cc1c00b5bf5bde067dd1ccb73f953fe3126ffe27e19dee7cea864de98c519f54328787cbe1c1b3e5e8e9538d58fbcbd3876a540f1f2c4f6edf591eddb8b91cdcbebdec3e7a442373ed1b37f3a2209a690b99a4b502a0b812142e701bab341ce05cdc577e0573ae4a95fcc80aa6c50816caa185215cc81f8c9ddd12227b20a281f23302029df8979ec16d0d5a560b5839756c2df086c01fb99d4e835af192d1e9c63aedde38eddbd60a5ccbe07e1ae1bf11863cf8b69c3fd5c05a3fc40d1ddb76ddbb14ad6529bad60dca79d51e7497bcdc93788c58750261aa8325b375b0b89054198bfb0cacbcbea51e44efe6d1797db78fcb270dc396233a78b487bf3e47079a023e7db21cee6ba452a37a72f7f6f2548d695f1feecbfd07cb9646b1ad274f9733c2df113335b254dca16319de70ee0eab8fecae51afdc44a2c46906acae8a9ba1335ef1b3b3cd231c576729cdff9837910f913e78ad528e983e318842439ab87d33eeb133f44d01acf15a7e446ce2813f170638276d1eb61569f0a1dce405deccabe51ad3ca102354212731ae4d1f8c786664e2a64d7123ab23a7c93e690bb88d77926e54023b1447805c9f386f52818c350cd50c5e956d1fac7566a2299e40120d17afd5075a735ee58567c38338fb6e902ad2f2811167ea77cc14506bab43462a8d4afbf7ee2dfb376f2ecfbef86239be7b6f39d474f0588d6d5b53c4b34792cb47817add76ecba3cdd8d29d3baa4624a8a915bc9e019948a55f93638a8c10fcab892d5386d2a3c5da0ca2c29c6273f0e2082a16b7a6d38e1b78e5026d5a598990eae70eb7b70da86be0345dec93474316f73946afcdcb96e86b6cbfc2acb766ea23915bf62233aae21f6c73f23af1434af3865836e93837861fa9f08ad57dfff04bab3db3e27ec5e49463782ed507c86b76db38385bada0859d1c3823c7d4e86933a468f889cf3da5f1b7a8ad90c5f7997d7748b9a891c69fa77a80673f0e489d754079afe3dbb777f797cebe6f2f8fa8de5e88ed6586a703bcf0e962d8d6a3b6a885e5d89f1f6b6360d6580b6e80453a444ecb602162e2095b97d63981c84e1c1afd8405819e16c70c020147fc7406f3879ddc21def4222e1b052a630d0298ee00e6df8b534e456af015eb3d13dac1b2f192d2e3cd7bc2eac1450d346eb86c12171e0c90bd75c078fa1c58ab3d2cd14a7c79b4fe70edae177f45b79cf781dff3fb90ffe220a5b7c1c4f22a7f323b37ca69bcb31682e01bbc4d95526662640c1d098b272d2e558e566c8691e0d7e49423b9b75d2741aea66275c3a1b3b6556d1615794922e1aa98ed4605853b151f1e4ee5d4dfdee2c4f6fdd5a9edcbcb11caa51ed686ab8abf5d7ae1ad5b6eedb6597b5c1980a8caa19c8c8296b955f1b1f8525b89d6b26e56860cd8518792e000c0bbe5d55307883afdb44078340cc6b6568a4b5c2a4008c5a1489a767870bb23a804d41afb2120b4471c9318e49bab23435a690e1cce1fc5597156f8e9d963f5c3210c3732415398d6eceffb2f84c67bf5ae7609f6c0827793cafd74a37f39de9d2b6ba802cd1badb22fc29a61bd691287468cd97bbffe02cbf0b4757fd811c02f884ae888b0db97398a575dd6cd8ea0af15ed98cb24462fb88fbb15a8077ff98fe69b38251eb68ffc9f2f4fedde5e1679f2d4f3eff7c3950a33abaff68d9d6d63b53c0ddc3238d505b79ac82ed8c56f0425e2d652c9acb5042564c83d5aebc62a2184766706d40807106706506946b0409360cb4e4f808064140baffa082038f07089b3d10f846883ee8652c2e2d4f1a0cc3820c6702a2c8ebca33ec755e70c90b9e4936e24d979cf5da85b442d658f8c5e11d5f73d7d81fcb036b96ddf26658739a61e035dfa669bcbe071edf358cfbcce7cbd233ed285ff96fcb0e0c0fe3c4fd66ef1e5ef92913c945f4c88f1ed01b0453d711488364bde00fc4e4ba2499db363929430a2f7680104121a53c80e57eacd1e79091eae9fe72f0f891362bee6bb4d2ba8ad18a4d0a8d52db9a121e6b6db568c38246c57c4fed4a5c75d9d1d6056a7888125ffe4bc7966be54a1f978991d035a1363ee022a5cd43ac95245dba0793247042df0bc54ae46279a0b42241a5c108d9817b417573cc595c700c72920f890d5512fa829a8be18e91573a83655e95a15bf36ab8cb75c800994f38a373e3c3e1641ad81cda46601db7935b58217f595ee3aef921681dfa5e6c9a9b64ad9e681c78ccf1e26458f3dfe4737aca9c27fdf1733f338daf442797ade5a9b49ceac6830e4e52def844170560433f700196db31a5a2c11b59e604a6f37d2dbbbb36b55ddcad0f02794e45c3d20e20cfaa9e3ea051a941ddd6f44f5340b6d58fb4c6dad19a6b4b5345b775d1b10b68855b19cb25e148e95c56c720c9744408dc09b95baf22055a0fa351bb9cd9569346e9a6171cac0e8e555e8c4d2258e49640c74217a5e0401eb0152f71e5984c1767b5c4d057260c148283ba6938c13179107cb509134e574473b0b0e20dc7081fd49b492b34f2be2c125f8457c7072eca28445f9958e9913ff2563c618f0a0a3e3a46afd5d2e6d3f7e6076ec3d67259750b07ae92c77fb3447cc7615669f350ad6c9f92e5204050565ec06716eddb3486cea47ca14c883b90a22065a69c19418c93033ef6b116626d45c37acaee9f1ad2935bda52d70ee0b33bb7976335b4ad478f972de5ef6aaab8a551894171e8241e8ee32ffd5903f8a246e5b9a32979f125f60140b5d09ba2d2403bec0e58792442c88655196a41e1576e44ba718482b6254cf802bb70615101b53b385605d5b0cd7bf14a4d2aa1d2c484d2cefc237ea0d888c2519ca475b08ee096134b8f766ee4b6ce9b5a745e712b7ea7e13c0f9bf9b71eab075af74046239031ad67db85ec8621a5f9ceb0193e6b020e9f91af68ebd27001064e1751f2a29be5150f51a38df0e123aebabb83133ce9c802af03f84e0a116a279c9d385176e19a7285e223530ba3e874077654d3363812bc850fae46af7d3d00bef18b5f2c4f3efc70d979fc78d9d1ba6b5bf02d3542ada4740f2ff44e508438707800240aaea21c6f033739954f4607e1cec9e661184e299e355d2c062299771741b66b8d4f450d776ebd7075a17486b351d51841f635e922b74da93082b46523b3c8cb50c831193f8062b48aaf921203670dadef738ccdcb5c4bef0df5ad50d3c02df14ddeab9493b1b921745ec348c3cd15686248befdd80485037ce86607449799df44926839c5388a938477a72dbf881a3edf11c89f6b44f97396670d1adebe313f744d39a536159fb2933274b04ee4b904ca21d1936bfbe1083a7d1075bce8791571362e048096ed723e5b5bbbde98d8daa2196d2fe71f3ef636fbd6b3a7cb4e2b0b4d8b870f4c140c4b34b0c2210ff9d6127f340edaf8bf0020965dd6cfca89093442040b969a2e628b922db19a790480d892433243906828178299590f4595d03fbc07eb4093766648206dc34d6d1d4acd123f942ea3a30d94044b6b6de02c7e82cef20d35b2e0d2bb758e05c9f0b5044e10a2a968abd455dfe4753af64a1f1bddf6ff319e628eae11c2d5a1e99d90b256d7bedce4157960098e5120f229346ea155c401dd1283b6f506b229133a959d89c9ed48712c2683bfd28887397fd03a8d22fc2baf3990e1b881d091023f4854f0d60b4ce25bda8dd8dadad13e441a971b18542241efeddddde5ec850bcbf98b9796fd9ddde5588d0c5af4e332362e4896ee2d0f1c8f58c8e9047404eb92e8c615c162cc5548beae51242720c32319c856665c950a60724e3943f03880c2222166856ec645e7782ba204b8293010c83530c4bab656653f10818a9953a592c8a0f634c30cd18382888c422d7ec983323a0b2cc476727172baf3c9ebf8e045c40242dff0c635cfc9094d6f9bbf84dfaa83b8c9cc62bfa19ff9e2107d363d610f580db261e0fa6a1d92e70a96ccd022c0f99b3698892e436770f01119451ffb4803c4d1755714aeded6870c705d89190d48e191e7b8113b9ef594f928d3eb2be46b6462eb7c57bb7bf623fc0483ab7731a51bb8c8b02f85b77dfedcb2f7d2e565fbcc99e560ff917c821712845d31f449bcf5b3cec0fc69fc4d3cd30c02f42b7e2bdbe821f2e6df02d5e4a9882411e2ff11075c035b60ba62642a48718718e54a01aa03a699165cd3cc2811e2fcce0c57634eac422b9c48328571cc9564cb2ef1d0761895d8b0e26079144e154c21cf4ee97850c10dedc466b55f799b72c210d8a02bfa81d70a4ef7c60534e3cdf1091dac918c2de52381e735ce402292427641a45456bbd6525a29bc1ca0fcaa23037bb0200eaa2e862b623d025d6d074721bea301cd1f352ea5d769217506795b1aadd460f4d9613ac85ebaf927df820c81b3e456edb0fc336797b32fbdb4ec9d3bbf1cead9978f5e28bfed6b3f5b77518f7a0ab1b9e11ee2b1ca40524ab6edc082be7219facd48c26b0c3d27834c41ad29420104e8b404d0d00c71bc009040e700ad707c697af026dac223177e56bc5b3069321ccc2431450758111f5511c4fa80c667040a4889268f27d6dc89512a4e238aaff24ea00f3630a0e0bb804877dc8da008c30d25c1d80c27e9e75cf328007cc3b300a7f06a9cbe0ffac26d3fba62ad265ae7c675b9768518860bb9edc48ce6070f0279be2963d060ee8a085f07e513f35591dc69508a731650b51f1650322a6df568a5b8d3c0f8e8cfbe1662fc0251e4b50473b73ec2d69471efc517fd7972f3bae41c926df950c567d6d0da398f7cd1c3d6b23adb54d11190d0da5263c497d2820ce73a923c684d105d359229e538177d246d804c52d9945ef149419a08cd0276cdc72dc1a7ff48ab6bb2e01b4d1758995db12951a2023029091b32851c9e1d2f60d02dcb7a1bb9882467759e33203a11da516b3e0e9c1d3ec7e35ce982ddfa741a3ffca930708538f384ce79f0301b2eab3ee43b08dc3cfade599dbf4166165c563dc379e5bdf2699b831f73ac0c428badf2caceca096f1282e7e6abd1d29894e61f3af8707a82cd0ae2e62b9eba3b381a59a0eb5f2132c175f9230774d8393fe42e0fed74ec5c7c61397ff5caf2f8c3dde5e0705d970d7f979ec53c1b1c1a1d90e750bc93884dc4bdd32824b22377a54163c891e178f38250c123d9fc70b39d11a5b0442c4f5a05653352b6dd8296c08c5f8a94f350b5661d500ed2d2d6b0be84ad38c612239bbfe9e0334c3449a647162c9aa2f6ad0d175af1b213ccb899c322f154b6769ce418cc3d952ff916e9cb69e9f82c389d3fee25c73a58aa042043bab67d0658ae2f61345fbf043c504ecd8f4fc059ed1f14ae5cd111bc3090d543a7b91c867d42ebb8ef2a7beffcc99801879bd989971ad68ed653dc91d53e592bc2aa4f3400a7612913f80aaaaa9872b5e3f01de9296febfcf9e5eccb57979d73e7fc5672d8ace568c6e8ab8f836e6d39323d7d7506d20420d3b8c8ef007da58ad8fa166cd867526d7cf86853d362045c4b69839b997986a385c3c01a9021f8d080781c5139ca575a89193a0c683ac446e0067da8c801b1f818315af411ada8221cf8344f25f04feb6b4794148b32cfc4b81a1716d00459f1a98084d3f0956a8d7581af10144519eedcd0642857e0356fce9a796dc485beda17b67fce159b4e0bb33d91c3142e98d0988efbb001187e9d3ef5ec0a113ee747836293427ce0393e06346f38cab710159e45206b8b0d0d9428455c1625cfe4e5c7ca06041be3efee2d3bdafcd8bd74510fa6b52e3bd6f67fe55b0fe2362af8660d9f82592eb64155ba377728d61089236ff004a375a7eef880f040ab2c41f50f8b080cdb912659fc61e5f69d16341c160af25241c10f89380a1774f3e362e5e014d5ac9533c3dd3c9c8ba410da018abb81156eb08da88b81e668273618fa1301c7cf017566d8896c1750f25b0626c499f618043071b674b47d6d9bee9535549133a2fbca0f7d9aeeb9f8503754abee915b620bdc38b9a377d4914c401580c5a6e095cad14109d2f8bc759aef50b8616db30bd81b1535bd9290494c8b1b7772573da49bfeb8126c8763d1098061e62998f50ac55a5e826b5db6adadfc33973465dcfd5c6f2df3e209feafbae80e2172b12bf24b4b009682fe099d8fcdf19fb5b02fa04a3eb03544aba461e9e764a870321832d1665a7612133789a59115d3bde902d2553cc286abb17defca068753833dd0f2e083eae2013a96c1cec98a3793ee1a9d160278f14463f8de0eeb0a034ad0108038083b54018919e07678e7ba908daf4c33090ff2d70a30f3acfc4637a395a6f9fef1fbac5fbce8adf4e788525da207764c748a762ae6625f20dc735ca97600cb76cccb43606db12b015ff3ae7bd30b587541fc142738cfc9d0b83c0daf0b68a3d369187c526f6243fba97858cf2e1f89e221f5f917bc95bfa3dd469e973948aee55b4640a5951356d1a60b5a22b8f1313824e30a7e7c5438e537fbc3584dc573b2d1326c5f9828df4ccc1e13c9932113ee2a79564384030721cae3dff214315f390cc74440e4c1e24440d9e11411c2c25745fcb4c4f9a517422cc3970d4e29a00d502a854056a148ca475d1f8c837c172c99e089001a82f31c130098f5113e305dacfb0975e2c9a0173bf3b5cb8ade2ca7cbf0c304db8c46a7d63f79d66203cdfa18b2e6356cbe139fd390a0c3b6a78092e5531658527cfa568eb1bf202adb1baf4bd0591b7e11836af891abb41c02bb9477ca20cfc8065bb48295823da97bd2d4ab1d6de19f7de9eab2abc676f8f8a150d8d10443d742dff0d79418fa0bd1fa800fad1522d175a25921374ccb0560172cd1750b9f7431a4d0a10b63c515ec56c1ad0ff984bac7912230a1e0e683a3aaa11855f112003e0e349d6136bfe8904b14051042080569624e950c72d10954e04efbca05992331225d898c31eb780277387cf5dee031f20a02cf8675bcef4d14f6b1bd6136165ba7d07e9bef64b7de9b70b8429fbb7d03b242cb6fba2fbbf32d4b8dcf7d3db2246e65bb7d2b119d36415f100f8b1858d02a14cac01078a5ac0ac16a53275c7ef0e60f1e260833a81b94ba4766f2c2a7e38173e561f4eee5cbd9cabf7d933656219ccc1e1ef99784408c64218a055539659850d00d7065f95eca965f845478f08a5d7aa09ec45a414857bd0f7d730cfd2a01c48d00a2826e422d05b8a752a5d1140e8aa8f61b8f565060d317f5cc239a0bcd25704280f05de9fa0ee19f085d51a874edd39324aea003b8a9a3f3ac0b8e8c7ceb30c5216d39838d22f821e6ae3ceda349117812e67b78a512261b2ef2a151c3911ee564a7028f533fd283ff98015f5506ad67b8b7de7db732272fa5233a38441d94367d5b194bda172005b2dad61c94a7ff869b9dd11d8b14a2412a3ef107d0391cebe8d5cec50b6a6497f4407b7739d4dbcc84e60d0fdb8d0df8a0d3c5c47827ed1322a0f8ab10eb869f56de2da7edac8d0f3bd306a0098c8a483014a0629c162c74ce281e2b2be83aa5a80870be2105360fcb54b660951baed5cb91e8027707905c5d2950b305c39f382115ab650706d1a63380c023a1f3926e7995b9719bf3da57dcfb333b3c66726d39a52f86e8bf79cd7c10161ef08c68fc74d29ec01a97dcd860547c7dcac75cc4d4d33f9e59d5b63ad48c601dda06eb6755cd35fa388dfaf89f446c0f0670854e94e9c12d6394dd7653273a8ecc8eb71ecd0adb1c8493d0bc62734f279dc7baec05d6655796adbd33cba2173691dfa17d3a81cadf2be7c666dd4980be61ed1be0cfe92b74db41a602d47576314c36d65c665eac85d90538288bc190dc9ab7138ac6f8a24e0fdb6aa60ca24ce705535a47492bfbe5464423708b4eb7141277602916a2a4319660a8018a0d59c90db8319b5fe7a502b48367e7cef139df7114585996326821dded3c5dd1cffe0331f65bd7f229f922e452b639aa38b4f9040296fe34c3891ec983371f1f57aa3555e347c4ac6072daa6f069ecd281a448b0c141b755c50926a05305dae4553428407ee386b5ed80377a60ff18a1c1b7307215a0132c7894114035323d273b77f5e565578dede0d17de1cd44a16bf5d1cb648027bc86c1d1fe98f24c136164275ff793bca01b8dcc0d0c24287c9d4500d19f401e49741f2a0333c9801440c0eae58605e029c00b242b6ec8742983b127f9a9746d8fa5281199666362d2d611ba21b062ce34656181e3285787d9b90d9bf5eb78df1be7cbeecdef64e722a356bba552ca0d6d80474764004f5e4b280bcd50962a73fe042bb0f0ea9129f77994b2edbac07fb6a775c69309c6549474e2e3216c61e416fc9491740738944f195036235434f88146a79617bd567dc20fb215a3b815c0b8360849b2796f2febb28b7a5e764bdf327508bc1ad3accad033325a5bb8473f5109bf759d75223efca7387f273504276bb2ce92c2d1395a2070301d79880f5e94064b786e50c91b57b179ae9259919233f25b6e336ee5a34d1b18bed16da210583021754741d2585d696d15851fda764cf4a73e6cc287fe7f66042de190c6115e4d1a59ce1d72c8eb029a2b6da9717a81228182f48d3b3c9346069f34a4c467f99ddf72a30d78cda7bd494e87aa0992131b2cdeba59777ccabfe45a1593ad3eb6cbada264282fdcd0788af3cc0add058387a26b2ee9410558992568e611f9b1a3e9a13be63c644f193ffa70d1c20ce65660b05a15b73f4d7f0216a221ba93eb5df8d601088ae54244011ff79a2c100383646b2d78647524596225e638c116eb62018d145603e48870abd223bc41435ef10dbf24ba22da69c940e22a0b964e073f784148c5480107b25616a7710e91f2783800383d74619e96db76a6e2cf1854c02a84a9f01a6306751cfcf513cc35dd927a07b05e0371ed086eec6e09eb1d1e1d828e9c86b49f9ef742ecc64d5418f28b28ce73d27e47efce9367c95eb90d41c257bcf4356f381ab4491139f11ff8e8dffcfa8ef6b35d9d26dfb32ebdfa72e6e59797edb367f5653a4fc876401efcf35675a9a3b569c34fe71f687cb0ca85d58c6f01c0265f8de9e28cda4e8b33ac91690d2f479a89b2c64865491187b34b25d35991a98121df8e556e639aad10c337c5e5a26a19e499db7409638fa2e491240c9d146fa7f4dd08139c74e7350fd2a716de29f0e677fadd0a9ec24bd85232fad6550e699927eff04627462aeee333ce3f251f3af208cdc3898d0b721a205c174478026d1ea6375ec9ab3c0ac1f21521db288507bd7da834a0d60538109767d40b4448c62918aa4737331cba18f994cb86aee21f9bd12b71b6f2cfbc7c65d9bd707139d0f77c1473eb28140967a38748e9e63868ab1f111b3b361507e61741d1d97f60366e7804c24806ad05025a8d13e7a4616270f2c07228d8502048ce0aacf0c4b3ca3d79289456402c8e574e17003082d38ee922d0fcf0da18a8a7c0cd06db31e80acf8470aac49f716bfc14d6f3045f067f1eb335e81cd2e18e9bba07f5e31b019aaf7597b3ba31418def868f9d0eeff671d3340f68fe7868ddcada2e1ceb25ca4ae37f978fd38a977fdd8309861db109422b8961a6efb5252826372e89e0465749b02ad08655782aeed0f99d86fd6995bff3cdac121178cc112bbdfab27be9f2b2e8d59745eb3274ce5bd2118b6ce49ab772fd542c8a3563e74307382646e18166b6ab6ef09a759d463224c1974b988c24107134613b342810b464170bb4d045193241541030ce9f9b4fe505c3d7ce7501376de557db1cd8ad93790f2811f1cdff6c8ee0566203f3ff2431e49d205ae16d0f1ee88a8854a5cbf1f8c585000f120ad946df1ca59ca10bbc0973a18dc2757ef28c54781d9fefab8e116bbe126ff840441615c4375d567b12eb74f06632e7541d010e5fc362a279d976f18fccf08a6f126f7ea30e947f1a2e4218db00f338911f58ec83664be72977348a9dbb7a7579fcd1fbda653c18acbe2c027b8a2b176952aab9ccf05711361af00eb66f7df2dd608d64e088c295daf766344c354a780901ee408acec9d244ee1b8c3198d010371037d04211fdec283bdb045038d3220a7bdcccaf64531b9c6e4f08abe53e471cc4c1e7df12992b7a3b378e859b05b831c4393a2be0c3a84a09a9f1a3aa462b4578fb771db5daa84dbfb49eb10b19e08567c70d90fcf83329f228c1f66bcaaf31a147e374885dd219692a4ffa599af18a674407c12030627761b852ceb2200986ae4404186cd06e4606772a4b98f1922701f8facc0a15aab18e7bc3c086af68a1d111ab3357b4957f4e5bf9fa06ab9667552cbbb5814e6123099f80875ec80be8f96b23574ecbdaf599c432bed8354aee08517e33a6d086c0c264bab07964aa33d6fb5892995f049ee4157b4a6012f879d8ed62c79052269504471432aa42408888c4ffcdd79589c52a89ac888bcc8ee74e7e2a45f0c0919614360f48a767540d6bd5a06fdda125de773ba0cc6a39a12ba01389736d6fe0df35284e12befa4b13b2769613bc50b70b07f5000412becd213ccb292410a160614ae56e88c9a32769b2361e221b28709c215a05f96ce55d08bab5af1a1299d11fd848b3957f49273fb4d3b875fb0ba3a3867deb542eed6be8aca62eed6b602be7a28d80890348ab5fa78cdac23757d88419774076a30835912908398a2b93bc8c4ec0c21c1afbc710e114d93a8005b05e854f620e853c1aa5f28c1f8592b00e2a6a9024c42caa606656648002e91cba805c9873c646bc15a3b227a3bff3afe9fa4e2ef14ed398fac12f0ab7bc993d303ea1c38cc467dcc0ccfd8411f6c8c40e1f08860e65add36d827da08b64109c67bd4c62d8ea3ee536fbc21f0889f8ea0bf910f2a9803d1b6405b75dce8b9d224211fe73311b60000db56f8a5ca0c08c7e223f3257ffc56fa5d39ea68c972f2d6774fae3f1679fe8547e6de58b470e5f8017dc2ebfe886978a47639cb0b375cb1dbbdb364c58e35e93a13ef4a331286e93e69ade828a33e23b9b26563eabdca22f1d5dd185627c848385000b31a7411798d94dd914bcf050d2344dab3b20c37401a5f38be3c934600ec4ce8566b6909bd7cac03ed1a59def573f465ac852063ef4c67d3c893830d2e47705402e614e271eb895dfc86ffec8c7c6b5d0a0481500873c5d409a8253a603887165570ac1fc8c5e85631e45dff248b6ed452dc81a2bf4e76ea595f5424f978b989ab2ef45e54e013dc92d258684b279d8ae74e7cd7e6c05c0db0828e27599de2fd356fe9646b52dfd880468b6cbbe20be52254fe909387ca04cc7b1a164411aa99be5d33470aef7c962df90558e4f05462338c12a2cc314f2849369438b6ce088d4060028562da671d6bb9c69fa6824f54364a714b10b4560196c7f94d1f0c0c07602e99361cd8357023cfc216986695cf0cac71946867e9dfea551c5286c9c793eaf472a1dce30ab886abf4eb48d6055408e2b4c040c395547ac1f19a982f11d8d2ab4215df5122fb3c3e0e8403ab4981ee0491f02358f665aa4020e1ab1715879595480513a2215c7545d11ad3b7f09409c279c2fd3a550477ea7b943cb32cee20ac011ab33dafcd83e7b6e39d28f4e58560c32223493a6e601640ead0b30fb214a5affc022b3698c2f1c58ab91a50a3b8566f478cab40e456190e203b739cd77915979e325636e136b2123032464108f22332bd71e2b68eb8d9f4247072545a66cb309abc22b26abac0dae23017d02df9ab45692d591143072d248b296eac6846ce92c9191b329bb39735ff54885711a420272f5072cf6cc7c569dc42536874ad7c8368bca63da8349f145f42611dea9d02b93d80676cb06175de660ed261e230ffdcb81c35f951643f36cdc6815bb2c6b64acb69a877d107dd0243606a7cb00d2c6457edbd62cb903833ef7f80df8f199bd654fcfcbce5ebea29f46ba23805ae1c035e1266f9c95fff19d1fe109b70a6db3929dc7bd9f9d19068e60bb59a60bd33e8e61b069a418d6d9c9372d4815ec16d35bb75c2aed460b73fe21b7712be12ab165749e9cd5960a29782936278ad0385228e228e4a2d7ad0bc8360cb65dc900249ee776704801617b6f52d80fcae1cea77935fc3419c98bcd511cdeb36249f631b0e6256805f418d111c1566ba90bd94ecb767049532e09d195f8e03df28092c0968a9bbed3c014b057b7b617d0c938bc8d43e650980421be053c44af0a067f4ac3c74104b39c0673475ee7711fb629afe18dbfa10e6b64b6f2af5d5b1e7dfcc1ba2e33f2cad33a940c7f6fbee2ad575c352c6931e15034d60182c92ee2bbf3683328c5cb4afa6277ad9e828919714f041e8827f57c589de1fc0d3d0519445d60412045168a9b771962c70a603b8440ee286cc3438f1ee0641de51497695b1df525439fb541ad304cb30c53ad17cbb746818147c8dd1a2bd515ac0b3072523106326409a85c7c1af4dcbd462b105bb7968d9e89c7573d4ac223fa36b7ca2f61d1563afa0ca1917541992814ec94031c9cd34e598592e5403e4e8b4c71d77f3815adb25b9f94ab70fde78c4dfef022b491ca8dc8f669f13e458f26735625b634653cab2923df627574a0af24b0b2dc6c1558014154d014497b29f986750108137bf2bdf97048688f9588acc9cc97fcf0f33d152206c108652ca029c11f842105e4201c46ad16062cf4e203bc7878640b45782b6e19be347b71a974a10aa9f46a3ed2a34723f4eee0829c1e0e668322d3bedea0683bbbf0a1ed38723b3fbce01dfeb34e8d93bc56169d565e56cb062b5f0e889aa964188daf80c11d0e91928865b9270bef99af3086be8e87d986dec009e6d33a4521a0a6771917cc7643207d23918482f29d06419ff6135903cf420a802c670ad7bc95808780b193dc7c870892cccf99f8c78a422d0cc5a774eb1a6a10e06949c62f1691d36cce6aca78e5cab277e1451db1d28ffef105abfa73cd2e7a44208a008f349ee8657d0cafba278486211f4ead81ebb8d361b69ef810066831c872362e36cc90995d746ae6ceae446e65a16505d275857bc7a1b35cddc1c25834c955f7a935c6b0f0a2036e437317177bca5c444fc8ce5f6f5498a770ba50d67be48666e5db65173cf84776f522437ee892e7ab0ba1b8610a818c226fccd88ab50d090ebe7118f88a541c3876b6ee417cfeda38f109f93028c6b37e02b7ffc1b14e864112a1e6a17852f05228c401035730240033bc68da9c95aca87473bd13dd33fdccd13376fff47746eb28caac790d3d56887d00bff6c36a67a4a11edced5bed326eebb59733fa1aeffd1bfa76611fb1221ffaba3b11b3062ff2c268c809f67a457ef0db3f125c7e5044dfbb584636c9ac30849d763eb85dc90458dd497c0da629c546866801851f1133f06da52c18b806ea9a88f1d74d0a2a7b3e460863f182e93afd8305f2f8644b3d3a98b53129801610e87038accc2f72a237bc82d7d7c02b6556e2e9dea3612900d3418b49f261a74763b21e81bbc08cab4bd1b4bcbeb7dcbe3f57562d5ebe691c4073bc79599ef4ea3cdfa15b119c17bd042d7f936db98dc71dc3c8b72f7463abaf9cc6dbd830c5e7a0f0b5004ff51b624ff4ab970f7480f7a67e5fec0bfd70df8b7a78fc97dff98eefad037768067f031008bc84291ef1f83cbb0dd842768e585d58ce5ed19451a7f28f1ff7ab2f2bad3533af0936f1b6b0e9d27291613d2da8e2135e7d83b098a288152a0168db9e2a82b5420400bb7618d611b708099b454dedd23850478a552bee0318678289d2460c36d7c092e7b514dff7c71ffaeabf75705af87d9f7dd5b00ca511dff9b9479e5d00d3d2961bb4c8400fe2e625b861d604747431d0cc4d03be796df28b74f88467464a1b92aca832e4052811a5f0e08d5e136c864333e7b5eee6655563d397f256065a4bc971b75a25cf74937c09eb81dea3117434b667fa21741ad5fe93fde5e18387cb2dfd22e6a79f7eba7cfac927cb8d1b379687faed669e617ef35bdf5ade78e38de5a20ef7dabf381ec9e233cb22af20c3be5629746db7f0342a2efa76e15ded32eee87eb8ff5899fa872744f92ff6e5cb82b5d808478d4d5f0b2d0156fa733b81a7959bdf8c2e4ead5c2320fc8f077182996f186dce2b599266110528d006a210e904570427c054a433946d9d45e6effa93c3e26001d0bbd909afe50f3b8a37375053d94230c71bade9bab01aeefb24a7e1034fba269b2bba9726d800c805131d5a46f3e01e3e6df0f3824ed2cce98ef7bd1b11e98eb7acd8dc2904270eee49fae771552ac5b33918075a1980d6747c04e08c54cf9eed2f8f7466f09e7e66f6d6ad2f345add7083ba7efdc6f2851ad9a3870f8d07ddae3efcfed803fda6f33d7dfbef57bef2159fefc481c33f1292b8c53c77699ddb160c747f87727a18bda35192efff58eedc62d1e53a91b289fe83ce72e49cba379cfb4919b0ee803b5d777547aed073accade2967a77e900829d7cec2ba1696fcca99a4206018650d3b333c4dc145bcb81df9e58208b1f2785024c871efa32c38582e04685df94a299ab49981b8114c20c8aa779c14a48ecf4e236ed9e6c565a585aa1d680e6e4874122d2718602590171d9b5d648a62c39785de5442c60d091d3187066edc4ff242066186b7dc4128b68dd7b8e077e8389c4ee281031c6c1fda55821d4a8e9d79faa7df667ea8c6734bbfdbfc9946aa8f3ff978b9ad0675f7ee3d8d600f96271acd2c4a74fc3cd219557e1a19537af8edeb813153c7b7df7e47ed622f7620541291897993aace99ed231e1c638f51756b7767d97d51df62a586b6ff99d67bbc2d2d14706350cacb40409cda419010665f5ae07c11ce2c1ffc66095a363ec467302117dd141c6d064a3429a692170cddc00f00608279988379174be779be2c400ab22ab5187894524f1625719485463704b40ca2f64cf128e614743b383ab5b3218ca34abb416f11baa04bfba0ef02187d38b064025c7d810ee8139d88175974c93264c062737007df7260d38943ab39e85af4d0ad304e4b5b3f11acfc8154056d27aa278cdef86695d734cdd739ad183c856d0ac1325269fab74fa37aa0467477b971f3a6a67f9f2e9f7df6e972470f7e596f3d3bc8cfd14247e8739d69a08186a7ae92f1f8f1fef28546bcc78f1e698677de34f61bd9fa8bb6abce46a80bfab70d8094b49d60fb37cf3405e507291eaaf11e6afa0a33f2c2b7eb009481616cfb0298f520af19371e990af1103cdbdf6e648848e504c50106468ac2c0189d1c946585587728c0ca8c3b1f88e264a731911e99ce33a12e28ea1ecc0b62338ef2f0852622ca20f007e5e0195de08f1ec24136f7426ed1ad4317c09c6eae2d109eb6490c6d0b0cc3ddd7d0b632ce747ecb6ad9e66b651c4b52489d3febe0b858c235f0266c392b3c398137af96d0e9e6dd703857b1460f7b29b9e0a23bb4a6434005600d67bd74a0dd3fd654f735a56374fae28b5bcbf5ebd797cfaf7fae91ebd6725f23d5c133dedb3af6d48f47258c56841449f440cea1f8c19bc6e6d75282a5c67bace9a2a68cf7ef2f2f69db1d7aaba44b553bd3811ef8a430308c71e0beca632bc4afbe70c48a1f0ad4884985593ddc3a423c43493f1f90a3858be5498aff80b5742888e7f7c948581f5df817ff88800062f253398ca734046d70188716064e83a8ffd5b1e252d30173245f995d8866094105c32b6ea8c84d22d878adc68a05097c8795c59aaeac00d6eb068d8c318b321e59094d2c003092fa700b4ed10579e31a9f016a1e1bd9f6133ae065fef00b772b62f9f10f54f04a102f470bcfc0ce23b1d2047fa235492a05856759904ee4ad0f30372a6dab3fd5f7c933aa3032b141e146f5398d4a6b2ac1f7f7f7352a68ea255bbcb6d2b42cd3bf340efff09ff4673ac90c6c5bbfe9bca3b796cfe8eb01cee9e130a63dd01a2c8d33fad2586fddbab9bcfebad665da0d24b41751d77a964f3adef7f655570913eb62333582edf16b9c3a01f2ecf6ade1577883df6e86d748980118e6306848816f5ce792f0bf52e02b08a19e9301d04738a963a45d04beeba26c0a45414430b62121312c5934c4e4e561afda399d9880fce59f4a009ff0b3824e934da442b48f1655f1c906dcc128244c96ca43f6061fe5b56c505300e810594e9b6798a7e2992838ca03b7e546ad35bdc1c71e8277f44c1efe8a2f91d8f8e812bd75477e14d73df438ce74a28566a6b3cea53f6ca2d3c4a2688c0782027178cc30f345895298e9df811a158de6716d56dcfce286a67e9f2d9f7fae8d8a2f6e7a5a089e06a1c8156fd6527b6a30ebf42f361f1e66a4627ac8daeaecb9b3cb45fde2ca15bd7672f59557966baf5c5b2ebc7041bc3f5bfee5c73ff5f63d0d92cd753644beb879cb53d133fae108ab8821e5a8b6039f9e0cb3afcab442915e5a8eec4a87b37a91735fafbe1cf50f529c64a2b48b05bf398f54c2cc1f27e8fff9508a9135d6641b584280103c9030a88d02b01157be0b4b95825eca65566ae1990d85940d47d42e1d0cb110c56679edd4d6c3884e080ba5743397ba5b47382313e4811389aebce4d0b88ba97503af15084700c5a39818920b34d0cf76256d46e214fb5612b401dfb70287efea05c5acb4b2c7bc3ca89153bc75233dcb06abd3cd0298fd819d15fc037cb16aca3b56a33a7445beafa9d9ddbb773c05bca14d87cf19a9d4d3b32bf85453c48c46e517e9c048655d9abfef5d4fd4e8f43b612f6817efc54b2f2e57aebcb45cd5b9c1abfab2d19735fdbb74e9a5e5c2455574359ec3a343ed2a6e2def6aad744b534f9fb2d033aefd274fbd514263bfa48d8a35c44a64bb1cd78c8d747cd2fa80b496d9d685179673afbeb23cf8fdd934b28db22986f88e676db8d022e3cb7669fb9c4adf4536eb33b3d48f00460173abd1c5c8ca20cbb272a9c2147915b42b8915d82c7865b766e221e3aa70a371984665239611932cc88b66186310f8a26c078fe42c1fa03ece8bf4c45b7a24c3ae430a8c147ca2c79a477aa53935de34f63682ada27d173e2024365f4fca2a6da1f6277e84223c61827c52cd2eba4f15a87c63bc62109ab552927eaa1d401efc7eac67543cabfafcb3cfbd71c1ae2023190dcb155e95688777b2f89e7cfd41db0f7a399a649f09eee99fa6758c4c34aa573452bdf6daabcbcb7a8feb457d27fd05460fe5ef6a2ad947da6ce5e196f22e2e975fd2dbcb1af17896065b4e7edcbd7377a1f1c38bbcd8843fd632c10fe633db3ae2b3a7a0015983815e7939a3cd8fbd8b979603d96b7b365111e150aca4f3ea3f32daa7611adcbeae7536e555d3c5249883331570cfa5a17e47bd113d527a2da67ef45ee9c1c23045dd8d284eb0c9b19c0a21e5a87b92e08ad1b670c73b86860d89d0e906af0eabd2404a5a5b2f4850bba2457e3b613867e21d414db7de9b65ee913f8e51594ecb808638fa2418db3274c97f154ce10acdd983089f8b419c33f889ab2b0d5c577f4606d7614f81663f01ea74fbacd37d473cf1bb5a5ffdf8c73f5e7ef2d39f6a0770fd0d2f7880b3b7a7aaa188f9608f689a07157e57f5e29ca67fe7f5f3448c34d7b499f0ea6baf2dafbef2eaf2927ee9925d41d65ce0c2a3f5813f818aed262a5917b5ad7e4d0de98cce17f208e0785b020f0f7c0a84d195064743ef109d529fc24b5e6373c565228615c01ba76b1aa83bdf62b5c3afbee8b37fe3f3e598dd4fc19b3274f00762e32bb7d24a111a23947048987d854edef80078a461fb99e6a76cc53245d084403b3b573da4e3a8763855a01dd64a444172ac922eabc00183ae94f0bd12867686e9275a4a7b0e544ac204266a8ace334234231a55e4ec7216ba477fa8f8accc5c89940ca4f3fa0eb7cdd0f81b6c263de26c74100f35263aa8112c67ad28489d254113f381125acf4e17d4156913969c359f9875f05d9c44c31a8a75121598291b9d2912c80b6e7941ac89ed68b3824d8a4b1a9568442f69f380e9dfd5abd7345a5d592e722ef0ac7eb2487874cad13fbc88d3811356ff53f23aa42bbf9cd5c87255231efc1fdc7f0096728ef410fbe172473b98d44b4641d37311bfb618dd7c6a03b842cb4d9cab3188544cb4ac6bd409b001c2dbd2c71a3dc7ab2d71baf1c5adeeb9d92f2e4bd1970ea9d5e209bc02f1d6037b77c9229f0fa3d8136dcf3e5643dbd24fcebcf8e24bd62c0fe588a2f049d1056d214131ae5db1ca2e5a1a40f18146d1912ea3506c84a61fb02a382140976cae96a69ba003375c2ae739f89051a3897581b66d1908805a9109a8689cd930f4e64303e9ca1addaca8e4d8e4c9bc3240f843cb21de22d1cd96ea5a76b579ad53e0c16bf943a346462b318cbe5bcb3955322a36d33ba6641d90e9998b460e1a0deb2a1a160deab5573452bdfaea7259df9971fe05fdd09e1a07235e8f56f0eed910fcfaac62f48b5f805b6f25ed0695173fd9c4687859230b8f045a4746581e13307dbd78b18f58e15d33c121edbed008dc7ee78ee9d8c3875037c5348430d26af4ddd3faec4875bea7c0e0b93e5a395f00ad61f267e7da9ec220de69ee6c0679ba88703ed0efb09053842d59769afcae110ccc3c6c9b09a95571702680e276ae10c0b7f1c2360cb482d920709ad33412a414cca858479a69661e936ed653bc7db73ad0ac69252aa02c4179956d1d928ca39d9fc6d27a1b5417c46034375f8b65e085441efa39af6572a7303a5d05a364f384c471771af0e2e1af9e53691a45d9c093e958a6f2e66611830e8a138a581a7cf5b7a78acd41dc175fbce8675b48c0674ccb18a9bef6e69bcbeb5ffdaa1ad52b5a6369a45263441ed3446fa2204801193d4a9146af7c94a72d7bb6e599023eae1d4bead48bdae480a73b6fe9c24362ce29b2dbf8fefb1fb8de3193627dc66383fb1add1831ed478428947451a736d8d6d22918cf5fe30f51ea7f5b9b2e7b7c559c1aefd3dbfc803bf8196d574a782b437ca16df9236e9a153bfce3830115ad1a19c47c94a9619e052c3d1343341f0a76cf051db3205e634aa48605d8718395d03f6ab67233a5d776e041e300577093b2214a9b6558393fb9c5a990e1b1ca58552206bbac7da08cb093f8c08d39e408d57a449999375c12f059290b5fe22e88ce8d6cf81417db3a689005befeac95e8c12b2ae332c53c92ffe9c97930cb46c09dbbb7b55679e86df06fe9202d2340ec11b155805b24a2c9a81024085635bb8317b5c3f7b246a8cf3fd7ab1f48d67496b3835f79ed2bcbfff5efffbd0fe932a504064f78f58786852dfed031573eb3a1671c02d601dc077ed675dbcfd578b6c6460b75eceffef66f97bfffd18f3205d4da0c1ee7355564b4e415977e5e86ae3c2fe3d1c19b6af46735b2dad698615b3015dd7dc79fa513a612940c0d118250ed637d8bd5ae1af69e763ab7773ed5260fef97b1aecb7b6641e69a7219fc4706b683be364c649f1676ad143942a017dbdda5b76280d3711837328d66528cd03c9c9cf9755c776637e4c3d7c62b4ec108e27f2f4495ea7493929d9088e9cb69c0db00f32a4582696d8ab6e37db794ba148a99adf9818a934795a144154cd92224e4da57a0da4280450dac46258394b6f5744e36acf92a175f54d266805bb470036677c15e91fbf7ef2dbffef56f965ffef257cb4d1d597aacca8b8cbff8e6b7966bda16672dc46866a6cd7ef0af081a434428df81cfe60423ca9e2af613ed3682e446a272c756462eee3c44663a957284451a2969d6f27eb6a6e3539cac677a775ddbffec58de54e3407f3a058e5ec1ff059e8b7df5ba9fc3d168e20b8dacd2e1b2be838369e1a347fb52148575c4ead163375276438d8f2d820f739cea744131bba2d88dbe03a038d81e502ebe30bec54a3d8370082b6ec8222f79b982c1c7c1489896fad1f7ce26ede92224da33f41c9a85eb1ebf4eb83c5223d3a2585393956391a2b9801eee250d460eba550c7f1a2769e15aaba19af2c859d3cda374867823247f35844c9c871c3b0d3d4c119c418c082381a08f7a016de21a39f83002a7f91594de82c04d7252508614cc6032eb63c4f0953cf0c921f47c7fdd99adb58b379b349d5205654a45e7c65637a3077cf97ba6e916e701fff0de1f5c3169506c42b039c58746012cfe46277cc2f3afbc00c9d9413ee030eaa5e144f7b31a3dd8c0a0b171548a3e5cad66b92bbe34684ec1b3e1d065135bf472a52a24e70bd9eef779458d529ff018408d8bd196753d2f60a21bb4e8847c3e0468eea9f15dd233341fa9126c67676fb9a435d9cb5a2731e279a4c47ef9859dd0873a5942034c2157196374b9dd8c75b13c3c577ee09e8037aba3108cf1675bdf2a9c6fb13aabaff0d60fb83b88b7fe0834afb0e79a308b9bfdd2f9abbc86f4c368a5d9a1e9e9427e3b387362e6c53e78cb8e4c07e3c620834a87564e8c646c1b2c0c9372691e10c880a2338f718961b66e031f049876450a81443914556a0f10782b2f22125937178007a19dd2f45d3866aa86e67c2b8a206185a1b38923ff34671b4117e8b7996e09974ac7629ed1885d34d61b9cf7a3423f50857de38daf2e7ff3d77fad69d355f3c5001a026b98b33a6bf7e4e9335554f45017a887b49c6aa771d228a35d3d5f5225f6b32f9d27a4c2b2a6e159d3dfffdddf49c6eb6865fe3438361b68d8b76fdfb14f8ea42fd3537483378d8c72c53fe87f5b0fa8df7befbde5830f3ef434f316a7403452f9b10f46c25d3ce88069548e170ca18c7c6cb4b06bf83aafb2d0a1c8919c4fbca0cd144656ea1f1d0f4e3b3a06ffde725b7ebaa675d92e3bdd159006ff6ec891deb9ebbdcba77dd4fde796d697672ebfe475d933d99bce30b6a213c13453a1771d6fd9e0347fe3a7784cdef08c6495a16e45ced14826e13c34a417a5276923c244c892d072ad849dab58199c4c464653184e2ca15d41a6110c16a9425775836057c50b0bcc0b7ecb0e565dd1a9018a406bbda1727ce5eebe4ac0990ffc4f8676922557c940831c633f47528db21841cf4605959629d31df5f21c21e2843a8d8b91e8e1c3c719695479e0fdad6f7ed36b1324a01295fcb27e9584350b3dba666e826fab011cb8a151460e3652dbde8f1e2cbffad52f97ffa6676037d5c09ed130c59b75cebd6ff18ed66b5a77678b9d0acb74d3238a2ab9470f2931460f357c1aa01b8b84d0406ee8d5fd9ffce427cb1ffef09e474bbcc12e221dc9491f9e4ca3270fb839ef785be71ee970d8487190fe6799bebe9c9195d76108f8844e823393dff8c65bcb9e763bf1abcb4034dc7194cbca3ea02334949ccdb821557ee072c652eb527e5e69ff537dbbf0c1babe32ad2eae776217fed052c682af228a6b6e889ee50375236b6770c7a13bea5dd87dc2091c0ee5b434888da7582a19268a6616484ecbefcabc59174915c6146d5878852f0aa2344639ee8bcd4485618cf5322f240e4ea52f4e17d019a9b8289c7200b819dc9bcd602b802cec2abd2dbc90741bfa399b0b98991272eeef5f7efa2fcbbbeffed68b78d6164ce558e7980efff1273e8f752afcc1c3fb1a31e46f37041590cae1b276fb1889a89c7e56a5691f9b135fd5ee1feb94944b74a301f2dc0bdc1ef9182568e83df29ddfcdeb23db5a835fbc7051479d5ef6563cd34a746754baa387c07402acfb32cd43479dfbab0e18996cdf47767ce1ca15c76e3a15aec0f5e16b00589f71840a1d794480ed7c3866c509914b7a44c0460f34f889a355744a1c527e410d11dfe2b3b83b76c300fc6852f22417fd0c5746df510e79eeb0c4efbc6c7ca0519d575fa007afc3a059419ed9753ef7c6a7eee0a30ea1ad2dfc06726798df510bdfe58b4c1e6fa9075423d357681d7b9d16e55050b118e9ab55134c9a90b9da8d06d953808480238adeb7400193e9540a8e54f113411b52e8e6d3f16c3a84af0b2002ca61c21a82ccb1c4a462b7239a579053586333c36beb88989b00004000494441543972a029bbcbc6d0010bc057c9f3668670a9b89f69adf2d1c71fbb00f24c4995d5cff7412ce5e400a6514cdb68844c01d16d4785c6aedb0f7ff837cb3bdf7c677989758b1a05eb2b1a5c1ac0ea8e331af958673142f1ba085a334265974e15556ba9bca34587bab5bca0918a75108770d3c8a87c475e6f314d638a3874d15a9075d115c93fb3f7913b60f83b50548ae0079795ece2cffe05013b5dc86ceb732ef18ea7cc991a5331b54120a7bca0e75617f558811d4d464e68dce8358ab35b99ad7f8dc4fab364f1c5e796899c3f12d200d0af5481c1193debbb724d3f48a11f0aac75d9a87f42c406748b30a2c427bb040a7ef3459b4d7dbcbbd80a828c22bc8a408f454fc7fb434c495e7821c448b3bfcca984d97f5c2c1f1455b2c647a23ea52b6441c039c1b15c23849f9d001682045f59914ea001580f65f62388352fb01e7d52e151ae89a18dacf0073f6aaeaa96762587ad740a9d4a47607328151cbe10972dce8d6dc8a04233dda31213401df7905817461bdcf154d3bbc8802f085bcb8baad8dff9ce774cc7c8c6e6148172a301f1413e3b6667a4d74b3ae5cee8f7f1c79fb821219b11800d13d6d7aef85204fd988ed260c9a7910b6a5e3446a6b7ec3a824370a3d4c84345673dc70608e6a065bcd5d70256f9c60ac1248f38f63152315a7ef580d1389b2bd43d1e8ed391d01981878e6cbeddd3faf38ed69a5f79fd75cbb6bbb141fc08e0753c90f54a5ed7f132cf99c055d1976dca47363dbb7d538f4c32fd5e79a171f8db6f1383b6abf9a70a5097e25bf0f9acd34551984808bb6a5c4c17b777b4bba3211467528ebde80eb1257341875c898a29aeb40140aba2cc489633c983a24374801f0aea5e598e5b4361da8850243b85bb6ec39b8b10e08c0ee0ea222623878860761c594ee19cc49147e5a577a707bd779751e69677cf6838ef7cf39b3e00ebb588689b1e7ea4e2a3638f36bc78484562b3c22700d46353493975c19ae7b22a39af7f3035fbdad7f23c484ca41d5ed05d95efacf03b8c86e5bc4cf1c94326a31023dc57b58942e5646383531aafbdfa9af8bfe21d3c9564b3f246c2256dac70348a750f8175139b339cc267578f46ee20e79cd7ef307352841107bf10cafb89a330be56882fd0bffd1c38fcd9e8610a48a792461cdf337de4c43ef632c5ed919aa9259b31ec787bf7d5ac901c9e9647aa0a10d9a9835645f1e8e34d3c121d34626e6be43cab4d95871fbcefaf2448ae0d1116a994a74994749dea1cc9495987ff6c6bcb4f97686a2e614c8fe8b9b77a3e9cc042f8f0e840bd0b051da121c110414c564a29ed1144c9d8023ea1ef49812dfd1c669bfd4e124629070a5335821d1763c21c217c84a81bfcaccf244ba091226e3c60a2c109e93822cf428d040ddbe74f97f7b593f6b39ffd7cf9e8a38fddfb5271dfd45a88d312d7aee9ebc5d4e342671586414a33ed16f405ed9881c79aeaae5e4ca4423312d0a0807b0d225e34361a9e0fc28a191aa15f7cabd10ade8caae2ebf251470888f5323b71ac5bf85a352a22cfaa2ee8e0ee3ffec33f782a48a3dbd3f3cfb993b42bd05b32d8f8409fdf68dd480366238372674dc67b5d54a2aeec5470d64c740c6cd9774382dfc9804f085d09b775f0d75336c9a501a127dbfd9c3ae9c00c818e81692c2317765327d808019f1d4fafe3e27193d9f723164e5dc19b2f77fc2556c4449d0e0c36db3ae8cc3946be2aeee8c9e3ca13f208c22e63429e86d532da078d33c82ab236325123d8052b6cafcbd4e3d293b3583fd43ae1b87782662ed61cb51588a335a16e2c6702078895dcb8acb2c8e910d3ac89b03b955c5231a8980f21704e834f4cdc25c3d4d4522aa745860e350f399ea4e90885b7af0aca08c00b853c2c453ab59b0d089ee9bcffde07eae56f2e5b1ad9d979c5273c2362ad40c3882de8680dcd8bf7b4e875c1a362fee0fbdff7c8f5b276cf38fbc79a89cacfd48f8a87dfb1a1dfdb6ad35c90ca43737a61a6ad0787f96a351a166bb8cfb5b9f2a976c718896864e07cef7bdf5bbeab0f5347fcefa2817f2292d730ceca660ac8ec655fb611686cf7efe77b3bd848a13381963b6ba62b57af2cdbbfd3264b4d9fa1e9ba43dcbec01d563c693a5ff8620d3e6424bb7fffbe3b1a771ed289cd14a6afac2b790ce1131592c93a9546863fc93347f8e373176e351c9240879da4ec02df5b21672bcb5f7ba0c6c50ee319c93db87f57b4d965b4ef43adabcaa778222f5c4bf448ad33049767e1bb9141bc3214a14632e6efccffc559239976c4b42e3bd6d4dcf6b46033411cdada62476db7717431b8542a14b20c696561daf4213075745a89922e63c12fb64656c24e00acde3e8ee00e6bbe9be2c0bb6d4fd4837aea71ef8e9f4ff11e156fe0d29bfeeddffd70f9f65f7cc70763699854ba0b6a0c340876e6904f8fceaeebdd3bf7dc1b5fd0b4e948158d06456744c567bdc1f329befe8c03af6c46fce0fb3f58bea92926af87a84d990ffcd83da3a26225e630da64c4c11e1a547853f9791ec586018de953352ceea9788fd509eafdaf5a6fd141d2d818d96864c8c1c5ad7f2a8b1a5949a513b82c3c1e06efebd91a6541c3471edf1ac59a9c9d3f1a398c78ffeb154d3dd9d97cf8f0916d8167f81ac54562fe22011e1d5246c0b764172325fabff9e69bcb9e1e1dc19ee937cf0519f97ff3eb5fdbaf12b01c686465db9f23563c8660c46b793123d6e0c596977ab15ed141da04a0064d532175cc216735305e7dd9faf4637d573e6bc1c2541940074ff38580cce255edd138335e84e4ba8e6405450d845399fca29eee54203640244599f4ba4415b7c0c4134599d0c7d0e0d81ceb05f72908993e63030a23318e4382dbee4b4a57082cdfc5150339b3521d892ba72a1d8de191b6c5efabb2ddbd735b8dea0b8f483c40bdadf443e51de8d908f6beaa42bda383a23d3a219f8e862dee0b5a93d8c9d402057ad5077a53e1961a120dcba71e541979a8cc770bf2dd172cd499dae0c7afe87b2ade79e71daf71fb1915fce8d531663c63127b7843c7d93ff4e6a1adbfac460deac6f51bfe42d047da05e3f91753c52e04a678348c1d8db6c8a42341af37dfe471402a24f2284b683ccd2dc7b3c14527f3b25eb6e43958c2b175a01130b5bbc0f329ca4599d95cc90e268d0caface595d21a652a3b89e3e339804f87444781bd8c60a9333c80d756be46caf392f9543630a3809ca92b0d2dbbaf59a3ae725729fdea4bf33b29d7f0d2077a0615be8b71570fa6b7346a6ea9eec4a85082dfc1b656dafe107de7f75d208595c68d6c55547908d587826224e38e51ac4ffcbcacd602166a6ef406d528c49754070b851f6ccb288447c7c2830705615ead5a1a2b29a635b00c8d70f3af7b1a3b73762acfa1a650f4b84c43ee3fc8b1a04f3ffdcce7de580bd12b3fd439381a05238f6d64d12b7b62e3a1f3530911c93aa44f875f5d3ef9386fee5ebe9cd3e2aca77ef7fbdf2f1f7ef4a11b159b003cff8177427ce8f58de0f7d56b53a9fc5cab8cc027f8f4408f48fcb56a8c1c376fe888d227d69ba920d3a947d29b1198510d1ab6b719add9a0c23704aaf2ec63687a3a4667f1407ce0879e9cb2fffa37beeee92a65042f7617fdcd50c26569804ff06b367dee6a6326af3d81df5346a66d4ca33b90873a943731f3264d1dd0c779158786b2fa421d1f1b42639d251e3c24e76d6abeba808e023eaccb78664b07c679c6733a01b319528f2428f245d38168d7af382c75d6f99ab110fa575fb6f4fa8e1413a0e9cb1a31410f87baad3cc5b5f2251e6ea8314246b2262af780802373f28387d29a0aa907f4738b3eee681693b2c5d2ce14bf1826a0e2f08b8864b4225d08286503504cc6416b7a780ad9e010099e29140d3f23957a7c55604e375cd7698a4f99fe6934a192b1ee620a454344aff4f4eb16b8a795169113143c576204a0422195170ad914f8c10fbee787bf172fe6d57a76d6d875fbc98f7fb2bcf7defb3ef7a72665199e62a3f1e400beaf82ed707462516f5364236b1da663bffad77f5d7efbdbdf2dd7358a50a9d9c840ef04f44617769be910d84e817d3a97b68d46019cc0b48e976f7fade916dbe434543a1c7648d9c9fbe1dffe707945afb0b026ec704ebb86ec30d2d8f0834759a9002d535f4fe97876a7b9073d3f5346364bde97fd8c44ae7018d60527daf83c775d93877ec2e1c326126b2c3a00762cb10138758f2923cfd0f87254d6b7941df8741c4c852f5346435eea083e1a5e539ef36de0d44094864cfd87f389bb8eca2f7bb2674f7a1c6806a16d46eb4236ba379e8061c05d017d3bc4de35ddf96964e11405a5a9ff6430d3450a966726de61544fda3260e8389c4ad1164643299656ce62adcc6a6c140a0f414d4ae78131e4b1ac2290e37589a647bc93f4400f48e9dd7036d33f7ac29b6a54c0d81123989e4ae78f1eac9b9f194b671d12c5c3e21c5bb0765de873a6900a46ef0f025bc95ffffad7f4f9bacfd751d8d0f25c8b518dca895c2a888314867fec03c281da67aaac0f3cdd81f7964622e7883fdf07fff39fff428decb7d60d3e3ea604bf1aad15313e370ad50d4b727635c5e26ce33955102a2215968e10ef3f56437df777bf5b7efdeebb594b8903a301f6df93af38e6e50d04588b171b38a4a9dc34321a2a5f0340a7c3949151c75be7d6458f262493ce08f974c0d88b6edc61983fd4169c8e4ebbd30478d0d0d94de579d8d7bef635aff7c8133985e78e842fda7945ef979d3b7b7e7970c00e676ca7f3a0ecdfd00e6fb6fea18cc4e890745fe746d03074f20c093a646293ea395f49705e8f3a9ee87b4f8e9ef2203c7c8d81597c2ac4ce4e59edce99fc10501ad9c8165331e203477a4d5736a5982e327a6c06344413087070e5a23909c16304c9c4834305ef02a1f04347c1d0811f494e46aa3ca3c2a9bcf3c46974d63c9942e9d850f5f8ed4816fcf482e911c5d36aa5f0c777354a183691c548827ed053f91f697dc1c9084e9073decf410ad3a0ec5474732566178c57332e7b47f2aea661ca320d9d12b86c66c013dec43ded52e5a537663a041c3da9d4573482f0750034001a183acd015c78a2330b7e76dfae5dbbea9e9e171dcf698af3c1071f2cfff37ffecc8d82f6090b6f5428821c4f31e1a10fd3562a2b7ae1331cc5231bde80667b9e512fb3169db66093478d92064cc320a0096f4d5fd2b413182311f2e01d5f52be7464aa43b295d75858d3bdc417ec68b4785515998d0d1e98f797ecd86e717155101f6ca2d1f30804d9a91b9ccd7ce48e89b2a791c53712451d34b1e215c8ebd0bacd77f2e09b7fe9aa06cdb70b6fcb36de961eec6c579de037d1cab7f9afa2e6bcc4ddc81841d2b0428213d9e1f29451958942a291b1d8c679e9cdc14da1c5a149e3a6b61783465094c6c436ae31bb40bc255d6fcfaaf0a98c4c07983ad1b0d84c60670fa7da69e691c2408faed4f0c424178647128d08faa312b10e62c4618a7349df8af4a27a49cec1bda7b770995a622f958aef60e79b7199aeb19b86fae4e1401a8c25204419f0a51270aa9d531154164e89d3bbef6be4ffad46118e53612db4d8d55f0a833ef0a262795aa4290a0d996fce3de933216a62400169f4d4cc822ffbfc915e78e42031cf9268ec8c94e8ca94934a681a74542749e7426363f380914c6f7f49977cfd1b744c6ff119f2193dd01fd8537a72690f0e27e069684c0fa91304377675102f5ebcb8683fd28f0dd8c0819632e1a1355ffd4663fa8abe60e7357d38fc4ba3e4040a78f0a20c2957976dccb42d3db2d291f9d517d53b8ac0cfd7a40f0d0fdfc55f22c44ecac6e5459a924be8c676b281e133996efb3d3b3823ddb5cbbaa7b23c54797547a11a607ee6262183afe0ab940d94b229f26bba2832346c6add51a81b194ecbc98f54f4b922b4616117a561630350ce20a922279162ad4183c5598c1c14de175acc7afaa75d3f16b68c54f4b607ec2ab94714a118d2d83d952a01eda48c2eadf3ae4eac9ff7b63beb291efc32dfa717e5a5c04b9ae261cfc73a4bd8a70e52c03923c70f22303da600e30f694dc7a08fe511b53adb7ebec3eb23dffdcbbff41a81e353f4ae9febfd2ab6f1e92468605b9a76e598526ffb3322b8e8dc01a0270d8f351bbec5bcf69ca2d53b11d108c2b129553cd62bbb6a603420fc92674b97bc3b4865d4d86cd93082270d8c005f74614ac85a8a0d0497a1f259a331bab0bb4719d1f8f02d534bf0d910e946b6ab064583611dc71d1ace34f2703d5fb043fcaa7ccea97abe0b440fc3d9b0b116dd6971ef2508fe5d3f32cd1d177621f3e840768a9aed75ea0cdbff34e074b2619aba78a2a352167ce78616ec15e634e52a393bda65dd93ce4ff428e3d8f52fd87d9d4b2771d14970cbe08e975b1e746e6438b3ad6f2614869f97f10c8146e6e9229b1fd3b0292268610b6f46a93ce44408ec6320ec11c0f48ce9deefd5cb7ff0e187de8e65947aa0c6f6443de681b64ea3b01a93863da6555b5beca0a15f2aa595b730ea9e7a4e5e23d75487dd291ef252c06cc773e89534a3178d8e290b5317f8b203481e05c8c36076ad7818cdda8a918c5ef2f048dfff20bb6d47d92289b283447a301e5cbffdf63b524d955815c8531e6573aa831d3c8f08350233225051a9b054c414069b199aa6490ff4a1f2e3a7c8c8153cfef033be8707bb845907d6036235324e607070d80fb3858f6c1a3c0d906784349096c9ac80d902a31e233032910a3e23078d822960b9d91b3bfc94110fee79e6071c9a4bea54bea787ec9c27c406a6bd744ef637f546be453e3e6cfd1599422a277a51ae7c78d0ce8c893712789c808e7424ccb650923a4403e3910c4b8a1dc94921b5154852406814b5bc96d13ed84489fd1ace976d465a1ee0cb7fc7aa17f64118da47a13340797644dd03b3305d22cfd8bd5528642b1567445d0916131c45453c3eaacd0ff5244c51c284ca16e673038375e7136fae14f475f5eefffd7ffc0fbde9fbbe17f03db7a430bc5692ccde5c88e399a6a151763c9946d0e33257670d4185e5c128bd1a151b38fa51c9bcf1a1866847c18082a493d01f158a0ac1da820680f9ec465289e9290f9ebd2e1ebd2e8b7c37709451601ac6e8c07753a21c793402dc818e5d5979e3181fe10f2a3795063c4f95c487691a15945effb3cff48c4a7cbceb293eed43f4254e1e1dc0179a2ed1313082d03970c2831189338a6c24d0f89956bea687b6c8fae52f7fb5fc4a1fe2f0e631063c7803fa157d4f624627be5c87067fd9079a29a7b25a159e9325795ee62350f8525621ffdbdffeb6f8b22348f9a953943d71b5b4960d6cc8e014456d031d51ece2b14b0e09e44b76345557c3666ac8490f7615f9fa02b6eb0f6b36c3948e453beb491e4a63071b2f04fb870882b85847e26b687f366eb97445956edbe2c7179feee8ade943d98d1194ad69c577e3e0bb58cf7522fc5779c480ada7f0ed1983958392d5c8f4247e57671641c6280a17c350304111d3a20c1127867094c037802900a6159ee608d6cfa8e04da01258696d9f228042e3cc1d15889e9ea9df15352aa6287cfc32a3b69c6930342a1a2ac1f2905b3cb167d5971130a7cf2f6b9a46a3e421a7a44b3ecfca5897691b5d769e3bcfe973b810304071f1b53395760ec20c76ca783472be45977508d3a4bc9e724515ff0d77046d2f3cd1925198ce82ddc2438d3a5bc734eef21b31c9a031219b87eb3c48a737a7f37ba807d3bccc49e524fce33ffe83e5724a855187e92f0fc67ff9cb7f358f1da9a97aea35220d079ff3209886c2687cc90fa5fb281630cd6234b2f01098519e692ab3014a8c06bbadaf0d88fd16ef066f7f5317e4670e99ebea4c9fb3d468c8333f1eb43322335ad1a0bfd08f4bdcd6e918a6d94c63e189103a5cd696ed337cc19a994e82e93523b15185ebba6332b40b8b2e2fa7292be0ba37bf21082ec85307b9c3ba547c9fe94b8b64106684376c9398e8cd72a4c37b5386bea67b151cf4ba225095961e8ac2a7005897f12e103434a8a12c69e46f30409f4d088d86c2a7e7264e01d3efe1487077d4a0999ab23dec13089afa5d53af72f5da7a909663493d52b951212226781ae828ba57c15221cac2e8ab1cca1f1e8c649c57e43100012b9ee9c1305faac933b8f68df3f093cdc16e743689650feb2d37bcbfaa9f63fd8fffe1ff566f9d93f0d8cde60ba31ca3577b8b2ac8260b534cec7ea046468fcda2db7e2e4c6cddd3a8eb759f747ff7dddfb8f17ca667837ecb5815f7ed77ded1c992b73d3a517604de18a073a23361c3c3be107f3662580753a674529847e7e3738ceac0e8b418e1c9e8af00e0991bf6f02a94772ef125a3a3e8f8c3a67ef64843a3e11ee80d6e1a0d23388d9a06c58f02ded0ba95919493334ff5b03ef65a657796d48f7632baa178fbe4c9919e3b7aed9e7519b86b5955799b28fcfa8a9e8dc77d942108c890099ccff5112bf9ecc9a71f697a5aafbe14b277a937089b7beea9f37323d648d628c94caa15b1db285c3532d6473898d309f4f8eecd4020e8de51947d8e174a199e42a742f1eb8a3e8caa2c2a1053c0d735b77febadb7b213a591807381544a0a1c4782572e7445a49176e8690882a8a47d0284d197e76c7cd5186b8d4c7d792cc241d4173d3d826f1e78b2f9a12d6b153ea3dba17470615300928c8d3428af3b318850b6195e5e801feb133f87120aa3013e01d53a9b344d131237788faa2f68aa7a4fe98cb4f8842919d3605e53e18b6dd8c16454fbd9cf7eb6fcd33ffdb3379062fba2d1ec96b6b7ef08ef0dfb0c7dd11f3df8fe44a69a04ca974e849184cacfbad565260264d22879cb8063635d1798ca71048ae7a57bda85c3041a657790e0513ff09f37b5981168dacd6317a67e6c34d199b1ae635d851fa0895fd425aa83c53f5d7794e5068ca0c6e32c29b3a80b175ff0d77ae3674a247e5514faa95cb07f0e6dcb0c339168521ae92476c4ffac66160f350b91c1e60fdb2ab126d964a354eb89eec8d6cdd7d1c89c3e71f10965194225a7521e1ce45b9558bb64fd140233c5d2721ad016987884e240d640ac57684034b2368fc27de7edb797bff9e15fbbb77683920fed181bd815544ed5e884e588d415b1296015a05fe1d7e602af493085e224c8c1b343f7f0dffdee5f6a24ad673de2c1daa5d3d183caa7b3883a9dc1f3322a0276db633615efe1be80e2f4c0326e081267084df3fbddf84418f60dd33407abcf4649783153a0217c5deba93c4fbaa2d1885761f44b282a6c2a3cfec928b8e351e825ed803102f11c8b86846f9986b1dd4ec74243206c1d65edc47494b709d00fdf629b7fc8efd61dc9600a180be0e535a21ada2d358ab687635f8c98ac9fd8ea670a070f463bde3d332f8d54376fdef0ee2af7dbf23d8d93e36ef0c135f047169d365e347f6b5ae5ab3878f806bfb05460c641a7853f587b737747a0a92d332d7874e314b5e9ed5adcdb3e2f198dd7728d221cea917175e3f7d0ce4a1eebb32dcd6a60e8cd97b8489c56c6cdafd85b17f8305b6a45d63559634d77f060826398c61defe7dd25767534c0380f1bc6f706dab28981a3565fb148a67172b29dc39fea76ad14d3001e003f53e3c50064d2dbf9f4b0e8bab7b441ca031f1dd848a14766dd9107d69fe7171f99db6b5dc57a85efb960cac434f3adb7def274d56b06f1a1e252808c243936c66f631dfa199a473216ed79a45546c516b9bcd231cb3e278a89fac4bf440a0f9d4d434e2a13330276087b1ac554ea1b6fbdbdfce8dffd3b3fabe26bb07b14c7ee54d21436158be9345f0540e5768591601a0295fac953358445fe2d5f62271d1b0d0f991d7884c28ba8cf9eade7186900940f53f4f7f8b21ca687251fdeec0e33b3a133e3b40a87963fd51703f1b88293241c18e61d31cacfe68b969196ef2c697dda169ce5cd0cca589d1e0fe4e938783b9a5de2d7b491734d23f0558dde3c7aa153e42135e5859ecdc7f68b3b777ca57f07a7ab701a27399d9fbbfd4bf9b8c844ac29a37f285075e340ebdf63f60808f492ca6e398a043eae95b602c185e95893ad7852948410cd4cc6e3201c0b2d9583ad766b1489216d7920f93ff4c9443128347f17806f5ef28150f146063d22bd6f8efb3cf0034e8f56ac4b6020230f84c3c8c74e17dbcb34aa1bd7f53b5a7a9ec1348646c543611a1feb842e50d626ec62ddd7e8c64154f4f008a2ae26a7c9397570d11b1e8842cbc7aa24f7ef6bad207e4c3113a4ab22e8efc66e7b0218dd88f44d2153d0ac33695afab3f150664ac5b3c0dfe931c6effff07bebce6e2615e8473ffa7b8f608c2496018568998621bc79efee69ab5d23df353504462e2a1d2320a73fd82ddcf5668435b582545c7a7ed685c88e6f3807cb97ebdcf5684443b49eb290f38b8c7cac216958e0d3b1ddd0e8f4fffdd33f7914e2351e660994093ea70c590777fd62064043c0379407eb337810b0c3a399ea141d20fee73926bba33c0ee017625852d011f30641463fec49c09dc8e39e907ab4a612737e91b53f1b67be9b0d97c6953f7dc44abe7ca2c3da877a306fbb8c504257e1e5b795237e7433103fd0c6741125ece4be5b262ed29f9cc5f328d6614c0f78ed250ecb346536c6a2c498ca6549cab45a186010d3343dd35245628ecda29866cd733876cc383b78f88a8e1ef17c0c1239f3fefd877aaef6813f376ee881b57a501a243d312320c1052add1183be716a0a134b1931c0b7de7ef6c6d485699a762cf57c899e183c7cc08840e5f3ee95d64ae935d3702909a1e015cbc5996d17d36a0295eea9461336153ce55125071f3ad6b3740a3ff9e94f7d5e11627a7aa6b41c2246f6c58b782c05e5ca68be9c4ad1482bde4f8403cd5bdf78cb8f2f38a5410363c795a9251b2909f0c908c1ba8ec6eb464667a03f7cce48c85a8db55f074650463eca298d0cd71c7be302dd79ce882dd487ac37b341e6ed6dca00a708812364945f2a5d960aacff98ead2a8d8a964adc8cf26b13ea6a3a143a05ce04d400e65967bfc62ef4b06233a01bfcc21560bcb8533e73c1f4fbd6fb828511def70c44aeb607ecbecd0cb1a97a0115de428f45c50198f3541f870fd92e9a2d90c16f4caf426191534aaa891611830374c3453c8356456498ab42ae8443e9586c2e3dc1b5bccf484140ecf7a986ab0dbc40edfee5e5e65809e2d6b16fa3fff5fffcb8d124e2d8b8a9d9304e924ec70324b3037a624341876d4a8f8bcd345a0203916c4763b8f13faf4015bd67775629dd18cd3ea527904ec70900ceb20003e78a60ac5712a64707099ef566403e01b6fbdb57ce7db7fe1ca030195d223399b307e0ea78aaacac03a97868d0f5eb946e79046e54e4d0d0bdee4f38229df2bcf691536427ef083ef7b3dc76846796038a3c61af20c8b46c463107e50041ff1de145341bec99786c43aa7cb934ace0f40f0aa098d0afbecc7aacc1cefca668bca017ee42bcf75a170910fce194d7b792cc0e38cd77504ed158d563c3467f4e23b4e58a35b77f91f8f5a9678f81d32411475a031a4b3cbccc7cb05f98cfc9e3eae8d0aa22aa0a2b76e40abe1a5715176cd7f5419ebb0a58db23dad55bd95ff402fec82c8ffc077e91b170ef087a74c96e4926dd6b5bbd802232e48e605ae8da3d760872f67189f3ecb14a17b6ec4d939ba87179cc401a128a63fe0702645e3622b9ab9372357f23897a6b78a359ad19b337d21e058a62d2c80596c53706ef0e2d70582284d48a65e04c54d2e9cf4749c558437a3005be504e4d273f24531ec3e3e7aa4675462d6159e9d3ee28ce2fe3a0398997564b70c9eff7caa93dbbcfaf29bdfe8fb15e9283442b37e6086c42fa4f8a1a9e8b18143b879de946750da9b70c5e7951d4e32dcd128422562b466778e87b3eccedd549c864b6f49a36126b0fd1dbdc9cdc825477b5a29bbf0b74702fb9e22d7e6873a367e4a960ae90d27e9820c1a2e1fe269a41ae1d5f839dfc914930605df74b46b45c77febba5912e44bd654941ba3120d89a91f3bc6d7b4be62ed4b1e233b75a9cb1c97a22f9daca20ee4019f031d03b6d32170fa848e8cef18e124cf5ffdd55f2d6fe819648f6ca6a59c261eabbc09380b203e6571c46a5be57456ebc1fdeb3a30ad511f84e6d30a9a440207dc1524952f0d4fd34522274320384ee6cb60be909206c58980fd637e88225f5b1626ae752e48f8343f78200a07b63020c098aff37c8a1d4694a3e745168d8b91209520238dd05d389ca9a3e79b17eee4115a0f7a55e4255443202d1bd19946c68896354fe828242a0595965184ed7176e4f245a3f74c4743c4122969fd89b7535b1ad3d62f348dfd54cfad08d8c8ae1a274898aab27b482f47a7c1c602273c68ec542e64339af25afdcd9b377cecccaff188df6dedfeb961798a97d181911b9eac89eec8a62baac49c25540f64bda4a6d5f5a6021540597454d8cd5410ff6208b6b2c6e58815d376f2088cace844a3a4f3d9df67e19fd1c53305c569c4e0bf709e470cfa4e0ee1f20b31fc842da3624f37c1e1f9273a500e08a6bc88765d1110ef8e30c31b4807fcdb77dff534fbe38f3e591eaa736656f29a46737edd930f5ff494baa0e50202ec884d39945bf31f6568bd22895d41374ef993b7a5d900d956bd3f927f9adf4c87abb1690ecd1f18f1b1269b91a8f0588de1907b4d4623532f04a0cf31364d988299ca47ba15b1781b4b05809ceddb1c2ea52079b7ea48eb112a0467ec1ef1b5d56a6ce141c9e498128b7636217a8d001cfd3a4407a5cab1c0e3d060d053731cc7e712d533bbd7969ddcd992bea8cf75a646f5c70e1d1b2c547064c3d76ea902897db6ce0d87af2960b380a92053476fae88d723c944671ee8d278e924d87040263d302f80b2e0a762d2d373aef39ffff9ff972e37cd1739d974ea062e7b6438959d9196d1ce5335f9111b197558333102715a068ff3c7260f159f1185a9b0b0ac23f6ddd1e849637379d86f9c63d48ea41a3e1d0c6f95d3d1f2109d87da79eea68aad29208f1ab09b236e79d8ae236d1ea9dcaa50d58d5967a3148b368e482702feb62f476126829b5394c1c35efcf8d1071f79b387ce8a0ffa6787b4bf5a0e190a27ea01a00ea9172da7e4d3d10f9dd4d18a9eadfc3d751ebb9a711deef376836842d6b7b08c8a8ebb1ea2bb3aa1ce1c8dac058fca5a0251d60255193d1717315facc3c81047c0ac2437db3270861a24809d5a8545e5a5400efd3d1b1486463355401c8753d504ac05d322d64e8c7e37b76eba01324573434466d9634d66a164d1852a037e8c922cf41975b0057c8f2c7222bdfc9e7439d0c684262f9eee711c8946492502cfa16c4bc206f9d40a0d84293095f9c9137d1fa1fc85efb087d30d1cd162a4bcaef389ecd23133f8fef7bfbfbcfdf63734e561b19fe74e7ca7871f04ebfb46a8d4dec491306ca531c52fd2fb585fe8233ff1c098af02672794d7766eded0035f75526fbff3cef2ddef7ed78d848d033f8b5385619afab1bedeceb307351c7664d9a9e5db8269e8e88c631881ae6aaaf4f5af7d43bee7d75618a5b4ae7afd356fefb3817156a31c6737d19d697c87e89aeecaecd43ba5acf038f1dcc177c5961bbbfe59b870069e68c161ed86ee8cf8747e70b07f35c25dd763048e9b7527e1cc13f520e8a68a2e558ec8756016808f83e8fb961ad919f9e08ca6becfeede52e7d587acc12a3a62156d1bcd258c94396d7c8010fe9bc369e052404ea462b0a660986634a3d0e55dd3c529151db7e2a9742b001e159635168dec8c46471ea812e0e7cd0f35061a31533911a621a8c7071f5a7aec8418eb82428d889bcc0716209593a922bd211b1b340abc43e5c836b29e9749dea1a746d528d5c0ee6b5a82bc9e4ab9508a672921b14c9d725ef1554d972e68514f65a071727fa45ef0bffc97ff47df13f289f8e9ed654d11bff6f537f5ed55ef882ecf7da8f4b892118e11676beb4337a8aebaeee8e4ff735a53bd7849affe6bbac9a60c53befffa5fff5fffca0ad3e167f225be6523e71d35b49eea6e6b4715fff15017417e8dc3ee39d6e607eff0e93b0ee5a3acb333c2bfa1f5d47ffacfff59d66df96be018c578258732c0abdea955047740eb027039505fa05aebc6a8cc2e0e2e9da7a842d73ff3501a7cbb19fefaa333a623649df8e1873aeea4d11a2e3c5bbdade92e9d21a761e8943aa4f2470efc57199137743241eb6305a3bb64f2e53a34b4471fbdaf1fa4a0de51dac1812c32cc203a3b3fbc5afe18c982d6c8ada8180ad37f4c17f4fc854278aa699dbf235f8ed56acdcac3304aaf0a6cf2546aaa9c54680a9d0a41c523e04cd642ac63387e43a19a9b989fd728063ebd26953eb2a4e770deea4c78911f71d8009abe0d583d3c85c1a98e178f9902062fa390a63beab95817f2b85e75de5328366698c6d2c8d08f29e30888745a27d895cfd714c08b8266278f06c393227e6ce2673ffb850fb5b2e867f38143ae1c6be20405a3aa7a1833e33511b6b819bdfba4040630cd7bfbedb796b7de62dbfeaad740c8e28de89ffffc171e9d281b1f7f53b9786dab1d49e8d4aad430f2e88486ef29a0fc80f28c2af884d10cbfa29b1bbcf01919783114ff753da0a27a34b5df712199c91743fb68d349ff9bb23b7fbbac28f245ff52149338d08202524a15a302ceb6daf674cff39cf39c9fcfbd7f6d9f73baafda628bd84e3880400156c920a808084551c3fd7ebeb172ef5d257aefcdaafdaeb57265464646464446460e4b6ce293be69e73119661a61da286f02632704b947577105ee6f94215c4c4e5b644ec8aa90623d9c3c75b28a121e0d5b1b815dbfc2f63c3ce1612b604bbef278d3b75370f697856e51ee37de968f6e646ef29ab4953069f6f82ebc9988fee193a69bcaedc7647b0406cc241eac65ae574f23a611ae092332b978de2c8958c428e8fc59cf07941a0222745e420a4374694cb4537710275ee33115eb61cc18e128036a7818dfe86dd686c26e1f997ad4f66d5dfc49ba433a572894172cc1c6d83c5356735c8a46d72be77505c3bc0d21b66e511cdcdfaedb3c1b48535f2b302656130dccfeadd4cdaa985bd3181c1a257660a8abb9a2b50adf172831b5faacc9774ae5e69b033b71aa8081ba113226aca98b96965e48fd4f9dbae7e8f39fff5cc771ed4d9241cf645c74f6ecd9e24d09a9b77a58d06bfe8c80716cf8e9e12c02e638697d421738d856224e398d1f726ef78471138ac684027a8c68a23258d3221acaa08a88ed5d639a69e2dd2669f3358b07ed33cab0b4cf9f8e69124f98281b968d71a3f9d52f7de98b1dc7daca636b0ee16befbd4c5640421be05b9802129630b8ef30628bf7bc25deeb8766090c530c514cd7862e17de7a63ea30e000693670b5b55f23aa2f53ff3efe15177e0bdeca862e0098922616b8a8998cd5dc8027f4efdcb6422d38f1ab7245627bc624bc7ae64b64d1885cc29637d1ac7a94ae76cf5609b460a66056e3322e6799a68a32efc767ab8cc46e6198177c662e7b1e7c7362d7665c8310c62b4c1163a16319df2c7cc7299045b4496f3217b312844178c0c3a14c9547c2734de6e02401435af860022b346cd0c4e89752683d8fc1458fa37719f773a63732a6c3384c4293ee48eba7f78537b868872911e6031118ebf9b4030f1c65427b3b70c8c25c0e1d8e164e1d5300c663a633169dc033116eac6a0a012efb774a183396708a376ea6388c5d5b5fc286068987a7e092c72be0f4c5557f9a46fa6454ffc24fbd6cdc44fb39ccd51ad4df677ef0b52a48caee739ffb5ccced4fd5ba185c072fc30dcfc5452b6cf787c50e9ed33687f1733f7598fca945405c8ad2ba367c7a7d8e5238ffbb38c6526f419a150607915b8c6bcadec8b1efc95606d7c53473bfbd4926ae633d90462664b4b1c676a0665eb7e0a9c43c17d256304486f813213d6f6157e4c7d4aa232544b2a2a15ec0ac3be4913b76cd7c45924d6eb06d227b35ceaa55610398d0cae75669bbcaab7d0404033a7b1163d7348ac92a9dfa308d08bd3cd245268b937d4f75ed47a376a538ea045cebd2fbad82caf68f164b98fa629c6cdd8900502684d95a4b34630918eff919a3e981403547f5c10fc1253babf3c1704e18b00827e5623da6f7049e86009b0280bfe3c6a7ed2fb7177bfc078f1ffdec173f4b1904282678ca1e8711a534bdfbd068364212eabb4e9c28f3a281809e96b459f5d0c5d31144a6a576f265d04ed607bfa1c2b4014accff895d0c599a6cede41ead0aff62f04a6fc542315430d9fe9bf4ccd644f653b6711871f2981fbcfb5426f71ffc74153e7a99b42eaca978716ee5c09fe2274e5db63bf19347c4c442abe969ddc41df21157fe4df1001b975d8a221bb80bda005d75dc9e72d9843d8077f3640bd17de1937cb9f37115130113301dad5fd4f07a88b8d2062959607b18d6f3e07ef8a6e398994fb929036f1a3e5a329ac2aa0fc43e9ff31ecdcd2da2d0ca16c596bf52eeeaf209caaa72f14f2918de7defd2385af442ae7a326e6fb8d38ad211b2717ee47c87944753b31b301a812468f03a1e81176a21ba51a846f13f654d55f31084a72124dae6f9d20beb75fef4d6b882c166a6e93d30bf5e0e1cc24699381b83eb9ef70f23516acc68c262f1ac3a633c66212550a10b02d3d8f16abef36e3f92c131b296faa8a73644b7524c7a080659c722e8e9089271d9444719c44964f5ca934f3e79f4fcf3cf770298596937fa3ffef33fd55ced17674a0b24999b5e738b2e94f3d06426cc295475b6eb599dccf739cfdf84fbabafda0d9d6d46198a54d037185586a18dfabe9cd3b43e79223bc0a35884906dccd75c4738d40b1e724d58f1c3135b642ec35b731d4887b9129f7fc7622a5e9ff9b87ec32c4206686ad5b25a4a9fa7ecc2dfb5c3c47559d5bec87527eb410004b10899ee3326114dccf9a147d0bb2976aa75983719f3b8f161dfefa0069e6545f60661764e80a46e78f7dcf9084316fca6c13f90de4e5076d73cc6a1c009a19102a2445a25caef1e41990e133f84e61058733ce6737685019e60bd1fc6366fc51941e0197e1584985e18a39eba9da60bdc483b37bb864087f5cd80e331f98cade0836e84853231a6c4507d11f8ccd0d9d66f8ecaaaf9a9a7d519bc927a3e0a8750e8c98d29ad72a0e0089bb1ac35975cf84ec2121613b5eeda2cffa20327bef40ab67959dc0ed2ab1f81674aea155750af97c2d48f6735cb4b313fcbeca9f31fafb7eac20a9477d21e719680b565822f45b8141565a99765a110121b37e1de3333b372c3d6a259749e3c104f5ee6a385ce680c5f8ac22bcbcbcc8b69234bbf08a22106fcdd6ba3a524a6a283d5a24b40ec02fe398c2fdfa494c5c92d30a92f87bfbb5b3ac389775f4ffb45e14a3b905d27c70ef0ba8177fe5de15d3c2c50ba36c6402a20c43326abd63d72445cbaf934f8e57c89628730c0101001465169697dde3545a2088e06350736639249626de49fd27bd074b744a8162cc2c5a4337027640b5640ed82b28d158ea5e18d6f78d7ac5cb8ed368b513fdef10b0785255dad5f72caa35e1d97253d41a868063041676ef14c9a709e90ba250fe6a1d569636306cc4ef3df7bdfbd47f767bda2e563c527e3990fa70736a654978ea79c601506e5885907e8b4370ac5d493096845bd01bdf92147cf7188bc9eb9bbc77ff08330b853929de59f3322834e7ba98de683e39ee670f06b53e4a5965934733f38e5901e66a53162ca034fc0b068ce5bca4c9476d58150ce0a1ae3d5715e60f4cb31ad31be33fb396f5ec9dc20017d2de342b4b25e12ee977a049e52465951e075a814757f4621103096049c5920a61b5e7bcdf86826a83bde0c6cb83ef2c8235dce557e6a33ad9a2a67ea3a77f3575d4a9b83772c15b990ac77a99c03766e081f9dfbcd99e2122278b90b8b2e0b1600044cfc1542b6cb919b16de6448b0cfc0ddccc328969b9d0601b08522ca96379784113169a7913d6ba4794b683520e6211076d5229c8f9933d3349231444f8d0aace5a0e0fcd00378079486e17eff40bc744c2dcc6c6577852a0245788c5db8c599bbf55cc1ae541c642c9c25ec9da35af826cd720a703a94f9da28d6ed5da8fbfc7b8f7defe8d9a79fade70b83d5fc0d9cbb4fde1d6199359255263175f564d3a834619c1f11108cfd46be30f3d69f6ea989a4775b2b30befad5af76113006b71f0e5d9ef8e9cfdacb1b9fe9458fe7376da53ee8da3f6eda264d937a1bffc28d47574fce2c3bf3eb33a95f164c87b9092b989658dd75d75ded9dc0ba2ef4e2d1b379f4cc99a48f296d88a0ad98d126ec3ff4c10ff7594ff546056014ce6fb29e93f2e1151cdc369a87e4f5566707381e18fe304e0cff444059112b503ca67b3e74e338c9ec10d7b6a74f9f3efab77ffbbf6b66ea4de17ac71d77a6cde750a5e3d746c1a185e6ddc0a113e69b161f5c7642b115a8e879efc6dde439166beb7aca277ce4f3ceab424ddf247b619db881a2ec0ad99505a9781224a5ea6ff815859a473be74734721a1dd111a58ee35ce56da32b690b6ea748ef440e42d259be73f3cd364ec6048cd03231682d7bbf0cf235fe98a33341eae0945bd32b313bac94b0994f4fe5645aee6a4cc499c2b4b44a8099a52161d3f1639109f9174e1b2d1c2d66d5b9033897392a0d5ceaa088d05300e1ecd601ee180d13319bf484cc421a5c6fc0ed6ffc3861beb8e96b9a4c418c5c633475a52c7ef18b5f76fca4a7a0ed31bb0383fef11fffe1e8c4271d473d6bf22cc9523fcb9aecfa86836035bd3624cc33fe51d7d02bcf7af0fb1f78e0e8ee7c6082ab9fa0518c4e1bb612e5cdd0bc4c9dbae95dba8e31f5620236a40cfbba46f9e8ad2ed624a3b898d82fe6bc0ec2e7401c38d9dfa6a7610108685fbc36654068e0ed4729a9025cfdb8eba565f23105f5e6cce65b0956dad7589449af4d9e78e2898ce1ced6d4d48359aa472973de505e63da1785963577f35759ef1756fc7c1526699a2c08c29d191a21bb364afcbd2817028cfa83bf9b3d44710df2e7a142b61a4bd9bb4cde3767f5415f785e441b22f98672e6ca8ab47792ad12e679fa2e49e42ed6dbdf49cbd1402068abcb6f4d6a95a5fdde78c3c6c99c8e950a42056cbd1e17eea953a7620ae6bcc234841e88e3423abd1e65d0a17de018f8d784e9c07094002d0a4b355bef357019232812eab57ef2724c1abd1833c7140053197e7a4fe55a6d7ec38de6a83053ce4189d26146713fd3fecbfc910fa31210828508cc205bfa7ff8c71f1647e5f334c25f3e6b13f534eaa40e890af3e96d3fdc670dd9ba86c1d051a09404b42264cafccc673e7374fffdf755796072eef15b43476361f5521f3f3d82f18ef11365304d19218bc9ca93f8e978f54cba9b9fd263d8dce9fcccc7bef7bd0a1833d84f2ba3a11e92d004f45686de6aeed14bf00c579646cde2bb3ed11387294cb462997847f1b1424c23516e567758c676eeed7cf236de6d43046d669ca71e1d57aa400bcc25ffca9b9e13dcabf32ee4793877c5cdf33604b7ce6c4e178e2bdf37dc7a586ff24f9e40b90ad67af63ede45054a33485c5170c9b5104aea24c4a09c086cde7eeda5deaf6d2d2040098591fb85ee2aa32f9b60eeac15a385b8b7798a2c8f9109f39927d323b862e61228d92c447d284c23b411d9f1296b119056b664c840bc75226e840e3eea19663596e2b5eb5ccc669ed937e543e667cf9847627e4c5a70de49431a9cf3d8dd94f3f80435a51c300206a73d95e74a38f446172fe5409b94adfe98bba671bebb659586b470bd94759bf280a70e4ce23252944bcdc6300c27ca8439d6fb1361c4db33df85f1eeb8e3e311804fd41c7cf699d3473ffce18f536e7afff4ace8cf41609e8c02803525a00d091f85e5acc7b65770391787934d9d4c58f365c1bac5322f3ffd994f1f9dcc3a4bc2b38ed9e6ac60d6c35b5d944111b42d9a93d08f79e951fcfa85ba93026dc24b3ee8f18ffff48ffda4939e1bfe33373738484f80f5dad89f09cb7a71fcc1c0baa665a119c5615180366f2f1de216a7e0989b96db3a0f061b1e838f2485979ba6ccb3b45d209ddef5c628973f3dff6ca634324cda6049df7c037a2c9eadaef26edec511b055e622d2212244068cce9545308c6f30a91dc088cd74a39df679609b1cb9c06534c92a2157c8e73d41b1f5e34399fb21b8343962321df5209d1f51c9aa14cc1a21dfb4f65021702238135250feeba7aa41734564da9d67ae671486f92daa35587640a65ec5094f9885a9d7b14299651a577e9ad3940273aa66d346407bc4ac14b939b8bf9e2f6fae006ff33b9c200450dd11417a7bd7ea9adf1c3718017d4a0df7f9273dad0c37789933132423d80f3ffc48058bb6860fafa98501cafb5936b6befd769c51fea5173765c19ce5a4a21cb5adde8839faf18fdd7ef4d4b1a79b4e6be9cd8db128944b9fa4009974a17d68ae5c9e60f88913280e5b5cc07a353dc8e29b75a52ca5bf318a49ef6b425edd9995f3bde9e10dbdb47f1c5ac6773ab9b13e4295cc95a60976b0bb4a2702a8de7ad4a79f79a6f56e9949b8685fd3337c2a7ee81b847737457ff767f1ecc21ba59b34f0d05c6db11f57fe0d190f76b7b47160db6d701b7a4f7a80b7576e97e363819a0c0a53f02ab448246e103657166ddb0aac951f59701b4c1063c2e487de82d1129a6087590427da2f0d6ffe4b0f457bbd6b509924cc504c6d699379a2d1c4035d031497242cfc30816bf1d453457b122cfba66835939b2f6610cefbc73cc3b83c7bd254d305acfc1a90701626260d93202e067dd34ee9085acfd048fd55a5bd70342a4fe51fb26ade7812f2948f71099317732e8aeb01ea8449cf30ded1a98fbfe09532f9e38ab9ea88c87eb2db3f7e477b100da927b1678bb90657422a039ceb1d0d2d99aa83fb98dd7f8cb0eacdf448e8eacc8a71460c1d99f280a09f718d7119daa89f36849c9a05a55cc7e40be553e6b1b45d162b67acf2f4e5086bdead7662e232ff4e9d3cd9435d79748d9f29bb1ffde847dd71ad97833707966fa7b126c6e9620c1a8cf48a5466d0839b76673e5386c6e4140b3c26c070be5863899885128e1ddcbdde526dc0d653afd3de5744e521705b6e2eab880c69aef3418a2882f7e215460da5ce9f29aa3c282e61dd975b9a3029dfaf3009a711c01aa6c68cb422b02aaa279b92401a68b929bca221aa385f8575b08f32acfd4fd3196caff92f04b5fd62c642391a3cda375e6f400b778d75682ce336ab056831e3219af2e57c0cf0c5b88d392608abd5da7a49f5d1304c48bd68baf222e7e21da153f68468f36c385ce31de5280fa3a3151ae84d3838c8d7083fa1cc06d1e0de6f8045d39759f29e1964ad2185f2c7d7e7a05125c9bb680fbb20529adb98a82ec3f0d91b86cb43df317da5e3f418e70166e414f059a2577e3bdb8102a6b880e163851fb8f9d5f68eaf6463e9995f9fade2199c37a51ae8a613385fcc0fce3ac68d91e059224d9be5b18112e1f163e6d6d39c58e908a8a317fee9fff8e7d6b90a214ae19dec17b45b9a62ad621aaeeafc1c210343b9e86c61b6674a8b62c40b70e3f0783113d77a7a4316a1744bb984181c96877165b0e9fba609413c69ff7dad9217a1c4ab5f42793e57b64c63fc49927e90224ad582e173bf7d29fc64fa2a30573ee973dffc0730e3f8c0b40a990272b32bd47ddfcf4d5300826033b09d951f0851cfdd018c55f020b9e04f6520dc90972a4c80b8e569facb9763e347b3d2c4e660b885119c60ab2937f67bc65421a6f92b6388d7b2a68c6dce2ce2a1239c3c717a402510a62554537062717ee0112ae666f1dfea66acc37ce50da48dad0dbc3333fec6428e225884b564aa265b16322f26434bce947e0925f8497b5d26557353a1e44a77604c4de3a4f31ed9d001ad90461439779c9d71a9ba62bc922d2ffd2354a944e1a0f5b5397f5bafc274eb969df444e0a9df99ec24fee3fffc9f55468e379875a11ca533ef5978497c3934e99aca3028a636dedc0d0152af55ef801dbc5382b663325292a60556a018580078053db58579315e580e2b9f55b200014c66a79e47fbf152ea75adcab16671e2a66df5b0f02248f2e92dd7d0c13385c1b4b727cfe4bc769bce60b05a3ca9be2bc8b7c2badfa5f3aa8de226b9727f0d9331f3add73e9b5ded7f1a577e9379b701dae71f9a1d57e07ab90a5bd755a8c62c5eaef9e945a682bec29915f921a60a12bcc1796016fdfc5168ef95a4b05dc53c8c27ea43b1b169b7d7d250cac5f4c6631a82e0804fbb3af7ef95f452560c68008374ab1410beaeecad3e70b40953d9c6592a5081daca2608f0c0da7ad01b33ae21540e7a99b996aca80ff37025f3a2d1cc845518ba0c73607e83708cb47a3aef7db75a43eb5dcfbf7bbc6618f7f2e9d3cf45cb8ea901a7d5200beec046a6304c18af9eca281a4c8c61e599b4b90b4dd49992b369d15a3f0ae6dad439fd5bd3f9b37a812a933c13f09a755b928583f7764c8381e60eea31b532ef47436f0db8a1619ac0369c116e42d63252fe7bf9d4916f757366114230ca6bf104727219cb9e39336378752694d62b7eeb9bdf6c5beb7ddf4acfe5db04944ddb2a65e9c5b583aba1467bf5d4a96c95127ce34d2ff6bbd75ecdb4c5dde1c9f466812f2827d912f6b4e9d3446eef373ca5c9ffc933e9db364cc628b2baf2dfcee65cd09abf8cdee755dea2eb584bf36af777012e6116827dbb894a80ceca0f3300717ef4609d0cf2b706916a52e666d547c4e01d8d00fb2d516e8dc53a9e08b30e15f232e5129ce79f7f2e638c59d06b2586710a172da12334ed01028f368493b02ad722304ecc2c717e14c1cccdcddc0f0f951dbf16bad689d11e95776b1b9f85b18b2e3af4ff5e3054adcbb1e298b0fccb78c6a4fa4d5922f5e1aca2274cfff99f599d11f3c5d2248cab775ea7620d71f6f0e0bb6854ad9cfa77de298a86f0ebcd696a2e763da5f93b4ae7a59c0d68ccd98f30641de38c61e3440aced00694d221a4a503592de572c97b6509ea0cff9613c74c9d1ea97c99ab29e64f4116d1e4174d06890000400049444154cb7b4bd1f4f868dbb2a3d49443b88d878db36eca7ce8a5f48670b839cad4e65667b6cc317d5180e9e938a4eceeae7778c3493123547aee9870f911640fa31406a77a2207b12ebde288a1e0ac98d14e0df20e41e63138c273c5b917f0d4619cd85d36ca360ae2ba8fde76f4ee1fb2c4ca50493e65a77e122e3860c9375ce9e920ac4403780a6ed11b521acc9c05a2824f83fbf10eed9051ea86f4609002e0b0e292af9549b4f2d8cf5cf51adae05c250d702d06e55ac798abd1a5d75851c80d83e11006c32c02691c381a372c9735d734f7aeedf4263a99584e9562eeb5a1da24d39382339a7970f46a1a405d525efef19611d0871e7ea86618f87a42cbb98c7d9cd7613cc4755ebc8313bc90017ce69878f7150af8c754568f887abdac18effa9ce6eb1838f340b6dbff3673357f88c2b1428479b5ea0cdef4787be1d15ef04ea22158ed9ea90bd3127dea6dfcb863c173406a049a60e8bd4b83e219081b9ec51da49c59e29fdedcbc166123f8de6b6b02c432b1de108dc58161f50ada2b774d971c5a19605298d2fa411dee68f25e7aee199a8479335ed6bedeab9ada49caa1c2f1c4d476a666e9218197603665ee13441f8645c75ddce17bf9b51f577ec6a1ef9c79bec31769bd92d40fcaab08f7ef2b644992c2472257a1931182d365fbb287f31d30a681bec5b18850330400a115532262297730dee1b0dd20a489e4e579a3a58b70f22fa1611acc51cf2a314cb918a03d5a608967fa31ddc06293dfeefbc4617ae7bd8b33012b8dc601b38d08558c1d6dbaab2fc51978cb1ce9fb9898505ee3b9dc168e2fb860528da967b3d2446dad4c306663ae5e73ddbe27916f95b3eae0aabce965e7f42a3d2c8d4f81fdef7ffbd73a73f462c69bc64ee839ca667addc222a4e89e00deaa9fe78975979087635194565238d0e781071f084366c955a6192c9b335e0b84ad0953ebc06c7eb0ab18bc1e7e80b3dddceafad69bd9719d78383856c2d8ea70ea43d1d76597b13199f6f87dc65d90299edab0146e546130eb032e29268db635ae45efdb6fbfa3c2fb4acc64738fe61b25667a5ade652ae2e29d99c34d1eb8830d4e174668c83f0bada1aab6ccf5bab1514cf080de35a1d1f5b7d9c8996f98e56889ddc958c9d832e4df32cbfb17856c35cebaee1a0e9894a671312ad3825bd5e0bcdada1985ba4d21e95a98c7decc7361e5596504afccbbb55709018db39696ac16ca7b79d64f7ada59f93c7f266bbbbe2ebd93f58a8e09b30487d7cf9ac6f6521d876c5d5ff20beda57a87f81bae9e5300e15e67edf36e71dfeb49686dde310ca5e9d591d97363b4f261a06d3111334aaf4fbb0a0bf7454fcac53c97896a9ec113274e74317015421a930279f2c9a7ca30a61f083d278e73edb543db2768c01773b92ed3505a6195252d927b46095734b44ccdb7d330ef6038ca2d505bc5d226790fe1edbdbab3c89959eb94ac45bb001f2744c664afc7dc35ed30c72c84e9a2a0d77ac8b3d64f36df28b954b834a5986abe863ecc4b4bac289c599ff8f13a65584e94ce4f7efce32e40ae900501b899efb36cecdd7befad73463ba97b5bb9fc39b451c1d230ef0424f393187dfcf3ec7ed1cf37cc8ea7873c1e3f820376427409e6bdeb402a10f77f51c89a6efba38015140a553d5617dca637e84468048de76f2d1e2e6249296b91de8a2eac89582093285a3542c3646442b48ec938c54e8912d1aed6d1196873575bb6c43c73ee2066be254c6d2e88b6ab4916488b50aeab5744acc39f72f454bb92726f8bc90b31d19e3d7dba269af114f7f6830f3e909509ff145c9d7d514c4b11c8b6acd67794d0cd3767b1b209d8f49c04d678c264beb1abd516d613f6f0cf30797b02a66bf067a615bf5008e3a82ff3f3f8f1df0e15536eb1a508f2db959b02ec59935fefa7b7832158d234b82692d98e1e1c0b2c873a8df28a59de7ac9988473daf2a4a548cdaff1ea724c98507eedd57140fd3ecbb19887e8b082323b399cde8c3b9e407bdbb60e6d66b57ff82730919275643c6bb994f624fca60798df7a2e4ad83408611d6756f6ea4508592a04cebebacef3a4100b0f8c07f5a2f2ed837642933d9edec175da7352a22f9ca42d7d9b287f44a6fce36993eb23f8e7ce66ec9b3612b40ab20dac46357f85ecea02bc5e71ae13a661dbf829681c0ddbb82cdb5e7ab04e842c399bfc706e6d22f237152bf2c6045bba416d084f80e6c0538dcddd1b410ec374cc1053e6f610d301a0263679db30b19e401a6979bac0f71f03b586ad7631281135b05e86b673141d1351cf80015c67ac95464aaff5e4534f1d3df6d8f7cbb0b4b872baf223efc018f7f6c0f6b70d0f87e0be045a2fca9970ee282bd1b7a456b69f8c80fddd37be51065136bc862aa941680eaf157ae270cc136732c2bb09830f2b42fd3111a1a5e56ffbd8ad9d57fad9133f8f0975a6b0569b81b718491929a54ea42bd72bc22221c812bc73e738586ce599a3e79c943cf350b3599407142de0df316df0caff968b9684f2f77110581860c5cbf0d3ac5524409410c17140ce898c03d5c17899d2e185648a52e69413dc8bb7f68b3280a35e1b7dac61f501929e6f1902719a71f9133279e0971bff8b5fabe82169bd5f74117f18e6ddca93f4cd92ba86571d7c1ac0cd3f40a70527ff94e57eb733fa10b0fb55e8aad808fe3031502a47a318f8139ed59b4d25201e2641ed72c4dcae7745748b579640487c5e96b964a06c6c634cc5443010afa68f5985d1872935668d9e809b0acdbcd846b094ddf1618842db3b93c2a4b9e54add3c9806e081b239d4d1670f3cf8601b75a185799984cac36c4c17389a46e09ae636bfe14613c4b04f7d2358ef651b3da6aaf64e1abb7d7ff5ab5fc594b604671a93f2b0d0d8b80abceb8fcf968c2594d3a8c350684f7130ad7c46e883d1f2d6d0e9d5687667cbcf87016fada0f1a4314dbb223e67e613327095532c17bd3444fea70fac40eaa579025905abdde5636e7dffb1c78e9ef8d9133157e763f005b4fda154cabcd98331d769ed31c35340f067cebd16e6b70a85308853366be3de7bef495dfe47ef294d4a96f9cf62b962bc1c7c177da61e534e9d3cd9406cd9d95d99c37c31eb39293567b57c24633edb8d2868a174cd75d5af91dbb377c2ba4ae3d7672cdcb009cdf67c2cb4be21cbd2b8f22fc6db5d0881a37abbbcc907467bb2ab0bdea01e5c9a539689cb23cdce03d4ae3b8c80e9fc981b7a3964907af7b71549fc605338036dba58111f8e8dfbf9cf7f3e1f687820664f5652d064196ff52cfa30175d0f69023cd719e71458fea8a012eba94a4f70eebdac028926ebf2a4cc591964bffa6a26acb3ecc62436c6a245bff4a52f1f9dc8716ebc63851038848c070c431b9369e4f3f999fbc2944e18eeee67550a4ee72248cf677bfe2f7ef18b9a51c621160afb7846f12a83077ad27214d1b2af6729d6adb77eac42a00d686b013c13b7e793f7dd3420e78195f05fffbbe9f96cfdc7b0f0a58ccae0c99f6ca5aff57f7a36c205ef46862e8b3e9e95874ec6717051af4b974eec041228ede92cfeb367ce365e7b2b4b0f1a70109d3606788017f7d5a2ae141ca5e4e42c6b0de104b6b93a5fd4f9487e848a15012425346d3bed3c05e5c5164aa7a495ce226f9ed5b7de7ab3dee3af7dfdabdd6bc621c2b132f4a1080ff00c1cf0377417d82d6ef84ba43482746e6137bc3c91d744f9fa1ae70d71a89d8fe91c8474dcf5e2ab083ca79c58491efe5258899a64ca94bff67c58a204af99158259f9cd01d21eac7824e5aacc86f02a6710f00428d407b85ea36bf2c290b4946a556092bf9bf994aec2b9ec7ab10daf29621e78b5ecc47d26e6553fa31a17b81ecb1e3513dc4cae324b1adca4baf117b30243aec6c60c56667006d0cc8e131fcfd64dedd5f45c838c7a641a23f357bf79f1ecd18f7efca308c59bc3f841565da72e535bde32aba32cdcb5621c2e68b8d614621cb81264630a13ef04c0b4c397bffce5a34fc6cc647e0edcc00c1aa3e5f54ba8c9c3bafff4912f8e8af55b8c83414bc370853c98d44e6b02871e2c02385bb8cd9260e629af02b2d51b8c66a6504bfced39f07641bbe5a73e94db7df7ddd7ba8ee0c36994b5f6afd9de98a9c5e09bbf93ac6ffc31d6b4bcca98d03446a732422b0afaeb5ffb5a77451bf3c2150c65b55df304325c87fffa2466f7bcf8bd91bb3fda6ff7909be4cfdf4b1403c7daed77c6957f26e34a13d31b2f07b4325638707cacc82b0b5f09177003e67159ce1804835c9be54517622ad1da1a28b2d2300521f47ade43eb5de21178874f505879f613b6ccde11b81227193b9eb2bc2a422d0e5395a8aa109806e8cf3df7dcd1771f7db4939bcd178672d5cb12664c042ed43099dee282150e8125b21eb0d8f9274f9eac93e3a3d9a2624b096dcced6d1cb4eaa52e607ef0e6d92cca913027781907a441921013953193d66e6638be1e67c1ab611482674780f92fce047383d6e5a99f86d7835cc8f8d1722827010b9e539d5da8d2d910d226988e4b9e1b7b05b0fc84454bf71c3a1d97a55730870569c27a73bca67a4d6633674743b37b3fa6bafa6943419e868332c431a1adce51e7b56d67399ae0a11e698ee6dfc0eff01c80292330098b0fd1fffbb7be7df4cb5ffeb21646346015e3a953a72a80044cbbced061f2c95bcc5ad6289be189c15b19eb79d1675dc5af2075e99cab2a5e13ba7ce4a18773b46016383ffdd4d1b9b4e1a50c05f650a7fcb87bd410187ff6af8bd856c02a704bd82c2843eb4f8f30e7005a5e451b1681c21c011b44c11ef8de2f584de6695526efbcae60a17c0281736ab1b11a8160fe751c110d69c9cebdf7de9b314ad6d9f91738ecf27a1963f275301e3c2b5470deca191c023bcc6f42d4c4256f5c8f0c0852f29963fb877ff887e0e37cc3b8e9d3ab753a2078a149e93255eae0dcb8c258c0042c73cf207c7af6e0948953bd22f3d3aa75fbc0d4f13ffee3d1ee8ac684be06ba9bb4ce3be623e501673dad49698c7afcf8ac7d4c92d27a590f53274a6b56c7df6aefda0b2f144f30bcdfd51f61534f34536fcbd74c53d8552e8d1f939907f7b65b6feb98cac734082f67c5f1d0472f629549e79dc03b08c525f0d1ce989690ead1ac5b546f615d8796636e15a9bc1b7c87b87bfe4bdd0298d50096786667566a759cfdea6baf6627f9dd9deb031f0c650cfcc01a0278d1386956d8973131f25e1d7638c9efb51ef3c45df16ae7dc9998a8affff427476f9efdf5d18578365b66eaee3faf45526f080cd5fb7c58c80eb8740916f0caa53169599a9346391f4db85f91df144983cc1a3ecfcd8e395b4f759dfb40937a88211947ca4cb8d6d1106dfc4a4ca7975e7ca90d6b0e443cdcefbfff81f62c1fc9225d837eb0396330b31f3308dc96bf95978c53160879772ee625f7af321b8adfac643079adf7f36f116e7a248926b97e564fa63ce78bb4e7494f7b43265db9df4d2adf91dfade901997db7fc4d3e031506561ffba14c1380094742b5d6167a1e1a6d5b564207f5c6fccc4bf8328d293bbdcd2c969d3c9c203d5035d307e7b319d3ee61411d044fd32699348f629cb1e6efea48c198e3c8b2ede89aae01fc54ccd43a59520782c2e3c829f2bb8c6d09f9e0ba683405284bef63186172d8b2a92e0f6392a64c738794b23aabd38ce5177683dfaeed120d2f736668793a8c6db5cbd48a12cac737b22d8812ea62ea940d36ab070ceda31cb56f1e84bd2a1c96a53e9e57dc7a3ecc723974bf782c70a3346fcc14c2c7320ebef999a78ffe1867d79fd2e35e082e61304236443fcc7c78bf0a11b70a829f7b95d6b0dd869202b94dadfc0073a5cddd8e105396cc5ba9a9c4d475044f1998c7eaeb67c27ccfc79140b8ec1d32969a31df9049d9566ebcf527878f66a57e7abbb067cbe69932b96b02d318a313c1c996e25af6dc286d88c80ddde3bbd35b587ea5b14be0269f41b63cfef9af6ebcab7017a737c4f0e2cdd97cf6b38fa4678d7919afa5c971eee5aea488425abd3ff8cc34837f6e6ace900a73600a4cc8f465bdb7b7ccfeb757f231ba679f7da60c632b0fb312a39dbaf79e2387eedc9a1e470062962e7db4a62e8feaeaede02810ea751aaeba3045ed6478f196dfb4d7ec7830a6abf1cf5d397ae02b7ffb95ce4da23be56a02fdf6f4c67a6793f41181c2552f82854eea207094587ffa62d658fa18879e88e79269fc56cea13c79f2e4d117bff8850a6f331cfc597c34b57220ee4d1d279aacefbc5cab83f7cef7a83963718e2a7461eda813e5fbd0430f754e12fe6db883320e6ff7e54deca2d7a11c1ca6c7df26aeb8f48f9dbce7e8c3b17e6ebafb547ab51f1fbd11813b1fcb6b1b93edb82fc9dd4f3804bc0a9fabf734269366e6ca206f4cb60ee36c5be68f9e22ffdbf01bd8d6b1714db4628761090b66fac10f1ecf8af5e7b7725ca2e55346cd3f70f3c328cc867af1cec7d44bcf519cd3e0e6d034845e96465b66b1f70b21ed83f9b8d3791a9964349f2d2c0d8b144958e1085c51355fd3a8162a338378e7782ef506cafd2fffe59fbb7a03a32f73153c650f7323c8f43e7a379e4393b5e2fa6be111747952478122206067cf9e2d43316f99968417e3c2fda31fdd945bd2738e70fdeb5d8def84c5305cefd766658ebc940a93d047f428b8fff5bffe674d4cab6e9891eaf3d1bfc9e2df2808bd410528ce7fd6824963c20617342e6d83b5723a46cdb8157c6343633b79ffed5fffb5e6315a514c5dc09b6a738ad8f1501cb5d101c788d356eb3466e7aa3059fb6d0154cacbf3190b9d397be6e8dd6f7e33e98e3a16d57bf23ece0aa0f90ace0d4eb12a7cf0405d014fadfb692b4fd21ea65b79d775bdab0f37cae4729c21c7efbdefe8b6e0f7c14f7eeae88df46a51ad5b8514e0b66334377f3d48ae8036581a802bff7c5ce67a1b8252e96d6506fe20bbc1ad413bf75b7d7785d1aa5648e81d3b3d4090d3b3ec423280b57eb6781010e3c10c9c9a0c5ecc8ff94cedf5ed315ab941a58d22e12214c1622e2e0f6374ef41711c161c0df647cd598234a46d252f65e1328d4fd09ca561d2f4739ffb5cb566e767823621b9946d1f5bcd03771404a1ed71041110c2e87835013d56c3153f75f52263bc3f65c363d77536224a27e531813a568db2510f4a457e160625631a8287152c8ad08ffbfffefbef3fbaefde7b6b6a1344f9ec78f8d9cf9ea8892d5d57f4e4da79c58c83fbad803cc3117c42d1e56597a7b7d0cbd81e7447be61663737cf2401e3a505dff8f0173fff457b972aade02e5094cccf8f5fb0f42a63d0c4a943feb72cb8ab1b256073ae5e5d7e4abebd71e97ca9cbb7fef0bb3f8c22481cbe3176340d62b78236ec27865beafc017be85da2964ede882bfd73bfd2ace7f5def53068a9a8e8a34b4cd38cd16e8a12bafe8eec41dc0d5ad56c17f60fab00af0e0bde278da0e9cd9842f957e70707c275647b63a83db8836c89542f42eebf964b14d3c29a430d5a1b3a1c8e98f9d3bcfe620042c4c3e79833eb149985420996441d2365bb3b66d7d3147853ac3f200d4c6bee086a7bc4280966e8120b63879773e2edd3bf7abafb9d98afdcc73c663433b4e0e77a297356c68b568598ff3281ad88f53e950ca3a4e74c19e38ace26c408a8dead00603428ed1a78618b46eae63cfedceda2792eebd28ee0df1d45839905b4233c3ca1bcaf4c79e5cb8a26f65a3d14cfd8acb0e18dbb5881b00ae3c5175fee3326a5343937d0f0ce78573b4716f8e80bb6f346eebc332b352c668ee7d5fc9c76f11e5fe8c9b51793da322a82675b4e5766a43ea647c61ad9be8f16f58cae172fce872699d414908d98b60cbd1873d08734ace384337aa956e993abdb6bb5471c22f067fd1873331bc1d2332fea1df276b2edc2a130892c4f6d6f970cac3869179cf269d2e19d0b41ec6294ceb11327324fd6cc1ba67d80e6845581ab0b2d961ba60848c0ea758b2c707c8cf36321b78707ea20e46603b011664a1c01b28691978b2665ca0921dbd17537641e2d668d5521c63ab7a6d18c7d301347028aafca6b608dfdc11cfe32e3914008d185364a08d127c448a35aeb665c66e0bc8ecc068b67ec6c5678fffbb7bfdd8da2e2fcd47bfd561cd8e058dd80d4b4f2f480b4ef300b973a217d2d9e300defc86a13b5350b379204fcae1ea55e85c3cb6950e5b44c377907b62908b8af150e60ac6f9da18d7794604044e08c59a6e70ba4d1fc017553bfff7c4b0594e363b5510f20cad88cc0111e6042818e012d0d1366bb905d1933361337d330701e65a45db5195e39778ec3634c62d688e3c65f7bcdf995d7b61d98b8afbe6aa7747645ff2ec726583cb0f5d6ea4e78083cfaa09dfa0a5dc4bcdd5340e8733942c6416381408fe993b884f57a7a3230ffdf833493f1afe543e7ce874a1d1e8edadb4a43b9dd7deeae2a7c3dbb4e48fa202617c2aa3053af4c959e6c9249bbba63b930a8eb04122f342a2f1664ab1808993183714100b7ac9b6ebcb966ce173248b68bd9b860ad922831036b60308de6c3793c6c9622d1a43df16ad02e7eab2ef0c0787fcc49be365ef2fe2da2eb8de0d195272118c16122cbbb7ead449ed55dafb20efa4417a6a50f36f8caa613b22c4f62961abbc209be15981206cc42db95bf2294854e43bf249a8cc5a18e9b985befbce3134ff99078ca156cb9e1747156637736040086573e4f1fe70005b5ca37be9aed2e3e86e8583c199c3fbf9dc71841b5f469052663a73ca44c523db2de65026c5116aef824e67134bbfd5d849fa365c167153cf9e493c589f774be9cf976cd60632ac38f42d0336df4575edb0ff8b6c5fe597d4a835c09207dfe762c1502ebe839ab7c501eccd586abbd0b73ab81cb61bc320fe3dc1fa6575730af4e33c70f880f261a789057fd7de24340855028f99324044bafe1045e15d38b6158f673b55e12c90fd91d1cf99a3d711a610b2b8ec9c34dac271acf9504ecef5c822707c74c9aaaa4c809f2af3bc261a5bc792b424bdbb77207e5cdf3e0c289a027639a305768d4404ffde653ae5cf32fc6e5aed1323ad8d725a9d4ad8d917a62e69ffce42747a76db24cc39ae4c5ccd62bb6b74acd57fa8e4b6a6ac27ca344c841f8fa25cab6aaf45386baeda885b1f2ac73e64060ce31536fbf5daf3165a02321c3d8bff9cd4bb22798b2f07de99c831185c084535f8caefe7aad19ef2469602be35ce611f5ba14084746eb0a527090b72109e9c215e0dca148e2618ad1f10373d21a4982543339ed773e9f107e29e3c6df9cb1d6126d0365ab033816542fc501a9a95fe8b495bff88b45c5d4a7608d9f3b21bdd58162b18976a640b2e0e02014bff686499c00fee2d5057bc5adbaafec57bf9f56596fe79a9e0cc03cf833656c170fa5d015051e664f758b90de44835acc6a6bc67b398ec011dea16a924b03bc42a602ae9eea73f1d0b0159e371d4fc4056f8ec918687033a792f9ac3001cd7af1621a5ba30ed8618c3c223ca6a389991f16ebb65192700832a5cddf621118ba775f5919278a319a86f5162ef5908531563d687ce3461a117cbb705760de9ec942d58b175ed8d1d33b0d6f93e4d063d5759f0443e805a4637a39ef449849f2991ed8e79da6a4a2aa1cc320c6937fc83c11a6c7ccaec60814956315302f3a6056bb0fe07cfaf4e92a0202ca6c75949a95278495521b864a0f9dbc1600f0089e38715760ccb869319cebc268d720c5fee029f465ed186bdb453d738304b4cc91eb6a9f194b1576c9243ea9105f2a8dd2b4782e5646da00cc6e794a1be989df493b72e0500c92cb6b1cccaaa00439841209d0c045c6a66c54f964eef6e5aee7433e3e14b0f57e776dc1f39431d946a02973abc4bcac4f227f0e01ef80ac9be4d3707a0e0cf96e5ce97a334c33c45984d9d56be0255f976815ce56f876af77348e300763107ba12b27a2b1730417a6b6e817435aceb50ae170b1620193385fd1e09ae7cfe1359c0cea7965182a68006f0cc0693b5fa824a4cc439e2b1da5c9653b023ef39987d288b7cc1eb0308a5ed496921ffce03f9b07a0d1ec331e5b8c11e82941298343e3951b02b5fcd4d76a0aa6de1d77ded179a95b6fcb81a1316fbefffdc75b8f9517acd51e2bceea92b733df44708a7b68274de4b5345cf3854cef859371a16567026f1dc788b1a9f78481b0af7298c7e69e8c69a4b92ebbbc579066a55b71ae87f1c307537ffbdd8ccb7cd3ac9f25a629872cbb9eb00e840ac1d06d6081ba125216d7749deb17bff8c5a393a74e763a81b79639f94ae60fb5fdefb21301be020716feb0b9949386223ac4bbc595b64dde3f8b562bc6f3cab3ae57a7112fae6937c1ad0b1fea25442aa15a1ab22b3544e6ff21f0be152fc85880567e8c90e14a0ccf3c1a87fae4dfa597559e5552e137aa384867f0ca1d4ce398cf39f7276bf6067142606995f10f82c28180d05adffbdef78e7efce39f54100900ed646e851220b80ad8304fb619f05f4cba1225e5be9701b21ec12a913733c8767622c632083758ffeffffdbf75f58606525fcc697e4caf6355ff223c9ca686296d2b10538895c64f7d8a57ae4ce32f7ce173d90df0a56ae435b9ff425cdea74f3f1786d91f455dc8c9238053fc53c8db11029f29a228c6635824d2db3a63de87383edac9df958fe2c1e40102ddd291a2440b78355d462e5d1e16fafaf28cb1662d83e43056e2ad539e3c3c8dcbe9d2bc81cb54418702df20fa9c94afb2589e75eeb7e792372f624910066d3674dad3cdfb890360b80668f7c683b7472939dfd1bca0784a4ecf6651c0b3cf9eaed5e3055163ee5a707dead4a90a192802fc57703bf027c6bbd5662b9d6761bd5bf12bce75cf6999ee5819bc80b8e28058052d40deaeb48771d2d78627643119514d03f24e3151d6e4b1fc0d2d40096ef6041cb41b953f73d88ab58018185606e8ecf94f6682cf5c4c192229cb22badc80b3339b170da131ca789a927b2314b1630ab66eb224be5f86615265ec62629893e3a74ffc242bea6d4eb47a3f33f681f5d96dfe8b0bb804ca1fc2461130c9982233e6824a90e97f5a3a0519908875f122a10d37b711d8a91fe6034b0f8ae9cc41d1fa1c3cd30bcde2e8e92d41313ecd9a48ded8ccc958e522acb6f3be265acc6e26a315161859bd055682ba6c8fbdaee906e51ba70266de92d2d3a6cf9d7eb64e0bbdac359a94921dccfffc4fffdcfd5b0b36f8a583bf296ef18e61059aa1b70da08ea06b5b45615e7f9dc37666bfdd4aaf2e037323562212d576e4a4e2adf579ab90a1ed2e9f096ecbd7ba1a258205014a96b385296c4c8ace0dabf2c15358e5f6e1aae7f5eeb08e2b8fb8152f9dfbd54e416dab045e688d86382ab2ca976165945ed801cc3b44aa1b3fde268cedc3808707eb4cdec9d7464d8506fe4638af102f91ca04fbfa68e013273e75f48d982e9899cd6d85c1cd190b69703d89b413b2ed9e672c1e47e3381fca2bb0829d9e23112563b7ab44901c5fd0837632b76343a45504fde2e7ebbfef6ee8a79e7aaacaa20b7bd3680e887923e7dd7fecb64c98ee34be73fcf311c32880a88b9d70a7065b93c18ea00d6dd56f85b2cac5d02ee3594a891382a38490a3176160fe705c981374b27299313de28d37a4978f023247e8f8020b996de2c458d2efc33436d7f96d118467c2c89c2aab67302d52f9cf354db80b1dcbe55b6f04573e655873a86d2d77fbe94f9f08ae3e6e1f81cd3f308deb4e5cccf7a6d356759defa0cdcde21765ebf14cc13805984080fdf1dbf39598c45bcff9e42f9fdc4c497c37f9d10b8c5a243c8679e1783de69f257778a2eff14e4cd1bf89e97dcb47fea6d329941525e3cb34f76635c6e1847457245d85ebd58f8bf75d5758f5f17c18bfdebb4ae35df793ad44892bf3972bb6bb954903af742b6e0142e835212d1d538e5b594f16efc796bc9cd67ba8563e20add08640d9c67fa210ca84e9a7b2997299a253b13d3768e432abf4614466826b1d0801e2bc11260501a4e59db188614c88dae2cefba81cf0e15f410c13181b193be0403d011c791d79c4d48d07abf320299f72b139f0fa1c6cb3e6f4864eeaa5a6658f1dedc4b61e8b08a1814139c78295237aeb4e4ba46cc2f5f1f46ebe8fcc2cfb588e6cb3d8d8a1344cc0ce0f4610b9d247f0d106030e594b9b3c531eb7a547246ccc60f8ad5ebe89932655295e70d35b3ff2d9cf66ffda979a0f4df540564dbc90ddd6cc44341667bca6ad9df181d9cd6d0a53471880ed28f5396cc9ea11bdbe7afc8ffff3ff6a5b98ae31dde02323bff8d9cfbb4994b733c00b07a906cf0dc7c45399d640826553a8a30af8022823ab4798b5be76fab5af7fad6337ab531c784461e351415bac8026c35f2b66ae57f3fc4ab3e2ffbf3cefcd459c2d68a1f7090ba8578b7ddc3779f242dc18c90a724c8319eb8a967ec1de724e4953ce14b7117357f454b82732d5cba59c611ed6751924b068c505bb6bf5a2dd694813957a2a82c4dc62ce101c660a4663fe2c4213a48edfc00fae84d259f5e672acec562e86c2e404815d8fa92a7469e03367ced4ce5fa681f4bbea22d016c4e5d5ae65e13db84f4f6050de3d6d29ebfad050e01c3879f264f1ae9995fac1ab4bce2886c098b35486360b87402ede05927bb4d1f3d1e804191250596dbada037ec52970f50ccc670b9887f611d6281e3d3e534bcf252daa31d1f528181bb34fbd62be0520a1b2accbd999c67404d532b093274f1dfdfddf3f98552477b42de04e299a52e0cd352e16c0d2db1853adfa693b3833fd79909f79fae95c67c29a43c7cfa2f0871f79a49f7cd2ee0df0058b729eca16cebcfceb7f17add675a506e6305cfd5e5b8cda49aa127a1179cbb9002ca22d600bae3cd35c438caea5a3c92e5bc378f8218a69c6bc488e957ba0a5de5b9878cf5bfdf950d250b374a638446b4aee3dc13017477bf5eb2d311169bf071e78e0e896bffdeaf454b6e76f5a584f7528949d43291cc54730c2d7cae096f7c54dbddf7266684cd306cef633896cdecb98c4cee63fc5abc7dc3b34914ab7f4cad10113b6723868d4523918c5bd60fc03f73703d742612657b7136dca82c258736a70d590ea7f215e4504d190fdeb7e11b4658a77ec5bc68e511c98d7ca0ae10a66489e81d157f5505228c6a3d67f8202be0d9126f7f5a0846c054eaef6c4317709a65e157c74b3da9e17d364f31a2f1326c2c8b174db7bb7b5e7461be51883538ea79f7b6e14b578f86d345326d89e098ba5568f3efa9d8e87093905ef3d33d43230534ada66d577eaa23605b46b8f1dddc427244bca98fb799e32578cf40373e217fcf57e770da02b8f1f58400360ddae0276990e6fa4dbd262ff31ebb20f343633ef9381f242a4d900c559b9ba9d8a89d822ddb572fbd2035eca08941515ef96a1f52a7a1413bd360edadec06c705ec457bef295a353f79caa29683804346debef6830e58e53847d0f3e1c0933f73766c7e8cb6133efe668338cf2cb5ffea23d5b57d3072673a99a75a3056ce5a912d8c60e535b53017384811effbd4e75ccca755554ae7928ae72025e02257e353e8563127ce2d567deb5474ed9686e09985e57eb8df908723469989e90e9d5afbfeef93062e63013e0b8e0376222ebeed6ab9a127016e43201f5a626e5ad153545a29ef2a31b21e385b5e44dd902c530f7f9ae41f841db0e5ec7da7eb6347164dd9895f12162be5d604ef2c33d14e757bf7aa68e2ca74a3bf2cf98fccd94f1d4530e268ab73969f994dc53b29a587dd08709ef5eef062fcaa5740a4e708608bc7327f92ef49db8b6e58a9642ca7d685e70b6b0f25dfdbcca684fb6887598680172dd85abb1ca8ba0dbd718d7f84583da2088f07e4d912483c89656ec0ec90dbe4b7efbaa8b2058b36bd7279034ba835d78c998046c6f4c4993128a4b31fd4c28f38ab567e90a9f20bdd561702458a385d6d89106b4da8189f2d2cb2fc6043cdb72967912448249ccc6e0b26875ad559fa5f3281af196ff60f6969d670d6b30cf54ed768f382aece036cf6770ff625661c8078cded037d0281073710bc745a6f6b4a1ce62163831c72d7e751a96b196f57d761318f49ff8e427a3cdc3bc0994090fe4dfa4473406ed21a0689263b6d166b58532e18e2e266ded96d686844b503673db7480b869df90213d8a5ecf7406a657c62e7d7a2c8b8e5914e7a358c0d0b2c6750b3eeb8185c08cf4d51969fffeefffae8e1c0e1da62e0febb3cf3ed33d69efbce2cba1d3aeda341528ccac0ac6406d6e6df766ac1bcaf7aebb4ca01ff814d41982887b55fd45afb0e8e2f9500e0ee357dabf740df8bd0b7f9f110926ac86f6d4fb416d7bbbd28c5618377ebc8c21066d45a3da5fa60122757b24951a380b761f874457c095064e18ff7b8f7d2f1fe2fe591bd000da8fa642383fc73eeb251673103c03dd0dc3b944006727aecfbbcec70199322ff958433f0e38ab013aa80f93a5c65b7e9731fd0c95a6398731bb2c283182d4703089cde368dec8c4f21dd9f2604cd8f160180e7db8ae7f97e98197b2da7d050cab77e66124ac8e461bda6ccc91fafa16d9a54b94d77c8554cfe14316bfc9c7d1994d6fbe319f967de4e1876bd661d6b64b70bc29781174632d822cc0bc262c664b682f932ba78e45bbcc61b494475047ce035e4c5e508a413bfad9186aa57c69bfb9c7eb49dc84cc58773986c0d2069639d95e235840d0d5f58141293999ebe4c99315d880af15e218041ee19eaf18bed2f60d417fda223826aa38055bd68123bc1fb8fffe9ae093387f014c9d5b7fd7fcf6fca8965eefe33caff7ee85f5feea7bcfd2eec336263b0430ef0f13ed93bb2bf060d73c928579f324a295d6507a158cd06d2f11b663d7ce89b8f227c35cf2778f8cb855e630f07ae7142c8375a605b2acd508c78e6d308bc310d9ba3426974962786a64c13de67d211f7e7826da9060fd3627df9a33792f732c790dfde2d3fae78ff2fd836f782e8c3f8d51801d6f8df74f4fc53b46901cb4f3899c8568f0ce394043335d9887cc4a30f514edd52284c65ec610e229085a9797eed2c593517ff90e75cc3ade328ccb818089f5e4e348f84d27ca312b8566f50c87cf0d71eabc9a55eb26ece1d46fbda51ec7339efa507a04caa74bd5542479aaa4d26668a4a7ee4032f470ee0927855e7e995b5a89a7d1ce0771757e24ee72cad53b7a466782002e987a3c5e5fe6eaec8503c54e80778f9e7efaa91c7874bad60767d2b4d9b5f916dc9d5538abfd3a7e4e3bd897e64ba7c78e3dd53a73fc944f722d1b029c32091c85685e8cf0b278aa7493cefbfea4dd42a705c427c04198a473ef79c5bb175aeedcfed9dff5aec393e0bd998bfb7480ad445703966ad83685eeb3344e1e8c44c868741ab2c7118489f434cddbcc2be730f00ecc56b9dd7333c4c3168d39aeea68c2301d4682979f81754db3a4b58e4dd9e23b891a0c0989d2c471f3fee8273f3afac1e38fa7812c2f1aa61fd7fd68441855f36b34665feab07eeac70c820b73ce18e1b630b139aadbe25a9f5dc23325c0052f7d8a2d1d50abf8069ef81bae9f3a11bef361620c8961089315269c13eaa017e7917bf5d5f9b8a13128a6c73c26ded778b0f9a35002a6f8524a7ecc4965a4f4d28632200076145b99af4722f04c31df067bfa574f07cf194719979ae805a7bd5deb33708c9b4c35bcf0fcf3a53fef5f972d8599bb6c29a77ea1b1bc04efd7bf7e21ca22ab4bb6b670a5186c09aa82089df08c6572942865a9073a77eec128ab59fe846e7a54531a4cd6ee2a00288da69d5b7704179130f49e7d8286191f8b45612823be21f056e8dd56bf891b38d386935e3ecfff7fc24a3fc6765971b2af170be8e1558a5d35568552eed2221df3d0dcf9c1a7bd597a3423d48570f19447fe0df1f56ec3a0797b9f2437d0c0618edaedd1eae95476819644785ad5798d8e78beebc4897ab8aa49115c3794404098386b558a1e46b9b41e416ad8b90383725acd2261798c7130a783644c1198d834e9cd83658d2513b0e5658ca35e9622e5efc054cfa08170f99f5b5ebafd413f14916555f0770aae39b8ef7ce73bf9dcec996a7367453225093d7ce18511c7b40be0d0b6b45462d248374ea1d9d4685e68e585f797bff4c59edccb24f3d3131380c7bfff78269a9f2dac698fecb14b6f688c4410d04d398441cf649e8bd7520f7c998e0a9df5189c434ed5d20bbe1a65d139ab98a7268ed51f9d840e23720577a66444e6972a99ff22184c777376e599bcd2869d748f23a4bb33f2027927a3ac6891ab76adf991f9cd379c899f0f029e3c79743c756df266c9dd64f634b46d41036392ec52f7fd8a035f183a1df276daa738edf349b709d964102120e661e269c47dbc2206ccfcf58c790819e69dad09b61b3019d78e5c30f7886dd450dcaebc3e6ccfdd2691f498c45915189d666632102a9e32e7e26b6c3d8b8338f510046f7aa7810b26fc2cc0f5f9242ee93fc64c14ab5efe414c9aa6ece39cbff1e0a73f9de3043e5b01c08ce6ad3a4db18d314b23d902a71a791332f1fd959ff6e5284579703cf1c91347ffedbffe57b98f3e7adbf428b6e49c3d7ba6f33ea60be0a36785dcf48ed3439676f316f685e986e0314129244c666ce7eda23b867de0d30f363b41852393cc8e052be3298e59819f6cc9e79e9389d9caa2300e3e7f3e63c308963196315fb7f0948e39572363cac71f7f2ce9428f94dd698896911e0412a06ebd792b95e73d0dfb5a353a76d60312543ded124cfcc5ec334dc069d432c09429e02993fea3f5539ee5690eaeb5589843cc78527ccd4c6d91fa344f014cf9f3d7bb81bba17df87277bfe442847a0887718dc89f9d90fdd9cbade012e1e0be80c02b16b9badf7e90c70cd54e6944db5dcc9731eb967926ff156187dcc41e564a1118d22a8daffceddf1e5dccf8c97887a9d69e24abf4697e0c8378ba54f540730474e31e5e84d5aa0f8c86799a6eabd7a41a41535fb862202b2b4ede9d15de318f5a47b0c1cc4faf312b54a668093043c711796feecb9e26f3780485c9ccbc53964da40e96a12894d5315bf2c2bcfbbf628ad1da357b9377da26f55bc4695d933abd98de840260bee96dd7f7d8cc11e96557bbbaaa9b4ffc86228dd7839b1a3186e62aa7bccc0d521882f28d857ff7fb6c19ca8650de4b5eddd762c2be1025a0c759cc253d9a57d96c65cddce408b37668ef957768b3cc7074ddf5ccc14f1a368031a589669b2c590c0b7fca52fb1fcf2ef90b6fcfae02746beb85561d83a68ca97756d344c91bebea518d25c59f8bf2d77b5f8cf200db8a98c37aa88bb0c8bdca9ed8fddfbf14bf4f31773b215b2f200be91506d9f574e5759f0a8be5974a6a30e6a2b193791b0db1ffa4d23068ea29f510a6393d0fb479a78208350df0f1f456c60f08612293e0812fcf686c33f803429a1957e57d607861598f31031c2d7f9266572f9153746ef6c140fe9d1c5c338e05cd3e4caa9c96a19ce495bd38a497bd78d109ca17ea46377e3261cd6181e1efbfef819ab6c533b0aa8cae09f903803e98c9f1cbe92df3adb5b8da35bef197b22648d8c277f8a3a09ef9739fff5c7b5c93b837dd389f4e22b872b6bcd2c17d685153762adcf74943f8396aac853c73e63735679709ce4bfa8b5ffcfce885177e5da6375e322624800465e1d75e2160b5fd623e0a0402c6cdde534c2be87599e20e8ca5044d3db054a490bf2efe08b5b2ec24109082f263f63a6cd666d2a6dff22066cb09dfad92d008ce7a33f832a59d42cc1b2bd143f1c43e929521e01ed64779fb7aa0d484555f4f87f7dbeb5e76f1adf7553d9997899fccbb16dab2cf8b1d2cc8174b7ff5209e93df98a14260501f4d61ccd1f312b3a8555079e52cf0725e1dd652a1a64e5a0da227924bc505a6426fe19b32f5201acb7bef8c634c523aef8f57eeb7d1bea74f9f0ea3f8c83b810990ad0a838d7a0c6c0d45037296f096315938564a9fe08b19686c6570134b435b7258d4159d41bbedfd0454cfe94cc44fe4b0194c35145083114e9568f99b89a307f3051b4c4fc806c9266ff973b7d1b074bcdce5527a7b2b5508ecbe1eee23085ba6d2bd79f6746cf989bb25735f5ffa62ceda3f7177cd315b4566ade39b478f7dffb17e48833344dbae00ef45bb8527122e6fe094979e3aed47502ccbb2d9d51a4c8b7599fae6f26ce979f43b8f7632bb8ead40e504d293719eec9645259e3050b826c57dce69f59ce84ac1e3939b62f6fae88676b36ccb385a5bfeebfffed7c0cc46ced004bf58286cbae5befbee2ddc55af7585ff0a687af8bce20fafeb7de98f2f931d841dc5760916d3e3b7e1b949991cabf1565a05340eb00d1fef0c8eeb084865eac64fafd0d68ff7a8c9c06d9e550038a0a5c91a2fd556dea685ab65fa3eef92761c00d39b69540c6fde860dcea43107e6d866fbcc3c5bc2e37d9581c295b0999770567ecb4ebc3a197798e0c5e8cabe9815f34c0c734c56118039a693ef376783604d92ec73e3a4d81ac4f8944962a2f69de4bb1c73a5da7d232c2c4641ede9c089c2f367799115508be6f095aaabc68758a232899bf93593d811766b13d72b75aae99ae3edd44b3c86b4bb1ccc5a1b61fed59684faa16874e61a13544f2823930f431390b72f39e33fc380a4119a3770873ed3b36977f1755264054e3d98392e1ca33b04c95a48caa06b302310c67494913a9bf79ba619fa1b97891b27cbf4fa7063beeb619f3bfd7cf0e488b10d2a6b546346eacd9565dc46386d7781cf534f3dd5096fed60cc7a4d7677e399dfa547e344b1bf6dd162b55f2bb9fd59ef0ee3dcafb487edd4341bcdd5a742b61268f409fbbb8d1f1bada0959649a9d1dba892e7e77dc72569088d21a848c7654918e5b10bf20de2a0acfbf57ae2fab4a56b3929c4b5e65fc667189a29a021f42206e94cb4dae0b1e9cd31612ca14507bf412140dafb1e94332cdcbf0a610e19bb992035f7648ec7a4ef2ce18ad0e65d976085496a3ab5c7985eb73dabb15634ab3aca2bfd1dc1c5ae6275183ac266fd6019e64f1e9b2e310ecd0cff2aacbca381a59f3a0c8c7195bfd15dbfc678d2aa84b426dfe5c7c41486deb51f790f73df932d1f6b3e2b28d6642478c522082e41aae3294e2763606b3657fb4fdb059b346a5abb3d815e01cc3be385e54831794ca88c190f57c037ef46037cc27b6c9ce5fb62e0335f95efe8058a0c0fc1c33b7999c89c52ac843ac122501401456131000157975132a3040821eff0eb31414ba35091195b85991ed158162eab7eebbaeab9e8e27a18b7ee5dff529edd2afc5d6250b6408c687b61446a58a2cf21d26a882dc1b04b0a836c0fd6c9bd9e4c23138c393330a90372cf28909edfc0c54c85b8556690e73c59df1a73e213e22fd3cc3d17f13203d7dc591920b8808f000beedc4f218b388b40533273346bdf32a9fbad6f7fabe6a10d826b39d272600c7d8c4352dfcb6b494ff05db54bb9762ecf3619c720ec8f54230813dc6c0d945b5f6df96098c7de389ec277526fb8a98350dce7a6f77a565e388ac5988a7039049599c569619e6dada67026a4f1a3b59d1f8e494a28e1ae75e1d0f6cc2dbaad80b9b9fa31a99516c565ab1ff3d711de0e49bd33d30f26bf79ff9cbb016e156e7a0cd7456765b40e5b637847c8f4564f5d6f11717a5ad64b8245d91403d391a04e3b1e55b89cf7f8d043f9a20adae7b7b76c8646ca719689009aefdd29e3574ffeaacae7baeb4659a1d36fce9ec9b4c6bd9b253082dc7ce599c1c5f39211f8afb0eae5baeed7bb5e93f68afd6457bcccc3022a1e4a031bb362af2073d018f34e414cc539ec1401393d986934eaf19c8d35907a69ed0f994739c2c4ad8656deccc1fcec673fcfae6527dcbe1613ecad12bf8d9e7268ada9a81e44defc5c52245c2b78ee8be8f6a275ba92a853bebacea2d677de7eab42a6aeab879ab2a6474ac278c352424c566335650e730df32a0f93684ce33ccc22d1012514b90bea70439c1e34b4f1c79cb2e5f5d48fb2c2406ad0f1473c6398ddf8e43fdffacf4e5c3b7a8ed031b798477a34d536deb193faedf4ac4c5826f1f410810ea090ebd0481ccb64ceafb428d830403ba2836025fd030f3ed031a7d5f94b98bc43bf0a6deed7b53cb3d563d284f6b9e97eb728082629478716839065796b02def86af871da0b6de619a4c17995d3fc22e1af62f9b10e3e91f1d94772e02dcb67f101ded4bb53a2d336034bf6c37058d6ba3fa4d3615af7dea5c58a63ed03991a09a1840e83e6b6cffe68a4bec61d0987638b5558620bb442c60318bb1ea134e61040e60d30807f25286be06a9a5922e3f3b096462dc6bb261accd1001ab760037352abde04795b546ea60ef3a6cd5b1492a3d72dfdf6b0f2a98b1eb884dda13e19e0177eafe62584d767bc00977ad3ccd924991eb14216af16f392e770d1694adcff6d7d5371e60e2163fef8885f2793ab381c8c33672332c94c3130cf984d3c66dffdee777b6c02a15a95a2e54d734c838f02c0c8e0724aad7589ab0d5d0f998842313739ce9fedbb026a103c095c97856d6db0dcef43c2a1917694f610e6bec6ee72bc41e8661e948b9d00d4224993aac78ccb7edf256be37419ba2e815ab017fe87b0e1f35e7ef88f97571e8e166db470540767e62b476f3def16ee7bb958f00febb1ee0fcbbce21ebfc412dc998b872f5988edb1739de2bc9dcab5b1762c3cf1fd0b6032790fd1b18963e3aa6834781d0271aee5d556418477af51a72156450a6f2b1831540671acc6a6f5986df2e48d3f0761b013116c0bbbf9570ac927637202acfc0d86f2bc4b86ade86ac1dee78fd8183d12f53f90b42941809789715f7191f9c9279feacf1406dc694ba6da72a260fcc3ba4a83662bac152126e19d35828600a3a915e57ffff7dfe821afe68c301e6bc1ea1a4a4dda9e7f1298fdb7814507f57331f9dc4d96e9e50cf80fc3fb318ec5c59c0acc3a0a6ee16e82d75172dce2deadbc2906197701231b57cdb0e1529508e1965e3a8accb2b43ba2347c7c9e90457506df5973ca33fc40f22fcfe6e4db17501e4a6962dc2b0b4e4c4df5b4def3b538c098ce84f83028cbe20463fabbee3ab1533a2bcdaa93e755ee8a6bb92abb85c3e795c6ab19e926617baf20b85e166129366aad78512b6eee87e9a6a8ed6f4c469e353f35ef1ac68c47fad04cfe10822b116c6cd22b0bc22b48c5243236f01d69bb88cb405754105a07f8e77e6060ac024d9e1168f187f0e151b342818169dca2f758d7511ac65c1bbe016705c403d9defe852f7cbeabed091b8f59773807bfa79f7ea68dcdb4a241e7f8eed1a68b5946b0c08ca3258cb8f35e46ab5a04cc63d8b7e801b5fc987b189a876d27ac79f1c1384a6c413973e66c53a2a1f42bb4ee7910676cd88ffec564e489036785d5ce431f748a991941268c7a33ccb8c64c9407a6b5c6d1e268f55974652eeab931b9b2b8ce99b0e0715a7ce6339fb9c299d1d5fd1923325f096344af69cd971164bb02a6d79d5a2d3c17dee8d30cb930cd7ff8c31f1efde4473f6a997608e8cdd493c9bbf8ae79b44d1c3ae6cd581c4bf807dc9e9fd6b3eb0a57f3e98a3fbce2bd113235c7402d759294311b7dd8547987417321942de4f0b57b8d9bdfacfc30205d7bcbc675ac28e16a22891b3486a15a06785b7a0d68e994f3349a37656caf644d1882ec1b59dc2057587dda03ec9c9a5c1b4204a1f0fa0c56ce7e7452318d9b3ad8eba4a116de98c140fa9e7beea999c32c106efac0850ac18ca730cbb8ce6d82c46c18d39c1ba1f263ba995e30ce7c29bb037c73ccde398cf227ce9c0d3f427ff11a4e9439a189ebfe86eb4383bc871321efb825755617aef755b78a1b7a8195ff29b95eb531cdf687a1c25539c896e449df980a0fc1669afe2a9b26c15f828941399ecac4ed49071fbdc973a74f1f7ddb370462e61338c20d8e76bcf7de7b2b5045280532917918b9dd798805f542231ec03f86769c418bfe4d70f067d121c8b59ece1b719a16fc46b8c611a372163a08f2a82ee132ddf3667a3f66fa28bf29ffa0883fbb5d65baaefb057725866f856c21beaecd5064923448e01fefc4e7461bcc8b5e10b5115bba2489403001540ec2177328e979a70a87002a38b006c62eefb4e8d03c254ccf3202ad5c70f4621a498134e58c0ba7ec44ae9b2baef2fa07ebae9f33868aab628d9b16436a64cb90cc35e9312d36b6cbd7bac817c3fcdf7df4bb1182975b27f89b9b232098692d0856865dc48e3be3ce66a65ca63d833b6d4aabf718b4382f1cc26a7ecd0ee39733f5c0bcc1a80d7860a3353aae76812ba6211c966ce939db56a9ba413b37b679b6f9e8dfbee1878a7bfab8eb7eb130ae32298481131aa1551a7ca1001fe513628b98d149bd2d0ad676e7d2c3e8a530ea82d33cf9634919463757c9db6c7a82a07956dfc39e8929cc64bcedb6783123647b419e05cf6864f1f794c17cded7a7e59527863f99d1e6d12c6476380f5e5a34c437480bbe1f6e300d62c99871aad5392bed157095a8c27f25a0a1b0d295f712373d595e1c2690a8e66373f8b37f3f4ff3b7407611f24c65544a4fc62c72a6a01db1c6659636994c14e0bbba6d15dd05f5d81ee1bce15d21e37d72c2942efffcf9ac0cdf76c7cabbf05fd7c338f0668508c69bcd7e3d4b304c4a73727d3b968ca6e6d62664cae90a0db4484f268e90818f3ec6987aa7377354dcad990fa250c47314981bba336334bd91d51b1adc4959e03df6d8f77aac9a81b65e8da626f0ca3096eadc5a8472873f5a6c6d1b4c6a7a5b88ab67000f7373e3f390f949ba1ab940b63f759ea0799e9557f778967e8183d957bdfe3cef146e3a010d30eedbeffcf6e8d846074a86194868d072f5027a520e19bd9355f0a63d280c533194841fbaac1e513ee9eda23e7e3c470f70de24508f705486ba12f2145dfe6982833f0b776998ced681b20a08d3e28b554f637c2b5acc474afb898c75d56ff872da78810657be056395e3fd8a5b6957dc2e4dbaca0ad902b07b01e06ad983dcd2edd2884fe19012e4d85ab83d997466e3113143d16a7c8e80e8cc1d8cc95146c9970000400049444154abd9f78c014e6336f3cbbb21aa45b6c7d3108e5fcbeaefaca4d8152e51c2aaf0d4a7318d2f2ec1c34c3f73c031cdfdfe7184cac25a8e0b3d012d691ca9c195098e71997572870c0428e130c16cceeedd77ef6c3d7d03002358af69ccf1f0239fad29c9816192f4e59c29783a8783125681d9682da61e79e89aba2b374ce1b9f508358ea5dbc0a428c39bf74a06f0bf7cf297154a26e64b2fbd52938a50eb2984a1017843df699cbec94b2b45d2b31a4f45513033a7cea96fca5637cf4b0064f0dc3d68b7ee0f49ed76ff805cbd2b9a52acca947799984f3ffd6c7b8bf46da99b8f0afa7ec0f4a2937eea4b69f03032d1d5a5b8079fb5eb9a8710cca1d5d4518d0e9fdd1b43ce2a908f1e3d7fecb9c07112d6f56d6307e31a87dab5dede2ef0c054b60da914c16180c382efeab9781d263ac0e1aae8f2e8ae275b80ae00aac157ae5191f324722b702d4d1a019bd7ad6890e5fa3ec6f99197bee9cb01c21dbc903d803e19b7d21485a1b6ffb2b76234d42d2192d5f476eeee70967bab3cfc310481a2a16fc907db11936019439930b54b9740d1b698412f923e28bf2122136a089998bc6342eaed5c31488aa8f0bdfec73fe454a567ebddeb3791b322840968bcf6852f7ee9e8f3993065662db3e0ed8fe4fc8d68f69ba228f4843b737783a7b7194f6286fdc10b03aa6317ce327342079ec4679f79e6e8f433293778120869fa0bae6835e41866181a89f43c66d2a21146b7a2a5db8862c2da0829ce625d3dfac99327cb7c3c7094aeef97d90541795ae59262734cc89caba2c7300fb884cc3b632f1f06b45a04dd2848719c24ed3d610529944ffa1b6eb22ed121b6d945fde69c0fa92eed2d3785308e9a615b79ae0ee0a9b30daaf7dd775fe05faa6235dda197a464958d97daf649ab1de42b2e9eaf023a382e5ce73a740de649bfdecbe6feeab89d901dc29d6a2783d23c6c61155e92330d133fe407dc83b8fc832806a6a931719ed9e7fdda4bb5f4e6575e70f35e288c42d4a4899b02b6789a3f3d598454378ff9578524f0aee3929808ec7aa61f130db3989b720214e6376629518387fc453b4ca4c782c6fca60eb49af7ea6155bc8679338d7fec58f2269e89f4a378b07ef293397f9f379090389bf0e4a97b2a00ad57c66fcab2c29e49a2677c434f9c505a19df44c88fe5dcff0f6d2696357804546ff36c04eacd68f1a450d54c6158ca653d28f7f7f438e0a097f75e4ccdf4b853f66c37b28b6168a8c730e5e003848f7ef73b1d27bd1607064f21985ffaf297ab9428a31568fb9eb41c5a58c572393d2c0560b915736e4c4f93edc22ce6bd3b5b55bef18dbf2bbdb44985288c6e6588fd79bb10dc675d623693661cacb76f9d92c05ce35b29af477247b8a543cf56b89073b7e3a112200ea80f7485bd034eb51da58b47d66a1d7997f7789448281698e5cc0dd60eb7ab6e5659a2078f7d82ddbb41a3efdf57c852dfbedc31fa1e46efbc4f82a9e3ee1dc1d0bcfe866582684daf4dc830c68c41d6716b2be3864df3ad387012bfbd52e716971b5ae80361448c504741d218ff69bccf3ef2f0d1830f3e50f367b4d59c7ceb7dc105485788eb15721f31dd4c5be69832f23e0d3aabece77c0d668b035f5e78e1f99a82834b0a4d7a8ae38d37b25f2e79c037317e6dcd3f3b72e7080082822232180732b9280370350878342a65f0c0030f1edd7bcf3db33c29f9647470272749997a238833ecafc5a01b7d24449ff9934b95c6bc5cbd3a0dae17707e0666d7bbdb66f2eb332f1c7deb9bdfea4e6cca07ade0a72731d663262fc6217084cc4e6eeb0ad14bdddf8d79ac07643e1bdf28d33bc2e0d42c0b83295d4a8a6388636ce52d654204cf0480125d93c2eda593362f2bc03eed649a031ed20f5e435dcf87b096b93a34ded2045763f38e819b7a4f42e9e0ddb0c1da92f4e2fd2a43c4befcc354132fa6b8852d94bc13b243203b6083db0eca0e30ce4858da52f508649febdbcf3373318435eee00ca049cd7f100c87644ef1a0402c4f0139607393e715a7ccb94f098179637a24e607ad645c9676a940132acc73e79d9fa8593790e7afb25ba7143026da34880211fc42c65270a3897b145abef365e272c63b2f47dbcfba43388f265cd88f901c8bf9a4fe10075f1d9dd561bc83b1e12ab8625a0ccfac5c95447be322f347e6deacfd53023898d699f4474716cf8e47b50d384d30f5423075cb3f65e8b5098245bab694182b31772dacc5a0d260424c6c9c43a80817a74b4da894a697f63109f8b3002aac1112a63a13bc1faf9816dfd5d7962293e58742d01dd4e9c1378a07b26a6bd3895957b45386b11445a03dd1efdab6192fe63ba1d9cb353bd1a33408acc5b7e0acfbbe0bf8c3725af0f6a7a42bdcadd5e01301bc901f5e29df863e681a20bbb2ae809177abccf78b6f1c990d1e3b21db57769f650179df7772974e4122ffae087954510d56132dc43b1781581ec6492b4f01f471a3f91617881bc8750dc434c251ddecb681582ae454aa158c7108ca98476297700ead26ddc273343037b6c36a1c90ea70192ef77e81328d4b8b73933bf454fd0917e66c0316d8c0d708a54f04c0ba42f7c610e6bef4567a3bee69f98c51adf7a32430543535da258fc1bd31cd6018e14f83ab33b38750820156698628219dcbd07efe1214c2faf0230f1d3dfcd0c31d8328cfd444856723e6c056ece598a4f3b51cbd0cb3099e7a7287dce8cd08ba745689607acbb74c106f2440e55d7a8a45dd099950bab8cec35c73bf68b87a0ecfd2a287dff198cddd8e8218895fe6fd9b7fcce7ac22f8b7e7501c79e559b01421f47963ab669fe82ae2bcec7bf168809e78c67ec03ab1523f6d6eb1b371a3f69667d503a875bfca7d3f1cb622b74b14df95117b022c60de6b42e333c83568e0dcac311b424f334fdaaedc4f02631a26832f4c5e8ebb5aa5cc9921dc1e18881b5572b7085e488121e90adeb1e399604c1dae6cef25e1d13379cbfbe679f0d9675e4491c1d481f9130e84c71e7b2c5fc63cdb05a25d651fab5c5a3f8ce99f3071f0c1fcae23a8a593ba26bd1e9a39a8e7b8efdefb7a251c2b18479ac3d1a358afb7735307962d3b18c82a7470d46b9c2e71de648c82e946c8d46d1fda364d9c583887d6a610b8a4397856bd31d5619b4e16f35ff3792642a66c30a4a5245ef8f5f381774d7bb57e54312b2fcc61594cabcd51be74083d67370037fb95fbdabc2fed22140befe649196574ca316dc131c22cb6d2e5f9e79eebf395353dd6722d843e75cf7e6eefb04e3baaac829498f2a5e1d97e2fbf2ae32a8c2c00485d4c941bfff99888ded2dce3df7efdab35df7510cdbf038c3c237460fa2dfa1edeafe4e2843d076c24582ff609dd05d892288594c0e235aceb3e68f412d5303d8435461964f331f2d8d3dde31546cd1014fe49bbcf3bc006b169c0d6714b90f8e040bbf0f29933abe9b755165ce320837093b2a3b9c13fd044a5f92804f13c75cc223fc13613f904b82d5ac08819d19017985f9d3802b87f9d0a6c3fd25dd17e8e4ae0dc60ee503053fec0c266e2098d35983d5eba65cd573e7b38681ada44f622ad41bb69068a05238227b4515722115bdd6af2860ee871f983a308bc5e61f51ef2fb318dcc4f117aab5ad4d58f89fce8a3df39faf7ef7c3b1f47dc4f0b287f681bb3a2b8e00d5302f38515bddf8ccb962b7c63f2f488a61766e918b37cd616fa281f4b82f0be9ecf5619b7ab8c7266b7c3dcc3cae6ca975f79a9c246291c86a17370d9e823bdfa195258c96f11c0cb29cb9993afe5cc4d93cf6865f7be312cbaf89dffc0f93881e6638735a3036fc1044f58cf5797ef79a5595771074206c0003c4c30382f493860d8a606621aa5c54b1c4402a58818e0f22e9a0b52015ac442d68e91d2cb0db2600ff2ae5b3d0af7f00f9c80c71496567d303f2b299817de5dc884b7cd916f672f150d398cb0c71bac055b69849580d8a2f17234b43cf0068bd9e4d92f18d554640221bac958db3e9cff67bc72dbc7e62b2433796d9e2d8215bc84056796f10c2e3d1c343d8d3d6396525db8805e42964b6d66ea47e11218f085e78ce33e3ce3b8a6dd37f4d02510421cf79894c7d058cba2dea534d05a7d9873e6f208acf28cad9e3bfd5cf3b5ee81011f9e37ceaa4517b4417b7028376d213d06f5201dcfab71dc5db91794839195c531f2eaab59fd1161e2897488ab437bba11b4e98d3709f0307b5a3a1086320841c7ab9b09774ad15aca8e8ff3aec8480da9ab82dee9a9a79eccf2ae7f6ff997b2d9b71f31519fd098c234a65e42c65ad05b5a68c019d5b60c5c90c157e715d6f361dc7ae72a8fd40742265ad49f23da3707c03db742925f9d25d9dbe9e5dac6484534b44a18b3d8feae41262c34b647e052cea2551bb144cc8b2dd2e01c8332859846844c005b831aa78933663bac3cd629aadb1f7bce3a1d905e5195f73811f4e9312d0236d0f791400e04e75ee8a53ecc431821776e851e8db00b50e548b91cad0d281aa9610bc80d7c088d31166fe8d9ec04e652b70cabeb20efbda7bdd82e5ff28fc76d76008b87275a0aab7eaedea5e43062ce2689030293bc77c72c02c09c18de894df69a9d3d73b66612c6371e61ea197b15864ac03bbf3a42328e9c802e5e4d3925a617add7e0628fdad9b367d24bdf5885f762e60df51ed66cbe9d7d794b09b6cd747f0969864c5dcc98156cbf15d4cbafdc92038050931b9f705af676656f36f0e48567bbd75c2806263b1a50ee8438dcd8725ad6aacfd652c6a34ea1a6043e75f1ee7612c503c80d3fcfc27aee43feacf8f5ec0ae745c1c6bf5f22710bd8ba775d69f724492105baff231f01c358c75339264c0fd64983de7003add062037faedbd3f630d0d6bb2d6918ccb8e6a68e25b883c15330d73bed4dcb2128534e1585e2da7562d87062e1369f81cd8703637ac0d1da48736a9649ddbe9de9b84c3502a52ed5b49b26df2085f1a7f7535669a5dca4816b6915dca6ae2334e6c0be9843464f9d3a556d69829c3384694868c19830b4671e1b67d1bac371aa3c14e95c4f1850913cb98e9ea3dc3871de7bef89f61add199db107c6b1bbdce05ef65d1ba6bc55e6c2bfcaa2450c0e2d602bb3750a826b4d2021b003bc736e8f3e7af41fdffb8fb4c57c526a2941b45bb4a819c892d96ab92ef35ecdf20fdfe51f67ce32cd2937ca6e792f27cdcaed0ae10d6a357d762d44d952687a7502eafdae5c3408415b77917d8e4d91cf6159a87dfe33efd669943783fbc6b08b56e2dd6b7f990fe35b177508dc0a59913d20b4cc87e1f0bdfb3f0bbba8ade9f30ce712295a1781112b98a681b35eaf9a1e820bd2eea61153c60e6821ad942ad3e555994f31b94b3b22d4a51c2caa27e3d163924a37586c39e1b40accbd3620ac777feaee9a3c4c416e6e2b1ab8bff5621a88f0a9079cfa0bdce207065a6c745bbd8b321483b9ce9d1bcf5cd72462b262e4f8b50fc7fbf750e15e178706daa00070530e9ca54e0830c2c76b085ff56bd85ea381ded6bc17f3c667618d8968fb7ff9977fc902e4974a73e34f42aa2d0821a794feb07550cc76ef597d09ef84a98f7b7583e30a04b182d68839fcc632b3b59113eda6970743ef11da214e82bfe284bacf839fb2e1a74e1c32ac86ddc138a91b45a3d7ff505cf804af79fb6798b95077f8b999fa32b7d1e7f99c8aa5a7d2bb7575cd56d7a654b1a266adedbb9d9c67e61a52ac30af871776bca494bc90bdb45bf5dbae8056c80e332c80575f57635c1d3f88257687f05418e218196c0dcaf4e099d3cbb07bc11b4d3f480fdc56f7aa22f21ec3f595c69a495df329377ff003b5d19977e081cdfce162a65dd47357d7402d9136bc144223de77dffd47274f9ecc44f1f108d5e6ea668e051e26d2282d3ef7a553cd1c80a76e6e94cddcb2d4889793f7cd3223632ebda53208c98419641f4fcfb890eb2afed066181196047952b7be612882a4c139770442ed9d9d025fffbbaf47597caa26ac1e1c84279f7c2a63e0cc4d666aa02b567cf43ce90b3db08d357a86bd5e36ff6668a5adc635ae8cd637d7d5f67b9ce69d7834dac1cc0d06be5e39fd05967f013e737c130f7643d2cfc47a4ea0ba91099d758596bff9be4084a21f1c8cc387b388c263f615efc016f67881dfa8ad5cef270ddeb3ba8479497899c6f5102f184956d62a3c30662ac1a7b438a2ccbd8e629250395b4192265cfd7c1837186488d0947fe10f008bd0921cde2fe03b0483f44aef8af8b43966d770d7f2b4a5c2b484bd3e973bff24cf61e183d6b45aeb9432e7bdb2a74cda294e8b9cd5b0b40ca2390997b7ce609ea019a3319f0e43d921e515e7c0a5d98d1f8e1ddbceddd8b4f7a58315e0ab4e6ddca41fc68c3b388e1642ad4ce519ccf34ad9d43847c4fdae757df081076baa1887958183902ab5da296faa7f48e7899168ab7a1b595d312117b771d95807598615e1bb2beefa3b6346edccb7d45d79ce37b45a05ee4b78e4d536d10a3376ce3dc170221711542761b5f5d0bc516dabc3f8753f6f53a785f062e0c0f66f579124dcd1734b630bcc3da74e1d7df56b5febf7c109945e4a1b2fe69eb667726fd45a703752295f9a858f74945e9d3cf1681b7b5a10ad67159602761faecacfdfbef0b7b4b2ebfbc59c9ff9a97cb79cb90e667be15c5739d21ed667e1275e0053dabf2a6487c056a64075bb0b05bc116c973ecf7a2966d2e508976f1c1bf7e8cd2ea5f2f69611c075764621eee0e649857259f01041504ce3f3ecdc7b30c7f4cc06cb98525602e8316c6390b7205dfd036f834150dc8b17aa699b66e2c5d5eb7468cea4f7ed5c5c883fa7654d4ff5db78ccccef9888d58371532fedcea961fe8beb790906d8578669e4c16e613435d643abb3ba6868630b8cb2c6392a65c7b571a841bda54bb3c0d8f79d1d679d435977349a52099bf1b15e9b39765bcc3066a6f4e68b7efef39fd7eceef8af4d31346a5b205ac2622c6f1a5f424bdcd712e416e2f9ab0207613d1f3224d35ccf359f459a9de42b8b74873f7c2570087508927bef09150561c8801e949ecda49662b1287e9fab39bc5a545b5b8303f5d2b8f80e6cc2246d7764a7e7638d08deeeab387757d767d5cbd53bd7f715b2c3040b484b8151c2ca7c78bf4bb761e1b99e1d5a976d9e59fc6bb2b78c0b751d78bafba452008dad9ecc5bfea9d2d6406c79651f94ff81cc937d2226c0a71f7c30eedc5b3a20e652c730ecf6698c8d5921ba0b8199ffc3cebdc973b479f06532d591e0ed7bd313779b0557b749cb68b65732a7a3f1fef4a6d3b2b2b5e57cce544f032b09035f93b9b6eb8cb1024f4f6a45014f1f06589a74a1a23a43b7033cafa8bf0782667e6dce805f9f0d420f820bb64d90f7de9323cdc20ce898ff5d846c5bc707c2c0968aad36654ede7df2eeae08b1aa418f6782dc3cd7134ffcb4a72c33a9665e74f09377e55f94d31694d568f769363485eb5421f7f9279fd0fbed4d2312e33fda31cd3a96cb8265bdd8beac2d65eb34b0bc6b6f9cbaeba966c58673f37fd7e90853124c756357ca0ddd5935f0a5d48d11e1e2ffc24d2970173a2d90ab8509cc451b39edfc90af29d46fab5333e4cfd5cfe2a75d27c5fb0ad9bcfacb7f7740951a64b73fbbc2bc1f94a7b09a2be9fe699e4bf130724cccb8cce07a181218b86b9a5d66a005c00a701888863746f9ead7bedad5e2e69eaecf2a899a50d57429bf84182c0a1b8803e2b0d58b7a006b34bd2b373733a1e724c67160e2f2a56c05e19dab4b3c84df23b7a072ecc4795160f0dbff14e70c444e081ab6df820e04c1bb36c4862766ddc54d0aa91ae7d12a1075b655e48f61a09a7c3137df8e276cbf02fea6d21563112c8c42b05bef00571e1add965399eebffffe4e1b543104f9eb5377dbfb6d11014f588cb2ae8d14bf6e7205bbcf48bdbd905efca2b77aa14fa2532389e0d24b689f9dcf3901d9c6ce7bee5d47126c703738762e4f2f351f433417e7901c0a8f79be84ca506421d17157ca6a9d0908fc8a53786e83bbeab5f0f44cc97209b1084c7958157222e6f8b5e131e928e356758311a80dcd9bba24fa8a20cf5ec8ae2292948748acfb2b206c007b497e69006d5a151a20d50e9d94362ebb661609ebde115b5895745f58fb3fa2924a3abf61447133a0cd5c597b8f89272c60e57f830aaf7bcc34049ac6b7edc69402138fa9371fdb73a291f58b39b3301a8c77c9f9efe0343f1811cec1125629c840444fab6c440e376164281004bdb615ebb6c17c34cfe3511b040feb0dd1f7a57121cda4b47586ccc6d22e65327d2e44f39bbad0cb72d1d3e8188346b7148ae934e49cb6719ab39e4a0f30214a86c72d78dbef6503abbace787a3c8cf05af597c77361e6fe62f0deb5f9417c8996f7da44981a4fbed47417e7bd31ad551ff0e2e55d74518e152267ce9ce96657665f8f09c818cb12342767ed4ce7c05487f5bbecb0d92d04c51d6ddd2f6c7678af84db95a0e9c9addfc4174c474eb2d53e7ff93a000ee14abb17b245b5830245c169013d7875e56d133665e34ba4004fce3eebc96854ae778d7521e316cc478b851fb770d0582b6abb86740155ea5468a611923ee310cc5dfc4294c4e41ebe7e03984653cec58be94133116e0500e78849598ce8b831676f302f9c42cc04e1ede392c012f0363756e1c0500b5badd5e7b9969839825bba12794b67653721b0b201335b0b282b6c8b68d2ad46997a7997e04f6928c128150266b589b1965ed138c651745cd4bebc72fad9d3ed3531a6b12178945bc7570143e809e81b6fc63ccb84eb850b3e3a38cb93ea4cea6a96391200f3f2b85ace754bc6ba7a453d5c77a4432ded08456504bd09ee17ce5b94f646ffad56bd68cae64b7aef559015e127885bef5900bf7aeaa9a36f7ef39b7d5f659634cb13cbbc9cb611397007c628157056e85dfe3073177d0fdf4bd7e7d401ade0e328398aca4a1f4aedea20fd920fd7f5bceea5df0bd9d5b9f3bc474fa50680640bd015f707ef5b680b5797fceb203513d2e9c9f43cb4a9496483f5e919c09f9f52d7807fee95721092b08d2b0ab1b68bb6aa3026bf95f8efbd87d1f25597ffa7b3737ff62e29affa3b93b930173061b80a8930049894fa432c49424c2a6aa5f4bf16a9524bac182f216a190c030c1733c31007c20c3017d767ad6775f7779f739868bfefd9ddfdf473efeedd97dd7b7fd5b8995ef8c335ea544cfdfeead51f6a7479c3d3d6dd44e08a0eec846a34228d24fdcfddf838bb281d68bcfe1176357c8ef8f091191afef7befb3ddd7539a6c43a4db4d2051df812120d95ed7482353f2aa83ea581e10bf21e1995a98e6ceafcdeeffd9e3e43f7dbf3bce883ee249ccdfbb7ffe6dff97c1e2354786447d1b2248c9b0dfad0c918b9f385a99c98c73edf5cd4193887f90ffee1dff7a6081d9877c158fffee55ffee5a37ff595af88ee75e907a7e86865950e84c4b41364ea1f230b37290c260f2d9f1760b7f4ef68facb8d8253f59ffaf4a7bc6955de897593d04d2e1b0ff05507e4a6a17f8e2d0b4d121881bcfe5296b555792d1d4749b1b17fbb1b0b3545c01d10245fb0a479fd473fd4eee48ffc819d6c9a0c2e380a95417d5d0365c0ef74b2567689ee6522e286326a7ec5e0e88fbb68a750de0091c3397dce1cdb774b9e299a9faae0d0337aa452167c8b4da52e3d7846d633793fb553985af0a62f3167d198fe7157a491c19b864803f00dc0bae278b48f1070f6c777e621a9a60c6caaf88d6b3db876ac46c22e1d0f4f1925fff44fff934ef57fd7535155b5eaea1d8f629cf2668461da710d6e840272c7a793e01b02b83432027e6624fbec675fb4eec0d58eac349b07ec3c32d5ea07554d245b3089bb3f1d890ecfa3130ee73225e654086b37361df015eb1c366bfef00fff501b497aa74b3bb68c14d0e23bd66c7c7dca3c23c07ab50db8914b068f53706676ff724a06b7aa0f786df9d9cf7ef6d197bef48f3d0a3ff71c275d723c8d3a3903f5c15a9417453954cd4653daca6e08dc3c32b5452b5e8065e7949d6c3eb6c468cecd05fc5c8d958a168cdcd4b75204b0d42c0c96d99ae1e8472fd47e7806ca21ea6bbf3011e8aa1fcaf8234d68fa4e273b0b8d39973211e5a8ab82f0ba610abaa7032ab3ae12e8ed560db5fcca23d312ce2ffa4f0b5a4fcbe29fcd909498d4a0701212eb1f39867f380307328f6701cc08e29d2535147685d859a2527eaebb11231b9474aab3d10a645ede4490432d4ffa622b23ce337a80cbf9468e3d71b6306f14e7a767794f8b4ec043d28c1cfaf516d9e6d7ea05a772f035f5c9da811d341e92739a04b80463a41bc8dffc4dcaf9d4008d989186cf03fc964e85b05bdae051d653d754e8bb3a5c4c603b9e4fd7f961b5d611ad64170a85a91debc4a79ed5e7b085c7a7d578c7ec3f7ced3f682dc789f4d7e5c7bcbec28f477cfe0b5f901d9a1ea917b70e783380d317effe4f31744352a49b426581e7b58c6c7aeeb9fcee17fab2a9e1c70be820ffe32ba6b77f4fcf9f18fda95b68cf3febad0b30f4cea1ec17bcc9813cdc4720cd86d713cfe98d734d6ba9a38febc40b1d9767957ff65fffcc533d084c032d8d53a783a88053f7f2a3445aaacc097f4b8677e8b841b20b6bdb6503ba9586f8cc976fe1fe4cb781d08831e91a7c4586c8d22d2078861d97d04c4588a1ff8927d3424632a6596fe916c10b9c3efe04af9119f638220c2bbffad431bed3c849dcf1bff9f2377dc29ae98c3b93f851b9b1239deb49c984297c192dcacfb174c174eee0feaea31a006b1ebe66c41637cf6e68bc38380f49b5f7245baa1b9a860fa323af8d3ce793171a1a6c04e6b18ee1a5401a32a382472ee9c21d9807d87ffaa7fff1d1d7bffe754fe198b671f7fec2175ff23b614c117b87af6f912783cc1f18d3f00f7de8577542e2d7f48dc87eb64e3e5063785a23d1afffc6a7dd61f9981067f868909c86ffca57beaa6dfbaf9bcf537ac44283e6aefd9aa6d3bffee9df78f4984684761c6878d31adfe2776e56e8c533507e0a978ff130f5fbe4a73e69df3125fcb67e9df32bffea5fdbae275469d0623f0789d9ece006927ac29ebba1372f765599ba7ee32fbee1d109bbb01f3ff173b79c03fd757de68077f9b87920e37f697dfabd57b4f12359aaa0dc9fa9e9711daf35d11eec4a5d7ca315d74c3383c4749229233f30cf3afe7c231b6da98787f44f1d8175acc93a3a003c098b0c6c0775306b7b8b4b79f16d01382243591cc69cd66b308139a4caab2f34fae903a28e8c3e1c8ec8c0967865710cfae48f8ea3d3fd1a15f1240d65c195e7d38ce0675a91a905e57418bebec49635a3c6c7d480325a7dcccf8eb873771a635bc40346f9874c86bee881dde84e47e5a02f5f5bc2e62cf8b50e5227e36b56bc6b05ddea3462c0c3eaefbef28abf21829e9e06aa31bea90ee9cd12e1af673492020ea1fec0df8cd03911f261950b41ff393c8fe778bef6f9cf7fe1d1efead73e795914db91cf949d9b0987acf10d7e830fd3c2d7b5ee625acd8d855d366878d8ff71dd707e438d99f534a33be73cfdda8f9e4f32ca7f503e7b7a8e4071c3c36ea6d2acff1a18d568b0c0b88975e3051904626ce006440367f381e92c3bc1e84d995d00bafe5817536f9ffa944ebcb8f3a56e5ed0630a74e207eee1856d10c43f74aa797bc3fe124715f34d1ab9de3796c7f5e9baf744437b65438c91f113fab4052f0dbbee5156c1fc1457efc62ef4c56bb23d2241704522dfe072b4319ea0e390e2505e1ea531b92eee6832a29de01d39904e4605ab49c05221f4f0200ddf3887b204b471a97058fc3235e0812a77b05ff8389430ac17eb8ff083920a607a4727607462cdf171bdc1cc3a833b31dbd794737774658dd9edf0a814d924a41b391b07773442465ec37127a321ab91a1ffbb6aa8bcb5fd863a19ef6879ca2a86dcd97980cc473891cba8f12bea1dd84d63e72e4c03798a6f7ee866446805a323e20d558cfe8c5234021aa4477395b28b4ba7e84600f4f8055b59c7d109d9fd04ce5a0a99fe64b562f464d4620b9bdd57e858af7100f9577fedef780d4827e18fd32db463fc049eacf6a8fe918fbcf0e8e5975ff6d4f23dddf128e3737e4c89e1dd351ff2e9d84ccb58023075f63135356e1a382f77b25ec537ac31a90d467d3a2cc7a6e0851ef027f02ad20bba71326be0a40e50d3ba981bbb4661cd00bc63fb910f7b94c627dfffde0fbc4b9b4fde65f47d4b3af188846fb0f4a6800cf899a7d2b619dd463ee504b2fa7db22402ba8b7c1247491a87f0cac18c80b9d9ad862714e35426e716a908a6172c8871a8b7f1d588549bc64656f1716282dda36439020d1e12b9dbf27ed9afe865c9b7e747dfc0e07b8fcf48166f49bfa0172bf942f027b58be50fca68a4e928c528618344033fa4590fd563ed851f38dec401479da18d1d1c170bce8d84cfbdf1ba0cb6bec5dd57d3126ca2e1f80c9dd6896c289846a4bc21cddd9e11855317b24cbc755cca0fb139aaa5d3175291f51cbb943cbba16172ba850692c0b7e4e79b85b29707bbad374ec4f3301cda8dcfa64abe3dcf1a8d87ae047cc2b4103bf89ee45bffed2dbf0fc65af70d6df9d3b9fee09ffce1a3cf7dee458f20d0d8479e826bfaeb980e063c5f9ec2ef9c47443ef753dcf5d6cf3432e8ede41fa8e1b259c39b14bc26d3e77b6c56b1a387edfd44439ee5650aec1b1f3705dd54e9649c78f9f9e77fee1b0776730382ef2735e567fdc7d7b4b899b08efe9046736ce60bcf1c46e6540c6b3ea6b774a63ffb2fffd5afb9b01b3c2d583729de969eadfc990d603bb21c1a273757bc907e71ff4f270d5199b4c2a06ec36b3c1c1d8d48a4fb7949f2345dfd9377992e72776434e359197718eed495030e21a35b64191068aef0b67ee1e986a1291f7718ee82be838bcfd31a195ed44edc3ffaeddfd667c93ee53b1b0dd1d3314f1d24c777445c419ae6ad9468d10355321a509832ee9cc8603d4163e7cef782d621bc6b060fe89e52da6b38551a9b1faa72d3731766cac88e97030214d089864003f0e708cc286f15f3b5acafffb9d64c924fc7e1c1280d92a9d13ff9833f70e3a541a11d3ea51331527352c575a632a4f8fca464d331d978aa7f19fd3ef399cfccc642def6fea83e00fab88ebffd0f3d9bfa933ff913e9fc5371e006c228fa9ebe85a167651a8d9f7a2fa346ebcc0e005376a5bd64e6c05a8da937fe8a8ff286f3cbdf7cd9fe60faf823751436867c0c4a7e823e4b8b3e86507d4827cbc26fb8541d163c7ee4909117ffe247ec659ac9c846fd7cf1b75e7af44549665afbc20b1f5107cba71c98417052c8b38a699f1cf3fba8de00601afd373f99578ac40f399c11a5e333357d523774661d04cace50fb55b2cabcbbb80b36fa1506331b299433ae90c6e680238ed006f8984630a6783488f7de7dcb1b1f1c99214082be216d23870f95567871368ccec5cf0871e7f2e9069188da53379cce94902fc7320a65aaa45148bb729ac139b0d9843dc84dccf42ea3091ffda16379eaa63b1b77da1eb3a2417ceec5171f7df9cb5f56c5f082a88278611b27e3d1e9d5575f338c22a6c6744eeecef88ace4121a7d03fa4e922e7e3780625160e3494975ffecb47af7cf7db7e5d85918c1b12232f9b47ac53f838293aa76e9822312a72ce8e67490abad001f92a17cf0999bad151b8fbfbed5fed2ad2617ffff77fdf5be48c6eecd651feed6f7f47bb82d4790e1264da9b1f7f60ba87bfafed207a4c6790781a30a3043baaaffe957ca180ddbc60cbc60be74199d1a4c1a69229676a6b1bdc18649fff8d2b458b8fb851d02ed854e2f51f463ffcc308d4c3c1d41bbb987c49981bca134fe8bb8ff237b678bc150378a077eb246bdb17346dfcfe82a323eb690e307ce6ef7d46bb99d994affdd0ca19d6cb0c6d692ee0045bf95656cb4d387060cd372ede35be9653d9347afee3c027748a800d0294f9999cc00363dd7f85954607bf731d06bfe8161d29df32b281c11ae879bd5b16c7cb58f51c1cfe536d8dd338dfd622fc71f52af3e22e88520a1ea97c8ba653b1d07edb0b6dde1ea6c3ae43a77a44c07481c6c7f40b63b081f5203f20f7823a71a69dea64b291dd303ed0f9c413dfca08291b7896053d77c34f7f5a8b74dd70508406051feef8ae345718532f7d4e417a78ada4343ea403d37019f158b3744d835d5e0f6aaac7eb2d9efa68c3201b2f1a7d345dfccfffe53febeb53dff2a606a7ed19ddf864faef6843845d416e48f52bd33b3a24df57fc2b750ee0a8d55dd29f680dc76611fa104ae7cc714157a6eb1f13ff6fbdcc06449e519a5fa796f297b65c6cbbfb140d4541e27cb3cb2b5100d290e924fc28219d9c43004c473ff2c247bd8efac637bea13a7bd53745365e90fd41ddecf02fa37ca7f84c603c9a2384a669c1b9f1e5918dd672aa5f3e2c449be5b3e4d0bea9293cebdd679ed1ae359d0a9d26343d4dcbd0f0d5cd865c1148b7e08491be3205f71a8a6787487e55903a6e24ccf53d0d50e500f136be8675e32f63a34ff440eed6affcec99e14e45d328389540434407fee8648c1cfcbdfb2e3f00c753ef3416f40e8e76af98fee92ec59d9d1d2f7ea28711a827b0d95982973bb0e8d09f0ec65a8bf388d052ee06377efa80a78c7c80f549cfe72b979b0afa9c9d431a79b3e2831fd217a37403e245cb36067c40e7c91418ad7dbbf24606534f6e0a34026c816747bbdcc4b6dfe814fffdbfff0f3fafa29180cf4e9a22af13cff51afea673b05dcd4f19bdaaedfcea8f3ca6c0f1697624e1d536e3ba72cda22b6d891b489eafa113a33332536604582f7aece66607cfe070139da35dba09f1409a1b023700a67d9cb37cfeb90ff926ca33310e01bcf1c65f7b4383352edf7b61c3e4c73ffeb1db48f99af75c469d258fb6c47a8d9f0b6616c3cd84df2ff8e847b583a99b236bbbb323995097c24e7fb46c8d6400eeeb4855ac71091b170e6d9d5d5e78945123af5f509e39360d92d18ab93d2319d38727d4981aa82c7bbf002a4ec05356ea21f02ed8691c2c94dd14e53d9cccce1953b52755c975288d85a9d977b4bdfbca2baff8213623c34f34ef7eebcdfce6d892251dd0f7b1c73a45c086d8ca6b303450f875670b1b590bf06880f7da68fc04a6ab8c823450d60f39916fe778c3819f58e246f17f7ea105378aaa88692b7ac41f8c7cba79a811f27c31236deedae8ce0379ecf9a6d73a79b00c1fdfb1c58e4d021a319d8bbb349d93f013ad3d7eacb38c6c0865bd96f51d3bae9e7aca766e220446633649904703640386b07ce55d57ab29a82bd1bee0c40675d4b3892e533153756e32e58f5f3352e95755d5b8f980119b133cb466d3854d193e05c83a96d1051bb8d9e1639eb9b15bcaa91ba6d3746af462e6c0f49132df08ad3172259f36eb7c7c2c74ebfbb9cffda6eb0f3ff1d6033383a79f7ec67ecb8c255490b65d9e6d3f75b6716e3a599d65b9ef7329a393f922a155c48a80e44d4fd104e6ce4ce560308d8e4ec6e257db71535910b653d1986b48e225032ca1528e13b8cb3185e0ae4501f37ca6446c49b36bf7b4ee40c1d7c96e758ebff88bbff0af40320d0417fdf45f7fec60714d23441e70b44aca095d72048a7391f06357b181ed781e2bb016e1ce9fa00f0969fdf0d73fcae97ed66c19a1f2022a7766604c293d728a3f9d021cb69b1fd3af6cd22069989c20c9d4e835a799d6f2a07b37561a1f1b22b7218d126b70511af78fdff8891a627e80904e46008fcd0a4e7870f3600d062fe47353e1813530eefa84b405cac9a4ce98aa5237d435a3019b099cb1e4743cfd9b76835d4fa9f1d2a1590bf1cb2b9fd21bde6c26f1e901ea13f9cc0878d19487d069d47994815c6e44e8cb71379ebbbda64d9fd915c9145d1fcfe1bc2a0fade18796b48315946916def06277d89b28dc4cf91b5f7a73ec8658dc261f1fc4b7d73e71d3c92af82428ec1a975185c4f8740e70517d2b9fe69906a3bb8ca66e38f8ddb77230352301d88bc2e2a824c2c5ae310c5c10b8fb5dbe2aac673454b6bfb7a1f7c3385ea5f69e2032365f18f50874301ceb16110c7114df0a1d2546156350c41f3788ffa3e909a325cfba980eabc43710eeba1c80fddf3fc83340bcc1230b46204633d60c4f68c70cb9bd63d2b0992ec18606fcbcd64974defe2a26233e811d4a3f3bd20da2eb1cfc9fbf6988cb97e82ae943eb860a13c1d816673acc9497f596066007f8b063ca9a8651d98f11e4133a3da3112343d765956b42aac481c61a8f3da11187b5135fff6294c53faa01eb4a07fabc3625bef4a57face7541c877ad623159d8a9b048e68fb22ee1a13990d24c9d381d84164adfbd64fdf721de057e4b12c61aa9a40edeeb689369b1b197259e3563eb24f99e1f3b7bb427bb3857f32fadb3006e70c273d0a5a5d706618a39c4ae663921eba85c050cff4e7bd47d9ad0acb9a2e67180027fec44a45f861e3319dcb7391de598d278e4c6f680cec32d1c8dc9954886cee9edcad18056c6b4542ac505b002f159cc0e18c72f0d70f36686b9a9d464691c7a6e3d28999fa71774616532ba17baa4787615b1c5ad60d04a620ac7f5e7cf1735a9f710ef1c3baabeb41b9a63834ecaffff99f3ffa8fda4e7f47d34c1b6f2a3555f9e1f1e1819e09db4fd88c6d3e32a4f8bc99007bcca3bd1e0ceb6e4fe761fd541e74008e693122333db401628d9df96ebc3e62fa29dd28241f19f6a1eb85ce909186693a6b5ed6b7d88bfcde4446909f19f223216c60106c872ee8e7338f82319ae84af1c849da005de0cb74f4a31fe18ce9c734b378d30f973974f009edc07efad33aaba972747cbf40bd83c59fa5d2d8c80f6ddb8581ba185f658d0b3f63d7f27d8427d283e9ddda6f8c8fc3a55c1513832a8c439896f0b09603a3efa88371a761edd077c0c0defe889198cd2b3084c663bf1b29cf3df82e1f73f1777828adff59a8e7278c681c6e64e2c35d9235111d8d06ce2b28345edf1624225246ae1541b6518233f6602777794626647974349e46024d4f19cd807123e11d263a0cd32c5e3804c69acdbe920eec467ef9cbbf6f1c3a1a0fede9a04ccfd8ee6644612d4760c43c2b151e344ae206caf133c78e7843800e0f8c07d33c14b7414266947f5d5fe5656dc6baa56d015aa6602fbca04fa9bdfc2db3f51454f5c4fb68affdf055dd3c7e73753210189dd19751169e7c5f239fe17e551d55321552074a48576e3ebc9d4027ff35d98fbd99224f0d485f42ccdab6d5ced30774a22f7ef18bfeb2338f45785ec829a05fd1b63d4b147e56f90cb5b371e4dc1db12a8bf25b0ed22b445c6f7c1fccde78660bbf8c4e81a67ce002be71272efd8223f442eb064c135503cfb3b27cde8bcd0fbe248473b3930665cd210ea7ddb17699ccc03aa3d36069d4fcb6f19b6fd318d3f0589779f3439d8013f3a03395611dc781523abbcf3d9a027e4a58fe6da305ea3289b706a03212cc91231a0c154d21e7fde8143d918f0e662be16cbff379031e6abff79e7ebb4cf894b17661f4e8740e18fee437cd3ea42f733195e40736902defdb5ff80c1cd60a14d0a17d4c4b6b8aecc4e953e29a86f9e3a9dabd64fdf8b57fff35efa2c2278d3abfb0c9e8c48d888d87d62323db0b2fe8a3aa426654f2da44a27ea291986753dc5808c5471f4e717c552f587e5347a9b82930b56ddd22cfd34068f40f3837a8d735d2fdba78514ebb82df19ca1fd8436d145a465dd672e074baca14f3caefe471c31b01928d6fc4c46927b94c4033974f7c935fbadfca5c6bb20aaed0f78b2b9418da3b860c42dd85531da4a1371670282399d62054561a4cd61c1861539308992df3c5b2f0012c6da43a2e0d9446c1c3547608238fad6d7600f5babd627702b988c6c2363baff34347473167464afdafedcb27166449146af6cb9442ffd4c97a028406c5c848e56017a709fc5bd4ea1c1c194ac8fa801d3a773cd1a732b75cdef65efe142f1a0fd35ad647dfd516b5f74e473ea342a675fcba685e7e64eb99b5893f55a0d11d9b1915d1e9f9e75ef72e220db01b13ba2378cacb94903ac0019955e846a11b179b16dcbcf8f6093725463b1e60bff8e28b968d5de89bbff88ee78c8c4eee54d231a39770843b5e4c5a190e41335233a5c796f2bbb62717cc65f9e7c83b293d08d0f6885d75bbf223cfdf959719e8e28f04112fc0dd36be781e72412f1cde049d5d0c7105b6a0b1b17ec9a50cef43c1a10ec89a3fefdb69f8c6f1fcb198679e9e456d9482cefa388643aa26ef98354fe5c216fdd97acd77eb69546f68c7cc5882f3ab1d4c85a844b6d5192d098c34dcf97820cb668425989f0ac5f8f44b6d1c9f797df7ae74d71ccd9db8fcbdce81b9f830aaf0909c3f5eb9608385518a69229d8e5d471af5133a108cec86ca228f0e6cb97fe8837ae8aaf5c5d34fff2febdd4fb97d4c47803ea63508c7a5e8d03cc361138829303792b30ee18badac7d388d6f9b2598b5129dfed557f52b969a3ae2bfdac90d8b1313fff25ffe0b77988f68ea886cb6b5f11f9dc8f53404c8e4c12f47b3befdadefe847f5b46b2c1815048aed941fa86b6c07c6b346a6af4cbbb999d46ec7baa45e84f840386d3c514ef8e95370ae7960d68f86e41635722998603b490771ec36603076846cf04da3f4cdc6c77dc2212dfc1a6fb63b75e2e01ad4b673ad7c1a2f0f74991ef13c83ef6e301af0611bee64dc65378f4c8b68b45a328b539c8d9d092be146cd3314eebe3672a47246d22f70eae97df942cbbb6dfec109dded691c2a0cf7f8f906177c57da38cf79f1e71feb39a67fac45986ee541b04620d9c8c89ad14d14e3080ed1f292266710bdc3a84f1db080a47e91c188839d34c25fe820ed9bda29e3ad6e1e07fcd11ffd9117f6ac93d828e1b57c8e42e14bfc96ca1537d1121891dc6ee028b6fcb81e0d99918907efe0a92b7a26c107673385e5f44dfccabaec131fd3b1349da86073890ecf549f802fb79ca9610961f3898d1bde1c785b1d47db435923db01599351cfec247293e3edf2cfe81375ddb4b29f2d212e9be44d74d663f1b115bd292b2cfed8ba9d74306cb9f1f1ffd8ed322e134c870309e3d04d1bf0793d75003ed3452a288eb3c083e254ac8c8fe2db64981856dc51cdf5e6d9987018c1f80e23bb8cefbe99e343ec6ef17e57471a98a491d0f8c5658c9c48a570e60f2766914fe361c1cb0157bf02af125e7fe9f12aeea0d3467c17e67508d63a34203a48aa43d7246e2a4bac5c81c484f83a6f36b305ce68f68e4e963ca987d67532ef606587910f92f29a46ee6e8caa1cc7e2392137853c5b5203d13f4e7c33d57aedb57ce8e7fb7ac6c3b1287ee2f5777fef77d5c93eee1b0afecd5f74a1c3d6e7d130d7dcb4e23efcc85b0b4c2d797192919dad733e61fde28b9ff521e569438b17a3153f7d45c0ae762cf2a73cd20446384eb9b3a5cea15adeae7ef2e9279de7fd33def1c206746063e5b96773e4890f9c12dade4ede2e980b62d0b13e6e9cb6103b4ffc5f96aece96d98625e6c04ff96e6582d32c4e9aa64f5ce435df783a19957fb7f024205d0790be37c044c19d62100ca9018ae968aa2a29a2c5be461376cad8c2f74369dd59dfd5cb7270008bbfdca159100f43c39b6e2c3c7514460e3a1a9db88d818eca193446842cec67f3430cc1e7ce4ec7a60cbd6341f926beda6de7a908f87bdac964eac7035d9e35d16708e07077a661d1917ff643d68991c0a8c12f8c700c8a91ae95c1d499531b5ffdea57f585a6ffe987e8be3168c7b4777a46ad066c8496d018db09e846391ff461538687d57c9a8e9feb653df53bbff3259fa4607ad7b713e8500dcb66f1af1c444d151b0d9ceade0246d68f7fe2638f5e7ae90b8f3ef9237d6c56dbeabc65ce07793804ed35a2a6a17e1636a3766bbbb2892bbffc9b4fdb08e68dfca1a91fcae3c4699ab285373618a6cbf56b5bc0859c16791a3f7e77f95c4efe809abfb3f151a34a0ce2ff4f301da4ab73c430f8fb6130d345ed64d18978a0ca33ad748c12c830a671636074603a506d56c200dc406361ca48e7e5854916f5f0e4d914533a1a1b77da1a4f85d3c878798f1104fc86550902dc9786876d94baac65d86d23e6dd36027ab253c7484647e26d63f760e1b353c76971ce1fbea74658fe58cefa14383b813c4c656dc74d80ad767600d7a302e1b64341cf1fb6f2d0155bd87cc88164bda7f5da6bde5667844487977eeb25adb53ee35318b20e754d6b7b9c4b032159dea4b7ef7779714c2b04a6943cf7faa7fff49f09e95d4f69f9e9e1ae15cb83b83edc5e87dbe68dec5327f267b933970bf8c5a368a50ff8c9131cd3109ff893be17177fab5ccc215e3a5656635008ab9391a982205d99df0783c6018f8d0306602518b508272ff870c7a461fb472894a7e1e47855d612980b3b6fdbdbf2f0a988f09b0264205e9d95b93e53c027d8aad794cc5ed385a928d323760019e9cc5cccc0f7f3140d3facdb465d152755bdafb62fb86d9bd3e9ea148c68f92647e8d9aee6bb219cca8726eb068d9aea0c3c5640a7ace3520ddc70d08f0d063631d848e186843aec46e65b899a92ea0645874aa7c277bca6cfdbcb7cfaee87deade3cbba742ed67ed0f677dcd8b46064a1d3fa9c679d1a17fb5afb0b3aedbfa66f7094c172a6c97e3155bccb4bd6cbd6db06593f9607f9e217d618be577cca4e9afb681fa22b5f9cebef302e4012e14bba3ac129f2964cdafd11aebe018fbfd5c98a0eb0a14a9f30caeec02f150507688c37f12d0f8d663ad8e9c3aa6a447c229b46e0dfd0b22d31e894b3d3d10e35fde521ab2b7cc5ac1dd841eb68952acf43cf1f6b0bfa4d352cd3cd948a51825186e91c0d929db0ada71963ad052ef9d8a63f1a38272a807393f8a91a396b2dbe1da12d82608994691e6b8f27b50665dd047f8f569abef169364657f42030727f4067f978bf8c07d26f6a43a80f9e79ede5356d75737a829b061b2dec10d2a978f8cb3638eb389fa5942f3932e51dcd69e8f0eeda9387e174484eb357b615382ed8555f34a6b8e9fae3c47379794c9b40071c76e217c5f8d33e82be659e384e638712c82faf3b3837001ab800a3c78a0d4a9d1a41e5465bb43b077deca330f0b22bfaa94f7d73c2c05b9d2c1a6d67805822101f4a53765fa8f3ed105b2bac8981f9785577ab047f5b2f4872574f1827d8b0eda7ad0377c82db5701a0c8b79460fbd1bec7f540dcfe2983272b01657cdaac55355463e16e9de7e57a7610a063ff85bddd9aca043b13eaa2c46639e4571529dc5fc8b9f7bd1cf907c2a02290852606383738d3cfce60896ac319cd3e24cff38bcdcf51605f9fa934ef06b24a3e3d29921a1637de73bdf76c7a163d2b95e7ffdafb581a2b59d4e9160231d8b902dfc6cff1bc00583f49fa9b97f7b4bdbe6e7d473e12911fba3e70927ddb2d66ff3f54b6370233272c937dce244ce348da2383ef100b8dd9c157f839d0cdca87982d365ec3a8d2c171a21f9da7296221bf8701992c2922d4ee3417274eabe3f09573655eaa4f87f489f02a3246aa2ac4c1f0701a731fb95047534022319c7ab68c84c9b5063fb739b7ff24fdae4bec08f3510a7d9e1ef6f4ea893d07959e7f0e7e999d63854046b1dd6649cbaa6d3b8418b137c69dbd679f4e0a6f0ecb3ea30da286111cf81575ec1e0a43aef5ec187359e3ba9b9a7a37a4aaacec8415f3a99475f99c3491746b23e1f72454be23a2c2c7e5e4309864f3803c82f45f247e7e06b5fbe29d99779b0cf4d063e6d1a38103b634f62bd16ee696a7f6986519f603af0a7fe1bbb70ca818147b8961b785c520eeea6398aef4d9e3ced8f915738f17d7a8e4ae699ee253cbc70d1b5f4e557194b998351ed5c659328ed15fe501e3e6ee1252ce32a7312de07a3fc0a2f8f1bdaf83a201b2217b893e9c33a340c95f08ce6677a2e9403a4401a681c690491a5b48a706665c1b238de31d4f317ceafbdf3162f84eabf7a0c53398ef0300a70f4cad5203a46113a259d8129181d8d291a8d8f3246460eeaf21903be70c57a8987b97ef0ab972ce954dea0b156e831d58ccefa47c3674aca5aebd557f396317ab31e62bac7ab398c84f8014a8fc65ac3fd9a46bfefe91b1fe8637c757ad65e20f9599a78706340769fadd56b1cb3f25946acc4390ae0323af221994fead02c27e37d4398860b4e75274d80b6b06bdc7262ca5a5e79c009278f339dd25c4f9af2212ebc741b461972e18fd708b9a1602d7fc125a1bf09e553be30b8035b04f1db559fe2972771f96dfd22bf65ee644642e309256a9eb8b093d1093f71ef4b97bb9d200fd1387c86513b68ac15d815632b9f4e468388abc229cf97aa832af4c611e15cfdd814a0a3f123813fa79311248f8d15a6676c103ca7c3c1bed3e9ca79463a52b7d2797ec3793f1a3e7f6cc1333a72a0988ec7ba8f07b8c8c3177996475dd6c2113938de61a4d3e878d2775e79c5272c40a513785dc5e6874626466191b8e1b3aee4b57946b5b77fc61a2f36b8330b07d9f1891a0965d64323b63a641b053e641accb7edf906e10bb2833518df3be130320f8273bac31c165dfd08db866b9d033ff15a5ed9a5233e61679ab22b8fc2aefc62af4bc58f29064ee03f1d2bfaa794eb6d90ab1c7af695cca298c2a50705aec65dbf26866670af36b49cb838859107dfc7aa00c27b0927afc22bd1096b9a985086571a1752beb823855c0cf1e70898beb1f5ac8ec05488edde3aa7f477e318708b2783d430e964748afd592f4dcff4bc88e91aeb97777f95d18111489d4c9d8617fafef88fffd8a3088d9167678c628c6e3c66884d7428d9aa4ae63b8a84da5e658da7228f246af07c5b846930cfc3e22fba0bafd7b38daf75e24f34baea0818a32b0d1e1cd674cfea277659c771548ccf056cfe48457674f14b84caf18c10bbb1851b02370abeb8cb2e22a33037113a1c5fd20287d1123e9d1ea3776d899e3b1fffc65e11d917c695aea9f90306c23da1bce3c7e017ad65cd37bec22d6b462dd24ec615e4f467a863fc0b0c1ec8e45fca8349293e3d651863f0617596958fe974818656e54b00002dc1494441543f7eb90bb760a3d5a7c499a388e0048275cd976163731a3cd285376e391a6dd1c29b02f8b326ea8f50d07879a9914ed6105edb28e0380c1e5b4e39868a3b3823156ba04ca34066d3e21dbf9ace83691a17a327816f7ffc5dbd8dcbb31d74829ed8f6e3c8d9ece8884565309d1442a65bcaa30b3c3997c86302d67e1c85e24c1edf17fcee775fd16f85e9cb4c3cbb437feec6529b0ec46911e2e79f8f1dc8e5d3644c4979b340fb1a597b89d241e58c7a9cdc6033852f637134891f33e75d2a46abe798ceaa3cc7adb25e832fb209341214b08dd44f000057087e7189cf3123680fd1175e66e545fe4c377f957fa5b76ce97faa8909b12657724d5137d8b7f1b175db82dcda53dee09b7e1359c2a96f71cd1e26378136730358b63ee12e208d617635eecc9f69585df8993b4a10aa8c33f015dc2510a1893636e800dc8199327a5da3221ed2bead861a3e77f5811f7c2a677282ecbb121d97bb36d33ff8b26d4e77622b9975191b0dfbcc60d4a163b1d942e80e22d3b986a82c2e83133c8d541a1d594b72ca9fd750f8c547b6d1f9ee212f83b2e6e2847f6e1ceac0acdfecb9543a23186bb26cfd338ae506c248e3b784d5c9d80dc457cf7c4067fd34c2f2e55ea67b1c877ae123fc56d9873df5c366be1ecc7943d7e5f8045ff9cfc64c43c22005e0ad57e2c20a37602e298dc749e39dc28a575e273de96b70f900cfd2d25374ea73a56ffea40596bcb4b25de151f9e557ac1ae069f72676f3ac75e3928abb8937bf82afda14ee2d7c1a337a8d8ae54c567ff71b7e3ffc8e60b30cdf8a847d2a07b979439ad1045a3a199f0c38479a2b4f57d0e8987438576df0736650273934357afb6d4dd50483270d9e87ce34eeeeaa859a4f54934ac74e8a6c3a6f3712788ec768eb1f43d748c5b3b51fe8a1efabea507fade34a4c4fdf7c8bd76a1891f3ca8a6f20ea5c7dde959680c3e9a4fa154e75b26eb8f0c3ea34103a198f0638018f0daca33ec98f5f28e660301f427d4653ca1c87ca4dead4756e69634fea0a9ba64a57e7a86f1b837386c2f133b4ce4fda784abb2eeb27e57738d3077470ee2b6d1b041bbe96e90cfeda37044084b32d0c2444a2640d26b556b8caa36cd3c3db226ee8a2ea965b7f3486f9e61151d09c72816a728e4271608d2ca1e3552561085185348e32bb1c9c06e308017b6de8200367fdc19d97f5041d8e1f07fcc53c48a5bd11aa13f8d56b4a4413d705873453383a99de15d33a843515eb210cf40e23273f3495634ac7baad7cdc01d411505255e3518f674e1ce0650dc7e90c9e473152f1d0978f5cf2e62f273cd8f1f3ec4f17a6a4d5d1bba678dba35724d5063a3cdabe277b7fa26d7ca68cc8a373d19c19593fa1ddcc7ffecffeb99f7fe5ed820fd857745a42a7acc863f4c5c6566ee5908f57129b5097ea08cd229ac2d2928d5f773c288ea8cfdb00c456d98fd8e183aa06df622ff9b70cb65ec3c954d4fb99571a1d09f03979610e3adc4a03a69042d3d6aef2a1ce09616b2617be8181535ad2183aaa2cfce629ad6ede5d2ca0711dddb870e2fb028cc7061757f91b207a620bc8fa03876d7c1a94a76bcaf34d07d6353cffc931580820ac91e4af015e611e1d58d3648791d7cfdf18120ce6d9925f7b51277363d6d4d24138347cca79484c47e4c02f0f7c33f5a353bdeee75a74d0f5f11a19c4c9954c77a5876ca963eb03a69dfcae32bea08c245f5e6207f1c37a378bef4ff0cd454f576d26f6e4fd384664a68a8b179d6902a91e07c2c41e611b73ed63e41118c9cbc30a18aa0b821a06175871cdd768b9c1410b09a8f82fd4bdf9011ffd26024bdcc401403a0be2e0bff898e77002785f18be8bff81630acae109dc31f22213b94953a8d4f0da3a8097006c8aad9f5a850aa253446cdf84027f8c6f8647e1e113deab93d14ea928881aaa50f337716d3880272d365a84a46d8e4276068747616f7e6834a3a1f25a0adbf8e7e60704558978f1bc618af35004a05ed9a7933d9fdf097bfc87fd5a94d67cdac1644dc6e84487fac563e7615a6d52682df583ef7fdfa315dbfd74484e8940872f10c108d24e6579962ba982af1d2fe1f939956888d19be929e7247990cddbcbbc6cc97b61fcaa0cdbe9d9f113330c8140c132f9d54a781ba2cb94b9fc848d7ea863dcf201aebf455f9a2977193cf9034620ab6850863ef647137806cfe8203794281cc4a40589db2897a8b127f2843c79f43a7572f9b08aceb77c5168482f05955bf30ec52f989659bd55b6e5d006a3c1d67b1353145a6031387602d70c65a1c2436123277fbdae8e348c9dc7c0412c7de0a92ce4061eac7431caa2bc47324601edbefdfce77a81532f72a2dcfeb0cea145ad342877118c8c51a97c762c1929f65785754712126b2a3e76f99d57beada9de1b3e70fbbff52b8a1ca6e57405af83305231aaa15d4687e8b84fa19057e9d8843ae033656344c5163a390fab3905c2bb556ca5f3f5297601f90605d359b6d27d83d168ca68e5aa396cab3d9859dfda64e118d7995d569c2b1f94a5acf562e54b3bf2285bf493ae941cd23e258e4c1140ee8d03d300e03ff2caadb1042a09dcb2a4536333b1452004bfba3ab6a3433b6a1b8d32fe85822974352eaef2a376d88219c08859e550987eec899cea52b5869642df4d456110f52ecb821ed2430e383a560593adc060ad2865308c90c6611c67816c198363e2e24f19f876036c9009922e8c0afd054e0abc8daf29631b6d940fad696c4052667493c716469bc7bd85cd8e1b3b983ca4c5234cf3f86594af7d2daf8c302d64678f45150d1e56d807bd0068681817cc41aa2b571dcaeb2f772c9e51e953d2fcd8831efcd291f834991ffaeac815a314eb3f3629e87cfc84947d2a866ca8c05457183bdd067afa99e206704feb0b2f0c07405b19f13b4453572d2ba1e50eb5a24c41274f9949f1bf02f9fc4f7a3449e701e108c3b774b60bfea00c5feb3924c67311ba03acbe930622b8db90cacc4f7c72546fa8950fa9af1813bc40c727f01b3ee05b18f4e228fc51cdb276d92ddc0ca01831a121532ba6c0387ae432ee5bc59b418c3c2b7b551eecaacd10444d048f736c18cae96f8461881521d21fb874329efb3002e038a68afe0ea3361104b4d13526a270468cb8d50129d182726fe3f3352a7500761311c8e6023b79ece891cef45825face8637123c748907fcf507ff9c22cfb40ff9f066a3e6034ff3830739c7c83736723a449d8add3f4e87a853211bfbb0d583231a2ab135a5887fd19c82fb6c5b76a2d7e0924ace20f3b4dec98a215212c2bf0deaa0133f971d75045578eb3a3e3097536900215cd858605ac15de7c8d79fed3783c0696f95315dc279e8135a3a22906059433ffce3a7299032c121bff1629d21869b636d520c0dbad6e720998f8d1b9218ba70e1017eebe44c9782f8e4cb4315979dc022b9602e657ae255b996c1cb2eba2871f2711ae348082f671879289d4d08b6b579f6e483bcfb25e01088ae32431eddcdf3700c15cb33231ee8f231183a1677a93ac436688ae68e851a439b6a9ece953d7d777ea676eeb41c8f62faa7d7f5f951024e8770269193193e6e25b939c132d3178d546e488c58134efd698484e5bfc1b9134d1d9dd6dec1194071e05c7b37eec83bf801312e482e1607df7daa5739aa7cd151cf786df81d3c30c9bba763dbb90b3a6e369d8b87f5f6fff05384a8e4504c015c1185ad3301b9b01d77181ad6cb094b7acc108224afe253da6e2be542dc1bc6595f671a1cf285452b6fe15384514b5a00eb0afc54208c282eb35666f3b7d86144599d4925368d583f98f62e9a3627f4ca4b7718a12c6f1c721b90724aba4d338ae44333bc090d9fe80b9677eca443f4cd2855dd4164fbfd397728fdcc29d33ffd18037ffed1038d8e74b8a7742a8373895e4fc1547fe5e11867236bb45ceeddca3ce8f3f211a979da076414285b7515a35290c2183a70e3026fdec978de2aa3cb3dc19a1f45453b6bc0691b654ee13272e85826273f30c7abbee29560f57a2832026b671beb6e0b701f6d86ffd99ee2bf2937dbc82319f4e663e48d4f8d7fa94bd3a5bda0c3d66390154566e2a631175c6f7c1408e03694f1052abcd250d2f47dc2cf72611e8c700b0a73ea23eb1a3e80f31e3b8c1acd78987b1be46a3ae75a74d2d880c590ead083a0fc9cedf31fe4cbb9cf6963037dc54de6e57894f89830eb375ea8643382cf9df1c9343a14a7ed3959c15bcdeb1ca33a1f232f66f46e9ddff71ee6a7c2f85232b65e532878bd0c1581bcf186c6c0cba534055ba28d2a2471f9dce00f9e61b6fb283d789cfac00d5e77dbc4adbce4a897812b019fd5d194aeaee13552c01f7b17292c863e4e0a2e55cee08aa788dae902d9570a634e398e2c50ac45cb0dd025dceeb3f16ccb6779ebd31cc777f7c12a8178ed2e3ec4b4ce3b1d5ea627cd29d4026c9f2ec3a066bbcc192a306ef3ef49ab01f3a6f45b8ff2d96e7eb8fd6e985d3f7bbc1cd390ab1f3cb19d351edfdb603ac7363a9d222366a6926c46d0a998f2f9277ad4b9f8fde0e7f4109b0f9f428f3edc00ca1319deac38ea0e2d92e51a9dea9fea5f3f19031e1488a969c993468842ed28b7c84e635f65a201bf7c4d785ee0393827f88a6f8987dc69a14bb7d0c2cba061153d6335e9b9e10e1fe0b5a5fcc0b2cdc30138e885e311bc511ce084941f5027ebb79443042ffbfc608ace09200cae01f7fbc6f425515cff5d7d06ca897ba6970b2e7cd2c9545aa64739ec56b68e3bf14e010bb1097b480e81c5b0c9dd887c8006e3008f667aa83b1b1dbcd0d8d75ebceb2792387248cd100134346047c51a3702d946f76b2a8af9dcf5873fcc0fc865f78fd18a737fcf3faf13f77ae8fb943e5bc614135d08b58d7857988b5c63a96ae1f1cff2a78cc672d1a7bc8a418cf6845589d44140b92214e38cb94b4e5e67fa2485c6a4b7c0c5bf74b96521a2d60c816447626c5f752fe81dbd4d1e287c6f545e4ac05f1ca7bcd6448f29abaed8bda428a96ce517c5f69141acf1a7c4fcb71f116f198816f252c764d56268ef89eaa7c6a09ce92bc9a9ca5976e7e3a6adf4fbe293106135fe4c2f1c6c10ceea58ca7ad88fd7f09c51e1c133a81c16cea8c12b2f6ff92de9d95a87661c1f23715860312c0edc0e489e337e2fbdf492dfa7e2b47ad65473429dd758e621385590d3f6a84cd34ba87dcd9ff1b6397650165db65f8abf704118bbb7aec1bae6afb476e70636b5ea00c092637f212a1d1e992e8b829bb629c1472de31d6a0e463a9b3363eed6b7f65743e5cb8038ff5d7b96010c7f4f7d06b698a69aa903351c305bef4e5e2eb649b0e2c5fed86d199615a2d83798ca801bfcea1f3c600f85b3acbebdc6276d64b2859fd42a2ba35381e2b40ce40ddbe9cd84541a9b699031ca572930cc43658c1e3e62a5f50e26b326e3e76fd8cee76c23b4fc0e34f8950b3d32c425a98b7390cbb4ef377ff3f38ff8e54446447687cac3ae1c1a47f08693e2d34ed2d5730439bad5c3d642ba68179d594e2307e112e0036ef1c3f7b6a25d0e1ffdd962f3696e33ac4ee5459cd0268d2de878578f8d9b1b54e990089bc55b05a5367b659a2755895ef38efcece2820523fd473e8803aabe754fb51d65e31f214777c860029f252d79242002be47a8eefeb0108497b06d877eea5b38271c1efc15d63886c0b0ba6cc382a3fd8653298055887419957963b38450a1b0d2196867d6e1c4a95ceb811384948788b84bffd8fad654913f3f3cf6e687ce08aa93bdf794a8fd6c29cf5cb28d6a0ed22fd272ddce03de69df934fd639db69accf84b0dc5d1b805d43edaa2f5a5eccc29b4ff9ad1fe1513ea53fe3961197df594ebafc1bb7528b5f1ea5cb888c5c410e3fddfa8c8283a3fd068721306d18580ec9818111eac1858cb2092e1b7b521ff7e3816e1d4d170616415e84b91f0802f9b0707bda597736dbff80ffea23e8d31647ce3df8a71f5d1f217ab05ecafbae5f3180a076eb68304e01c09b0f231a00509129715b16f8ba9ecac71e544d98bc677baa15afc7e8641a697856c6cb94744bd664accd682cddb88897cb691895af63606dd02947d7be235687a0fb7dd4b7acd022a17438e0bc432f7cec5566fb65f3af9f16ee24c03dff4e3cebf78082c56b7cf22d3ffc24f62b24d96be452b871ac7d1afb92bbf15741d0a034b11eef3b462e30bc60f2611c0e672742aa492c7bcb0f2ca5705a82d281426578640ce7d1959cdb8818a24b777e17bf819df92b5e6d407a836193dde5ab54897eeea13e05f94c83ebef2e864b9800dc01189599b8f0e4aff837791ab1feacaf78d8f3c424222e309c34b88c66798193531e39b8cbabfbfa092f8f708fdecb540fc2ea759bae7eb01cef6f9053c05b567d1ba3c768689ca6cb028ec0aafe828facad534a8c6b9bb73e4b9650d0e3e4e5f4e0c3c1e52742d8da769227afdbbc250f3678d84c16792ad3ffe4ef612eacf04d67a9176b3b3cf86726b0743a79b8059f36e342f142d6b9913432673d3d58a6eca39972d932547cf865fb59d63023416a541876f7db157fa6ac69e20692cb7665f694ba3467f9a9129d0a3bc3abf572a89c2dfc8580c4b3d4f9a851e2e4725d74a239d3f0a8a9964dc68a6ff8f28c74f368365be68c6854242f70721c8a914cef149bff0dcf51e4944b1a1c9c5ff8a089cfdc30001c3646357b3832062f2e0375db565e0fc5c5b51e95a7b8f0d22ddd2893cec8e26fe942dac6fa32f4c0843b8a511e3ea60a6b034313bca4533857d1579f8d334cd1a024035aba463bd3a26c6d0c571a6509b75ebb43a66ce92e06d681d8bcc40514649acfe605ffc51b06faa3943ae64c2a32ac23b40a1325735ccdbe0a0c7cf1759e0e9bb0e177eb0e450f534bb2755c909dd0d9c55b07ed225294393293ed706cbd4bb795db5cec2e59beca605863eb11e51fd7f941bfc0a9ce066f4eccfbd3dddcadd8fb1896f039653badb29647e12dbf29f0a801d383af3c53bfaa503ce2ea6a9a2928ec2c275d9c150b76e2165edc963546fe89236a9b80ba84e2b91eb695cb07c5736cfba04addc017f872b912f809d68d9383e66ea8ecea575e9b269c2c3b0a9a49933e2c200858a7447286e9e27a1b020638646edd9457fd3b3fc28d0f0371b07dc31d2450aaeb70ae24185b6875dbe566b62edbe6055a75b021b08b265b5e6067bef8f9c68772149690c222c764f4db154739b8a5692c2084369b24b43833ea40a58027145c06ba4174b2d9fc9875d9bb74329d616444e3c33279d5044ea187477921d750841abc71aaa70b84252b4cb777a6543274a7fdc1eff52edf9634ae9c60065a7ef525d0fbd2c51b2ab0925c9e83ab60f69d3c6721f17f69c6848376580011292478a99c5b57959d6a59a52adefc83b3cbd4f2cd74aa720b5aa97a61e489b4922bcfa8a3b4dfb9b37e61506a72d19bc4d6275071b402624ed920b30b9da0d8bb26cd878a6bdba5495cbc7186d8f637ddf8f409b0d6e5157ecdaf131fa7f12771055053879b0d2e4d63d7a64aac32c8a3fb913cd8a9b9cb21c8e21faf97e459d94c0d754a9ee9e2db3ea80ba32d7deae6802cb64e9c46d69629086219dc92ddc9c127b645f6b27330e15dd88d9c4b3938d772f2a75f6e79c975878e29dbfe0fafe826369566f7972c7070280e92e501506273a33c1555dae48d4de10aa74e0b784f22fac506a7c3fec626c8e85cf0b47f008c4f62eff6591e0180104ba64bd906518f6f494dc9c8ab1d64b9b9c6bee02c5b65a6cb75b11edba1ce5b28f4e8a6bffb7c50f83586169856a538f336dcc728aadce29103f70efeb24008561a1991e36bcb95893f52e68f9db28dcf4b8c9a26f80ce3accb9081c20d4d3f04bfe235ff508c1e573bc8e79f0a0fd9f7f1286de2f8a43048cd0b7eb51d79c328acb18f7280d6c6a5b5b33c866422fc519f94dba6ddb82dc30cba576890e5bc6057b9e8006ceb3b366da64e51ee472207bcb6026aba3b7ecd138f00eba04b4c213e02fa6ded5b0024f4604bb3c10116b6601a6ec306c30fb9951e9871aa0799919df2f826e0fa0bb97743f1af3198c0f859c8617ecba0047759de42ec848374d189af2b13fe54aae544688dc121569f8b0c6434e35d2da68ef06177f1e77afd9f2d78810cabf425478087d2c5bdc6e0a3f719c8a1e9b5ac4db2e5d0dca1bdf02a0f9b6c315b1eb0ea0b9ff82f1d2cbcb9de17ac9d68a1217dd15fbc228f98f2db808abb9cb2f2b3eb8dbc75298fcd67db249865a5ecea8b4a3d7538d32d870761d12f058391ecf61b50f319ba60d161e28974a353df5848175dee50c2368a18e9d010a8e3a46f7d5abd1b57d7e61b9bc971295e41335d1ce5886ee514cff1c9f46454878074975c907a8272216f57c411a193a96ce3f3bc4ccfca28e177a47fc6f12aa68c3e57d8ca87651c76ea71ea07cf3314bfb0f731d568e5dd1860d3e577e6cb1b2f54975670b7a8efc737e721a751e42f7e2dd77a8db23ece280c795b3728a25f69930f1c3cf22d2341c76d3e317ad686966c9af217ced4e65ddc69d04374dabdd22374899efcd61d5b239dc89a0a70bbee0adc57ec1a7da05a7c4989ae72290b3fc1565af8e61dc2ab3da1111584ef134a5b1fae3599e96ce4764e91288be1f7397e4bad11c5354ff4c21278638481d70b95cc1f9d8c4d10d665fa72ae4e7cf0111bbf9d2c925b5a981280368d985b1d4f838d7e5c6ef4840b428f70e6cba731683b5dcd22fba44b3a8de584574c75906b6c33f9a46f7589bce85835cb3bbc4a975c71b69cf0c75ff027547672a73d475a6ad4cef0dc7a5da789e5d378d36d9ab3acfe680d1a1f798cd4a3631ef8ce4d00b0477171310a6d5549707dd9729c6a99996dbbd1c16d9284da8e4a9cca05cac92fe62901ba2504f6d0b5b653ee278506986f9817a1318838843c71d32dafb3c02b8cd86969e5723b018c09cb2920e8ff1c12e6d73739f94187e337b75897f196b41d519e6211fbe3b8256b589ffa00aa4e535c623bec4ed9420a5dcbaf3c8386bf78be12bf11a3d7cec383bc8124ac0b3c4fbcc8c0963cab495970c30b26bd11998be8816db940c90752fe81222c3ca3c7690bb2af36363f2c5779711b5be2413fd21c9d320a870eb8adb142b12190a425cc665946093959c1bfc7120707fced816402c3cae136ae576eaaa01ef2f949e385067a3a9ecb053214dba01b5a7008e8f67ee1b47f6de19bd89738a24cea986bfe6442999d37c2bb64a8a2a56dec1b154e16beaadfff68059e2aea557f3eacc31aec1d7fbd2a3f4281d538e094eb7a2ad3894f079c3a51bc6c416e89955ef083c72a370c1c73706e54575a40670edec6082e340bd7e6c284bfb3e648170e5d7c9954af67270137f256a984d46edbe2f2969e99a175517906aff4a5aa4f0a272eec8ad3fce9efd251765ffad424f4e3631c76f8c39e11a88f5c5ccaa568261696f2e01a9ccada00385aa02f338a256df2cb459a880f9c261475e2dbb651a41da73cf690f64846318c471363d7a9c9f8ea86793a1228724f27a25c86e2a859fdc2214e206d13b8a8c7d9af4094807f0f0bc3d79f88d36876ca803efa91ba2f8cd43a7b1c16e311130796e743f98e16919551e6569af8d86d572bd10f5bc0c6d9209106107865030d2c3a9133fae8be4ca0204c9c3a2f27afcde5c4b84de7b59ee815dbeea6a1586587dccab22523acb0c6573d4f3e6e67e6971940ca3a5259681c1005ac436d6a6c39cbe52432164262e739a12404141b3778a98341b844d59f96db11cc03c6e099dd6d852c0eb66b148c4d29227dbb26139c061761a1a0a190473021653b06569a36d62826a3426c1b8b535e361959e0c05d317f7d28cd2608b87cb69b87d23e2aa5ed7d6c84a4b2907f0de0d079e18b872d8214a009d6c332634b3726621fba000f3d24e5b1e56e66c105ab32c14f79e5a0cbe6595ce284e8569cd05656e84e79e855bd930637badfc2b7fccad90768a1d932c22758e1d1b4b09c2c2e99ad8d522aa691152f75548cc6c34dd974b4c937329b5bdce688d180d8725c305c5420cb5d182dc3d034019b3658a3a3e0e607ad7100847aaacd4c6ad3e9c3d3079194ab6d171b0238ad0bf2773ad95908c299afb01376322c7cc18e56dd328c49ba2e51be4999eef7bed499f80514020fa4395ec539359ea38d2fcc23068f65e34abc0d8e65283ef195334f2e850720778e9faf3134d53734c12d5d62f8568fc629e1ba47b5abdc8d436aebb4f915b67c3a24f527f1b5ec96ebf8424070adddb821febb62275fb9d8bf436cc3470924f427e460919f9471a67cb041358650ece7c28fd8e5ca87924e414675609cd86aa001292f2eb16d9a32cba82162542dcdd238660d95b96f5a415c7ec08561fec6fce597d64db1d674b1801d63d091db9edd40a5ae0c290cec201e8ae2c607345652e34819ca93fd9b77cbb419c2ee623e1197df2d5b34c33ea3cd0898aa482e0851db122d0b79f0281fa7872ebc427736c0f8a13ce01ebd9d32b8657448a0f820b1ab76e97aca0607244d95bce1b108c6efeddc91566e8dabdf6dc3a95de1bd7665b7326e4ed0f6c7db499757794722fa543ff846cffaad38d65afced47c71ccea6ed847edf40873bbe50d1b8c43758511fa2272d5e467596cb6061cba020c259709536cfa183217659fe30f2ba0ebca1df4277ea8ebebbc8027e69f9c83c4948af1f66c749779d7d45c7a8686803ee492f0acaea49014b67390b6927dc90fd9c2ccfcaf8dad4da61d4f332ff08f9a0476cf5288f54746de8748d46587cc7e864a74307cdd00b561f44d7f28f11f04547f0535e26e90c918b0f5b0edfd04442f99de5e197f2dda9921f2a093c7d0d14f9d14199aa31f0e25a5f174291101d9b1329b40af6013c8717be5bfc97803a4a6543147ee431dac0303543f2ca962c59e5e19d4cf5a18e08956909e3ec514918e3cb21b6541b8074f92e0ac457e2d5dd43cb377b7080ebba0c47aa42c45b577cb0541ee1514572a68755cfea1f1e1062db6d7dc14b6385c2286ee4e35266076825cfb2330d3ff3f4250a57304a55b16588390a59652893676539f5d143c17c87912963f940125ea462587418a18027e0fc9461bc8008b6bdc0494693ed02231891b21e070a214c83bf6360e1037ef821e8d465d3549e891eb85c71ce3ce9add3556ef2f571d95ff3c0d370c027672f39763a40db728eb2c88e2e221203a8ec0ee3a7719d569bf3c90b0a482998d04e78da188c41941cfe11ccaa72c95bb88ba63d6cceee0fcd8a152c9c35fd2d6d6c882911129ec04d03fde8e0143ae9ef268c9dc0968fc4002f717440f4b704cb6033bf61b532c5b1036451655c49cab9f88d712e65290f152aa14b373f8831fd676fe9c33afa9d313f2fab20d41759b331a13b56dd09ac5ef0af26cb0427d259711ad936a2e2a463d63d9575cb2bba4311c7c368c336fc1606bca11556df00277ddfdfa649eaa4a92dc5f965716ca199e69f7536f0ae9ed563f3ab2f279ee8d405dc3b9c0008f70e7c334e0a3d40f25ffcb001b7c8b4bffa6f952cda05f128b573a841f317a2f441fd31c1a9e60b03afe993c74369fbcbcaa3351f37a532613d2de9c6517f0bced012429f9cf9e912ce292b5fe3a586478dd097874f7ce858150fa4ddc9c4876f30f20381e70983f04392a59909bc6d2020054ce2afeb36d79d2e63aa30366df1454152013cd2c9439b6954cab812829374709047fe967730ee5eab734beaa7c685178f181f25a481817b96477629896bc3b6bdf658cd5c4c50ad1f921f24d5ec56210d587a15e44669043b2275a2c26004069f25035afdb5bd545f3b72985a2fe134803f8ede7c545819f0ea0bb1877996515e5b6313dee9884bd6b2ecfd6b75d984fde8a8ff7a184d5a298556d664ac5bcbaee51b2e0ea20fad29738125cc914223209bd617952372a1041367f7bd325ee2ecf12aed306a5dc62fa9dc1f6cc5a5484e8e6a82eff21bbd4d6125efd00200970e5abd1befb2d0068ff4ad9cf864d343571869c2c9f34ca774d70df9adfb38af488a4b9bb8ba986ab0a2eb548161a52153cdcf7a1ac2a9c2d02f5809a0b53a53bea25bfc682c58c1b43957100ca67b0569c1c337124f5d0d598694e1c683f5fa2256c0539868fb115ad5cfe058fcd261935c4083bdcb6dd46272d3755d94ddc522880e636a90e7cbfc6882ca0b33eb03bfa2e2b34d6b14803281347f7794ada3ca4ff874b2f545614633c1f864f7cf3565e41371377ac07b7834ae3e659d3cfa3745bc3375f8a68fbda7b636e390757232b7615e5e27ccb863f896813e5bf7f283fefc63242fcfc6e51d5e270f6ce22f53e6f204b6e5aa1c5daccff601b8ab6e6e1da502950c6af9380bf8c4257dcb322a88de9d8532feb6a094dfd0b470788b67d76cf64b28369d686fc8291916b27afe153df9d38f2989895b32b25bb2d82de02d1e0a6c25b68e9b01b0bd852ff8a9c0464360a623f4511ccbbf863aba313c8c33c2c1dcc24365c795451062a98ad5acfce52a3e3cfa847ed0811739df7df76d9dc6d78f036aca5839e8d450589d6379f286db4781459ef80a3e6d87ee3efe1716cb5fb5b771e4a7c39846aa56c7f2280ef953f66d3e36deea73b59bfcfeab9cea92bc6acc5bf08a799d5fc6f70f792bbd59034e00977fa7c3a6a5c54d071100fd193238e49cc48818629c00836ffb41b25ec0c8d0da4cb950970ee89222d3e804f97446c1f3df3cec637172b052931e596562be941b5e287901464e8be1007e1f8f5ceb8ef2e82f0ad3ce890f9ce832e0625c637ed9705d1ce8489f74a36bf828e372f082bce6ca1ed2e971c81d6bac8b60d8c7b31c961f7d81939fa0655d960d11985d0312909e501d5d270b6c2de2870b6e1d56baf269dc72f2274ed32d6fbe748d5b8e6dd1692965ff15ef36be347017d25182b5796d9daef2c91b0f12d221f5f5c46d3b388adb32c65f5322d9233e00f357bb512e363a353283096fd7f1498852e53470838c3bb2c0107f734c214256a179ee6ce0835fbc55acc4127ff0004f2413464f882a5749ecaaafec4f35ccf26dbdc2c0769a574a497a245b480723606502714305355fdafbe062b1c2b2a1c0021ad3b12c1122a5a419a8e13fdf61641b5f9d2ce160bea49c0918072722c94758756ebe54d870b583b2136ebfd486122a067ec5a3b8f85be64134c9d05d4718b6ead1a7d3bfe8df1129a4d5b7f1fdbc6ba73d7bd1fd3e7bcb455c9dbccfd317362551ac7a9c427853a7fe1756c13b185276163903007f0e1e3763fe51667d293f8892a530ec5b14dbc63722371778ee1ee5b4675690a26fe50e9351c1726b5763d85cfd577c6ba27264fa5855111b475e5535ba2f36f4864b8458b1517cf118856f0c0ae3613877061b2d10e2946694f2f73efcf52afdae320fa51fb1bbc8c98febe687343af44105f2e870c24ffda2c2b6adfa9e3875e243b88517effde3dbca1857890d0d006e09d50578f41fa71461e253d7d81a5f568f0bba788daf55509c2d2bd827cea21f7f3a3f7eaeba95db7a5b0d954a1ca32a6bd10387cf661272f403095262d0843b2243035cc172c0d31fe5b603fc65637cc70dc26fd40bde7fd01b5f3007ebd9760843fd8d02d523884537d4996b5fa83f6ca275093b7c71e7ece2c9f49aee9ded0edcca5ea131681b3f79a1e1a82b2ff0f2c7898f7c51985f59e17304c0798173bd5b76c8a32c619ceb329c5178e341bb44d0dbf11738d9c2ebc07b5016a87cb63eab68f1c1ea86c304eac1f54bf97df49b26f4d50bf815ffaaeb43e5f7c12bc76aca6d3738e3d2d39ba43ddad8989418b618c587e633f4a997d881d1273fe4aeea3c7890349e2e74bdd2c40fca190e1289f0ce4f5ca5957155ab1a2617c6665e8ef08fcec622333adef8c285b797553eba9c76ec8d8f5b9a955bc40b9244e18d2fc52bdb06d1182b6a885d406602bcfc2b988c667e5696ed7cce33f209825f68f383b38ce7142a7c0f26e61e8631f42cab247c17c7562fe2fcc5c9854351dcb39636ac3cd3c9b1aebc761a9c0dcff3be9d6f99b1468fc575e9756bc7a91f69f401a37a6d1dca2971cb1774fc40be34749ca6179e11f0c0d18aaa9265cb9ee1511a78986cf40301af2f1d2eb6195e9e65728d2d3e48e8627ec241ba3510009867446a3704d4b02e26338300c104993f05bf02849202f89fec7231f6156774360134e04f19b050b454e52a7bdf91ac8eda64b729ca71ce15ef3e78a71455ac0a11db457843a147abfa5e190e7bfbed773d5dfc998e583d3b1fd6b9d524b95581ca56a7c6277e61d113dcd29f58851d5ebc5b2c88b41ffab318985ce3b2caa3bc3a16d6fc2ded3d0c0f84fb68287e88e7891f9bc72629892472278e1587e103a11ed9d3b7f27b8000708906a5f2aa73f56afee4646f8c3f475b4502b800485bd3d4c5c8b20cd283775351a23febed54afba558796594a332a74de6a30c612aa87337aa2323f3831d99bc886a2185a2c05374a1d81324defd2a420433062dbc12871de25e42e414494675dc64775325d64f4e20728589b455e2dc55155f0c2eb816cf0434727206047fe48f72fb0e2c711a72cd2f93b7538f177da626e74ad4c4a4efa60febf5feb9152dec71318726f8260b5a470f04a9fd294acf4d50d2a5e5c57a2dc26160df4e6211d9abefae18e7ef0164fb35d72cde510903c4d3d2331455184f6c4ff45aa12d26e36be149338757e8b3d048ae07386f2b48c6b2162a5f8bdd3c5b322422c56d5cab1c494fb2911709516f3b363593e86dad810bb22c5c84331f8fcab37514e5b8cfe24813eac8375f93e3e0785b3e35659a8605ed330aee996139f015184543229e972d8959dbc2d6bf3cd6b1fc1cf6e6778411f071125fdfe7175b0066303e933b4e19567cbc85396261628b005471185fbe8823dd7c12b2c3e29df711485a77f4a5af82aa31ee3d741b9956f6715191909a78d4d977e21b921092a229fa81f0478e003ffc316ab1cce24d3ba82c3332ed7ad99a8c4bca6ed85b54a0e9b47c68a4e5f295d7faff22622fefe4e761a18b5a39ce546e331427009297ed3ab42ad2717541ea5c19fb4e95004a5ad5070d239055096737a7c228e87d4eff877cbb226039e861d8b96cc1a3871756b0c38b4712a7068db71f37e57f29c55cc2b33565294b7716586de061c3ac52f270eb24f3dce346584c24af7503ed8c1a78ef0e94334c0e1d3f253ce4d9a7a98d07a5ff9298347f9d4ffd49a6b2ed5a71cd4546be422bb7f14b8515b6311805321f019a6a53d8ac233c44ba035168fc775a7e69ff9c34330421ef92074a400267b4aa5688a297472e8e17106db5f5e2aa8be27ce99a63cef93013d088d5421821ff2ade00684dd767a3097e0d829f4b95b1dc6b91aa6dcde0b2b5fd9fcc8ef953da9df727efad133cf3e23fd346dd4b63e01c7554665bbe072290ee09a17b3d033c2439f86b3f15b0625e93450ca835f3b552a86a5bbea72cd9bdbf8b565a5a5ace18415af65f7c65231cd7a976e5d0383cfc9776326657c256df9e83839fb0e78c1e5ed9631ae4c7d0ab25d7b23225e14c8e5ca4d855c755af60a6fa56f38450fd8248ce5c3d7700b535df9744bf10146bc3f6de1b460328a12aed8533fa067280287b63eb45eb405f3c09468d372701b80ddf939db169e71584491a8390285644123e4a439055a290a61349ae5b447f8b4885b1cacd8f878e2c9a7d4b99e7bf4b8b6f13ff0dc07f59a0bf05f79c4ef40331231f2d438d38f0e270cb86f9b92990682f0a60fab5478a55b3a8762e8421f230095878b6f2ea7fdf02ebfab9c12b59cfcfbe13c545e5e677ccabe4f4661a996a9a089caa738d85dd9e68baee3127738089a3f6c066c96e53b65d5ad60e395aeae86e58900121d03d81952c1814c99cfc1ea33724b8e782b636582321d65648527194a29a383214e30e8262c1f4c9e72608517aff00777172d6a188f0e3782c208c66549713265de122a203c4e65876be9c95a51b6ef957c5c0fa2f50dc6a79e79d65e8ec3383c9c759a0d17df1887a432223dd2309eec845bbd46becaaa3768e11b58d3c0c169fec43fd3e0359cf0d2b5ec1a535efce29e30f09b3fcbaf7cc8c3a73867fa8a5b1c11c03c2e9b1ee3aa108171a89321ae8e8bd790427f2d5bfce1e3fad872fc815267a79e16c39179c8830f6addf0a8cec42eb9955ffdd1899d69c2d20f122c72bca20de21ca430d091d8d60fae7d05f9114e3b015b5feb25da89ef74b213e9e0759bbcc7a91550c4c5e770d0e9d2918f8f56b041f2c963fa0cb5eb5d48369493f92688d9f688a80c1afac853554c3efc77be42965e02906e80ff993fd3e09cf9335d4796cfdf263ee9aff8e5d7185cd2fc5de98a038fe2ddc78fb287ca556092f09af4768bfd0fc229eb2a83bc75b3d3214ebd2d3889b3a20ffe2e7ae0629e2d130decadae628b2ad766ac2d9d837682c4f8ce19cb8f0fcba7e58e4b35bce001bc65d5e5f44361e19f2be5c04fbcff0b6812f2e14af2660e0000000049454e44ae426082", expectedOutput: "https://globeon.mobi/jyri", recipeConfig: [ { "op": "From Hex", "args": ["None"] }, { "op": "Parse QR Code", "args": [true] } ] } ]); ================================================ FILE: tests/operations/tests/ParseSSHHostKey.mjs ================================================ /** * Parse SSH Host Key tests * * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "SSH Host Key: RSA", input: "AAAAB3NzaC1yc2EAAAADAQABAAABAQDiJZ/9W9Ix/Dk9b+K4E+RGCug1AtkGXaJ9vNIY0YHFHLpWsB8DAuh/cGEI9TLbL1gzR2wG+RJNQ2EAQVWe6ypkK63Jm4zw4re+vhEiszpnP889J0h5N9yzyTndesrl4d3cQtv861FcKDPxUJbRALdtl6gwOB7BCL8gsXJLLVLO4EesrbPXD454qpVt7CgJXEXByOFjcIm3XwkdOnXMPHHnMSD7EIN1SvQMD6PfIDrbDd6KQt5QXW/Rc/BsfX5cbUIV1QW5A/GbepXHHKmWRtLC2J/mH3hW2Zq/hITPEaJdG1CtIilQmJaZGXpfGIwFeb0Av9pSL926arZZ6vDi9ctF", expectedOutput: `Key type: ssh-rsa Exponent: 0x010001 Modulus: 0x00e2259ffd5bd231fc393d6fe2b813e4460ae83502d9065da27dbcd218d181c51cba56b01f0302e87f706108f532db2f5833476c06f9124d43610041559eeb2a642badc99b8cf0e2b7bebe1122b33a673fcf3d27487937dcb3c939dd7acae5e1dddc42dbfceb515c2833f15096d100b76d97a830381ec108bf20b1724b2d52cee047acadb3d70f8e78aa956dec28095c45c1c8e1637089b75f091d3a75cc3c71e73120fb1083754af40c0fa3df203adb0dde8a42de505d6fd173f06c7d7e5c6d4215d505b903f19b7a95c71ca99646d2c2d89fe61f7856d99abf8484cf11a25d1b50ad222950989699197a5f188c0579bd00bfda522fddba6ab659eaf0e2f5cb45`, recipeConfig: [ { op: "Parse SSH Host Key", args: ["Base64"] } ] }, { name: "SSH Host Key: DSA", input: "AAAAB3NzaC1kc3MAAACBAMnoZCOzvaQqs//9mxK2USZvJBc7b1dFJiBcV80abN6maE+203pTRPIPCpPt0deQxv4YN3dSHoodEcArWxs1QRAIuRsQIvsUP7chovzGnxP84XWK5sbfrseD0vxZ7UR0NaAFPcSgeXcWC1SG9uvrAJQlyp4DBy+fKuqiYmwaz0bHAAAAFQCXNJ4yiE1V7LpCU2V1JKbqDvICMwAAAIB/5aR1iBOeyCVpj0dP3YZmoxd9R7FCC/0UuOf0lx4E6WHT6Z2QuPBhc2mpNDq2M0VF9oJfVWgcfG8r1rlXaCYODSacGcbnW5VKQ+LKkkALmg4h8jFCHReUC+Hmia/v8LyDwPO1wK6ETn7a3m80yM7gAU5ZNurVIVVP2lB65mjEsQAAAIA3ct9YRB6iUCvOD45sZM1C9oTC24Ttmaou0GcpWx3h0/iZ8mbil1cjaO9frRNZ/vSSVWEhEDNG8gwkjZWlvnJL3y1XUxbMll4WbmI/Q1kzKwopceaFwMbYTPKDg6L1RtCMUxSUyKsFk1c4SpEPlDS7DApZs5PgmWgMd/u6vwMXyg==", expectedOutput: `Key type: ssh-dss p: 0x00c9e86423b3bda42ab3fffd9b12b651266f24173b6f574526205c57cd1a6cdea6684fb6d37a5344f20f0a93edd1d790c6fe183777521e8a1d11c02b5b1b35411008b91b1022fb143fb721a2fcc69f13fce1758ae6c6dfaec783d2fc59ed447435a0053dc4a07977160b5486f6ebeb009425ca9e03072f9f2aeaa2626c1acf46c7 q: 0x0097349e32884d55ecba4253657524a6ea0ef20233 g: 0x7fe5a47588139ec825698f474fdd8666a3177d47b1420bfd14b8e7f4971e04e961d3e99d90b8f0617369a9343ab6334545f6825f55681c7c6f2bd6b95768260e0d269c19c6e75b954a43e2ca92400b9a0e21f231421d17940be1e689afeff0bc83c0f3b5c0ae844e7edade6f34c8cee0014e5936ead521554fda507ae668c4b1 y: 0x3772df58441ea2502bce0f8e6c64cd42f684c2db84ed99aa2ed067295b1de1d3f899f266e297572368ef5fad1359fef492556121103346f20c248d95a5be724bdf2d575316cc965e166e623f4359332b0a2971e685c0c6d84cf28383a2f546d08c531494c8ab059357384a910f9434bb0c0a59b393e099680c77fbbabf0317ca`, recipeConfig: [ { op: "Parse SSH Host Key", args: ["Base64"] } ] }, { name: "SSH Host Key: ECDSA", input: "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGxZWSAGJyJQoVBwFCpr420eRUZDE/kw2YWm5vDro8050DZ1ZzqIuYaNl0BGzMcRTeasGtJuI8G84ZQQSgca3C4=", expectedOutput: `Key type: ecdsa-sha2-nistp256 Curve: nistp256 Point: 0x046c59592006272250a15070142a6be36d1e45464313f930d985a6e6f0eba3cd39d03675673a88b9868d974046ccc7114de6ac1ad26e23c1bce194104a071adc2e`, recipeConfig: [ { op: "Parse SSH Host Key", args: ["Base64"] } ] }, { name: "SSH Host Key: Ed25519", input: "AAAAC3NzaC1lZDI1NTE5AAAAIBOF6r99IkvqGu1kwZrHHIqjpTB5w79bpv67B/Aw3+WJ", expectedOutput: `Key type: ssh-ed25519 x: 0x1385eabf7d224bea1aed64c19ac71c8aa3a53079c3bf5ba6febb07f030dfe589`, recipeConfig: [ { op: "Parse SSH Host Key", args: ["Base64"] } ] }, { name: "SSH Host Key: Extract key", input: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDiJZ/9W9Ix/Dk9b+K4E+RGCug1AtkGXaJ9vNIY0YHFHLpWsB8DAuh/cGEI9TLbL1gzR2wG+RJNQ2EAQVWe6ypkK63Jm4zw4re+vhEiszpnP889J0h5N9yzyTndesrl4d3cQtv861FcKDPxUJbRALdtl6gwOB7BCL8gsXJLLVLO4EesrbPXD454qpVt7CgJXEXByOFjcIm3XwkdOnXMPHHnMSD7EIN1SvQMD6PfIDrbDd6KQt5QXW/Rc/BsfX5cbUIV1QW5A/GbepXHHKmWRtLC2J/mH3hW2Zq/hITPEaJdG1CtIilQmJaZGXpfGIwFeb0Av9pSL926arZZ6vDi9ctF test@test", expectedOutput: `Key type: ssh-rsa Exponent: 0x010001 Modulus: 0x00e2259ffd5bd231fc393d6fe2b813e4460ae83502d9065da27dbcd218d181c51cba56b01f0302e87f706108f532db2f5833476c06f9124d43610041559eeb2a642badc99b8cf0e2b7bebe1122b33a673fcf3d27487937dcb3c939dd7acae5e1dddc42dbfceb515c2833f15096d100b76d97a830381ec108bf20b1724b2d52cee047acadb3d70f8e78aa956dec28095c45c1c8e1637089b75f091d3a75cc3c71e73120fb1083754af40c0fa3df203adb0dde8a42de505d6fd173f06c7d7e5c6d4215d505b903f19b7a95c71ca99646d2c2d89fe61f7856d99abf8484cf11a25d1b50ad222950989699197a5f188c0579bd00bfda522fddba6ab659eaf0e2f5cb45`, recipeConfig: [ { op: "Parse SSH Host Key", args: ["Base64"] } ] } ]); ================================================ FILE: tests/operations/tests/ParseTCP.mjs ================================================ /** * Parse TCP tests. * * @author n1474335 * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Parse TCP: No options", input: "c2eb0050a138132e70dc9fb9501804025ea70000", expectedMatch: /1026 \(Scaled: 1026\)/, recipeConfig: [ { op: "Parse TCP", args: ["Hex"], } ], }, { name: "Parse TCP: Options", input: "c2eb0050a1380c1f000000008002faf080950000020405b40103030801010402", expectedMatch: /1460/, recipeConfig: [ { op: "Parse TCP", args: ["Hex"], } ], }, { name: "Parse TCP: Timestamps", input: "9e90e11574d57b2c00000000a002ffffe5740000020405b40402080aa4e8c8f50000000001030308", expectedMatch: /2766719221/, recipeConfig: [ { op: "Parse TCP", args: ["Hex"], } ], } ]); ================================================ FILE: tests/operations/tests/ParseTLSRecord.mjs ================================================ /** * Parse TLS record tests. * * @auther c65722 * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Parse TLS record: Truncated header", input: "16030300", expectedOutput: "[]", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Change Cipher Spec", input: "140303000101", expectedOutput: '[{"type":"change_cipher_spec","version":"0x0303","length":1,"value":"0x01"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Change Cipher Spec - Truncated before content", input: "1403030001", expectedOutput: '[{"type":"change_cipher_spec","version":"0x0303","length":1,"truncated":true}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Alert", input: "150303001411770b5b5d11078535823266ec79671ed402bced", expectedOutput: '[{"type":"alert","version":"0x0303","length":20,"value":"0x11770b5b5d11078535823266ec79671ed402bced"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Alert - Truncated within content", input: "150303001411770b5b5d1107853582", expectedOutput: '[{"type":"alert","version":"0x0303","length":20,"truncated":true,"value":"0x11770b5b5d1107853582"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Alert - Truncated before content", input: "1503030014", expectedOutput: '[{"type":"alert","version":"0x0303","length":20,"truncated":true}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Truncated within length", input: "1603030032010000", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Truncated before length", input: "160303003201", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Truncated before msg type", input: "1603030032", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Hello Request", input: "160303000400000000", expectedOutput: '[{"type":"handshake","version":"0x0303","length":4,"handshakeType":"hello_request"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443210200010000", 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":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before extensions length", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600000412344321020001", 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":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within compression methods", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443210200", 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":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before compression methods", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000041234432102", 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":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before compression methods length", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600000412344321", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within cipher suite value", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443", 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":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within cipher suites", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000041234", 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":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before cipher suites", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"truncated":true,"values":[]},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before cipher suites length", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before session id length", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within random", input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a0", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before random", input: "16030300320100002e0303", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within client version", input: "16030300320100002e03", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"","random":"","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before client version", input: "16030300320100002e", expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, No extensions", input: "16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae0004123443210200010000", 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":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, No extensions - Truncated within session id", input: "16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a", expectedOutput: '[{"type":"handshake","version":"0x0303","length":82,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, No extensions - Truncated before session id", input: "16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620", expectedOutput: '[{"type":"handshake","version":"0x0303","length":82,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions", input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010000e00000b6578616d706c652e636f6d00170000ff01000100", 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"}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension value", input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010000e00000b657861", 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"}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension value", input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010", 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}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension length", input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d000000", 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":[]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension length", input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d0000", 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":[]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension type", input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00", 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":[]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension type", input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d", 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":[]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions", input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132004321010000", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before extensions length", input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113200432101", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before compression method", input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132004321", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"0x4321","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within cipher suite", input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b011320043", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before cipher suite", input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113200", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before session id length", input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within random", input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9a", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"","cipherSuite":"","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before random", input: "160303002c0200002803030", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"","cipherSuite":"","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within server version", input: "160303002c0200002803", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"","random":"","cipherSuite":"","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before server version", input: "160303002c02000028", expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, No extension", input: "160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b95984321010000", expectedOutput: '[{"type":"handshake","version":"0x0303","length":76,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","sessionID":"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, No extension - Truncated within session id", input: "160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df7", expectedOutput: '[{"type":"handshake","version":"0x0303","length":76,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, No extension - Truncated before session id", input: "160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220", expectedOutput: '[{"type":"handshake","version":"0x0303","length":76,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions", input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff0100010000170000", 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}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension value", input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff010001", 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}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated within extension length", input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff0100", 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}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension length", input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff01", 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}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated within extension type", input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff", 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}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension type", input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000", 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}]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - New Session Ticket", input: "16030300ca040000c60000070800c0626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470e7d67d8d33647e245e5e789f547d6a1be91ef0985bbfcf3b88760586b8f02570e0b7e8547fdad75530bc0261756ec994dfc725c8551c762f26e105e62290cd43773ea9e8a42ac8ac21467053240a29ef93c2e34c2f13ce8ff494d8c64f727248", expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":"0x626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470e7d67d8d33647e245e5e789f547d6a1be91ef0985bbfcf3b88760586b8f02570e0b7e8547fdad75530bc0261756ec994dfc725c8551c762f26e105e62290cd43773ea9e8a42ac8ac21467053240a29ef93c2e34c2f13ce8ff494d8c64f727248"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - New Session Ticket - Truncated within ticket", input: "16030300ca040000c60000070800c0626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470", expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - New Session Ticket - Truncated before ticket", input: "16030300ca040000c60000070800c0", expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - New Session Ticket - Truncated within ticket length", input: "16030300ca040000c60000070800", expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - New Session Ticket - Truncated before ticket length", input: "16030300ca040000c600000708", expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - New Session Ticket - Truncated within ticket lifetime hint", input: "16030300ca040000c6000007", expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"","ticket":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - New Session Ticket - Truncated before ticket lifetime hint", input: "16030300ca040000c6", expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate", input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d000599308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a59e12a5a72d436036b31fa130f6a72c717b3df403113ee3b3d1605f76e57e96b83e501ed5fe9200e2ea9aefa797fa0c8b6c5d8f12e4bea7359be03d3ca35d3e22e20639fc7e03c990a494402268a08fb1589dc086995b0ba3c9ffe255b6b7cf0203010001a3533051301d0603551d0e041604143e8bac5b946c2eff6a8cb337081fa4fe6ce07312301f0603551d230418301680143e8bac5b946c2eff6a8cb337081fa4fe6ce07312300f0603551d130101ff040530030101ff300d06092a864886f70d01010b0500038202010081ad7acd39e5cc60682c962d367a84d32191e5b465ed531f617daf5fd33394a3ac9a42116d34211708ada0d9bd2cbf1d4a4175d67c87116c7495ed372c585ae6bdfe0bc713aa1afd0cc3f025c322dc45be0c3be982918dea938deaaa9e5bfd1fccb3eb8a111aec0498f64bdb16f6cb07bcecd85f6b9e445cf596d85596b4f0d7147d73cbc26000d374085e9c69f56262827fa3d5a037cf1d2cfe0f0eca779b101da08a8d732ecf584a193d93449697ee24ed6f41f9735ea3a3f206f8e6b5bf0b0ff3488a31d0feaccd701a144d35c265dc32d2e650f855debbfa5bd2d9dc2d80a1b8f81013f8049bd7be83a3ec5ae554c19fd4241a6686d4094ff073022d1f16afa5a0297e54a9b56fd469b44c6904d2b542f83ff0cf6af3b649f408f72f7cb49be5583ec4b1d912a677ae1fd81779506af9b688d8b753fdb0451925752fba8efcdaedf935f2a264caa1f4fe746ac6c339cca647b25f0bd2139205e67b6e90987da8b993b85037931443a6652426ab779db090cf08b28fed862a0ccdde1568bd930bf2d39ab7b850f97925e9bda13a6ee5166e48959711065c054bdf5ff04e4b8d5120caabca40c7707da3bb10f2ae7a00a6e56b012a6c00daaec5ddf0b63f61622aeeb81a71a5aa17508e5471e777bed8d09023c24280495adc38ffc3615dd20b139d32d7cc30b0690ab7f3e47a0131fa3d81929e64c6b9c6b363da410f6e5e", expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"handshakeType":"certificate","certificateList":{"length":2760,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d","0x308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a59e12a5a72d436036b31fa130f6a72c717b3df403113ee3b3d1605f76e57e96b83e501ed5fe9200e2ea9aefa797fa0c8b6c5d8f12e4bea7359be03d3ca35d3e22e20639fc7e03c990a494402268a08fb1589dc086995b0ba3c9ffe255b6b7cf0203010001a3533051301d0603551d0e041604143e8bac5b946c2eff6a8cb337081fa4fe6ce07312301f0603551d230418301680143e8bac5b946c2eff6a8cb337081fa4fe6ce07312300f0603551d130101ff040530030101ff300d06092a864886f70d01010b0500038202010081ad7acd39e5cc60682c962d367a84d32191e5b465ed531f617daf5fd33394a3ac9a42116d34211708ada0d9bd2cbf1d4a4175d67c87116c7495ed372c585ae6bdfe0bc713aa1afd0cc3f025c322dc45be0c3be982918dea938deaaa9e5bfd1fccb3eb8a111aec0498f64bdb16f6cb07bcecd85f6b9e445cf596d85596b4f0d7147d73cbc26000d374085e9c69f56262827fa3d5a037cf1d2cfe0f0eca779b101da08a8d732ecf584a193d93449697ee24ed6f41f9735ea3a3f206f8e6b5bf0b0ff3488a31d0feaccd701a144d35c265dc32d2e650f855debbfa5bd2d9dc2d80a1b8f81013f8049bd7be83a3ec5ae554c19fd4241a6686d4094ff073022d1f16afa5a0297e54a9b56fd469b44c6904d2b542f83ff0cf6af3b649f408f72f7cb49be5583ec4b1d912a677ae1fd81779506af9b688d8b753fdb0451925752fba8efcdaedf935f2a264caa1f4fe746ac6c339cca647b25f0bd2139205e67b6e90987da8b993b85037931443a6652426ab779db090cf08b28fed862a0ccdde1568bd930bf2d39ab7b850f97925e9bda13a6ee5166e48959711065c054bdf5ff04e4b8d5120caabca40c7707da3bb10f2ae7a00a6e56b012a6c00daaec5ddf0b63f61622aeeb81a71a5aa17508e5471e777bed8d09023c24280495adc38ffc3615dd20b139d32d7cc30b0690ab7f3e47a0131fa3d81929e64c6b9c6b363da410f6e5e"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate - Truncated within certificate", input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d0005990x308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a5", expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{"length":2760,"truncated":true,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate - Truncated before certificate", input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d000599", expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{"length":2760,"truncated":true,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate - Truncated within certificate length", input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d0005", expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{"length":2760,"truncated":true,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate - Truncated before certificate length", input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d", expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{"length":2760,"truncated":true,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate - Truncated within certificate list length", input: "1603030acf0b000acb000a", expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate - Truncated before certificate list length", input: "1603030acf0b000acb", expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Key Exchange", input: "16030300840c000080a90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b87175e472c7c07b97699f72958e0af489df00d34e5e03dde2e09dfe06d448651ee45c07fadc05e0d1585589e3715a04b935e72bc28c34593712acef7883ed69a", expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"handshakeType":"server_key_exchange","handshakeValue":"0xa90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b87175e472c7c07b97699f72958e0af489df00d34e5e03dde2e09dfe06d448651ee45c07fadc05e0d1585589e3715a04b935e72bc28c34593712acef7883ed69a"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Key Exchange - Truncated within content", input: "16030300840c000080a90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b", expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"truncated":true,"handshakeType":"server_key_exchange","handshakeValue":"0xa90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Key Exchange - Truncated before content", input: "16030300840c000080", expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"truncated":true,"handshakeType":"server_key_exchange"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, No certificate authorities", input: "160303001f0d00001b040102030400120601060206030301030203030201020202030000", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities", input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d001813b3cdd60a34fc74f2e4ef2344cfd2156924d8d2810e2c86", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authority", input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d001813b3cdd60a34fc74f2e4ef23", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authority", input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d0018", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authority length", input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d00", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authority length", input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authorities length", input: "16030300470d0000430401020304001206010602060303010302030302010202020300", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authorities length", input: "16030300470d00004304010203040012060106020603030103020303020102020203", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within supported signature algorithm", input: "16030300470d000043040102030400120601060206030301030203030201020202", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before supported signature algorithm", input: "16030300470d0000430401020304001206010602060303010302030302010202", 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"]}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within supported signature algorithms length", input: "16030300470d000043040102030400", expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before supported signature algorithms length", input: "16030300470d0000430401020304", expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate types", input: "16030300470d00004304010203", expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"truncated":true,"values":["0x01","0x02","0x03"]},"supportedSignatureAlgorithms":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate types", input: "16030300470d00004304", expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"truncated":true,"values":[]},"supportedSignatureAlgorithms":{}}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate types length", input: "16030300470d000043", expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Server Hello Done", input: "16030300040e000000", expectedOutput: '[{"type":"handshake","version":"0x0303","length":4,"handshakeType":"server_hello_done"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Verify", input: "16030301080f000104040101009310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2a93d1b85ae2d0ee7c7170aac3397fa6de77183d30c99e6bb0e81f925793f64d8b490cb74d051896ebee9086c7606905b21bab6ebd9866a451958f7d839134aeb335b2ad5f9ce89a69321a099c081b5166332cf2bb231dd135b79cf94218e6ada94644eaa09ae6c0ec0164e3cca631c0f4b7b9a2d59fb40909ec88805e61b5917", expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":"0x9310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2a93d1b85ae2d0ee7c7170aac3397fa6de77183d30c99e6bb0e81f925793f64d8b490cb74d051896ebee9086c7606905b21bab6ebd9866a451958f7d839134aeb335b2ad5f9ce89a69321a099c081b5166332cf2bb231dd135b79cf94218e6ada94644eaa09ae6c0ec0164e3cca631c0f4b7b9a2d59fb40909ec88805e61b5917"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Verify - Truncated within signature", input: "16030301080f000104040101009310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2", expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Verify - Truncated before signature", input: "16030301080f00010404010100", expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Verify - Truncated within signature length", input: "16030301080f000104040101", expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Verify - Truncated before signature length", input: "16030301080f0001040401", expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Verify - Truncated before algorithm.signature", input: "16030301080f00010404", expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"","signature":""}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Certificate Verify - Truncated before algorithm.hash", input: "16030301080f000104", expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Key Exchange", input: "1603030084100000802b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602ad0b4cb7319fc52694a67f1e381b4d8a581823410920717ee85ef352dea39097e6b131bdfeb3913f0f7eaa3b3882abe4615cc13e2a133558adff159771dfdc8d", expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"handshakeType":"client_key_exchange","handshakeValue":"0x2b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602ad0b4cb7319fc52694a67f1e381b4d8a581823410920717ee85ef352dea39097e6b131bdfeb3913f0f7eaa3b3882abe4615cc13e2a133558adff159771dfdc8d"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Key Exchange - Truncated within content", input: "1603030084100000802b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602", expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"truncated":true,"handshakeType":"client_key_exchange","handshakeValue":"0x2b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Client Key Exchange - Truncated before content", input: "160303008410000080", expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"truncated":true,"handshakeType":"client_key_exchange"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Finished", input: "1603030028ed83078db91b046358065ca3f7ea4494af3deb59bf72f522e15ef9071c52becb0069a093b23994c1", expectedOutput: '[{"type":"handshake","version":"0x0303","length":40,"handshakeType":"finished","handshakeValue":"0xed83078db91b046358065ca3f7ea4494af3deb59bf72f522e15ef9071c52becb0069a093b23994c1"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Finished - Truncated within ciphertext", input: "1603030028ed83078db91b046358065ca3f7ea4494af3deb59", expectedOutput: '[{"type":"handshake","version":"0x0303","length":40,"truncated":true,"handshakeType":"finished","handshakeValue":"0xed83078db91b046358065ca3f7ea4494af3deb59"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Unknown", input: "1603030024120000203c210cd33fd2a7379ae02700b208ae7357f98b46a1dea566c4061acfb6e188bc", expectedOutput: '[{"type":"handshake","version":"0x0303","length":36,"handshakeType":"18","handshakeValue":"0x3c210cd33fd2a7379ae02700b208ae7357f98b46a1dea566c4061acfb6e188bc"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Unknown - Truncated within content", input: "1603030024120000203c210cd33fd2a7379ae02700b208", expectedOutput: '[{"type":"handshake","version":"0x0303","length":36,"truncated":true,"handshakeType":"18","handshakeValue":"0x3c210cd33fd2a7379ae02700b208"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Handshake - Unknown - Truncated before content", input: "160303002412000020", expectedOutput: '[{"type":"handshake","version":"0x0303","length":36,"truncated":true,"handshakeType":"18"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Application Data", input: "1703030064bbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca6731705f5d957c481bade800cf1cd066dfd997851af09e820e84ee0b531b4eaccfd8b5f28b74d756a8aeadf78eefb2d26e46b5b69", expectedOutput: '[{"type":"application_data","version":"0x0303","length":100,"value":"0xbbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca6731705f5d957c481bade800cf1cd066dfd997851af09e820e84ee0b531b4eaccfd8b5f28b74d756a8aeadf78eefb2d26e46b5b69"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Application Data - Truncated within content", input: "1703030064bbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca67317", expectedOutput: '[{"type":"application_data","version":"0x0303","length":100,"truncated":true,"value":"0xbbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca67317"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Application Data - Truncated before content", input: "1703030064", expectedOutput: '[{"type":"application_data","version":"0x0303","length":100,"truncated":true}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Unknown", input: "1c03030020c02beaae1dd2e9ec46c4d201d72105457af1f8e92d56ad95f339398e5774cb6f", expectedOutput: '[{"type":"28","version":"0x0303","length":32,"value":"0xc02beaae1dd2e9ec46c4d201d72105457af1f8e92d56ad95f339398e5774cb6f"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Unknown - Truncated within content", input: "1c03030020c02beaae1dd2e9ec46c4d201d7210545", expectedOutput: '[{"type":"28","version":"0x0303","length":32,"truncated":true,"value":"0xc02beaae1dd2e9ec46c4d201d7210545"}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] }, { name: "Parse TLS record: Unknown - Truncated before content", input: "1c03030020", expectedOutput: '[{"type":"28","version":"0x0303","length":32,"truncated":true}]', recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Parse TLS record", args: [] }, { op: "JSON Minify", args: [] } ] } ]); ================================================ FILE: tests/operations/tests/ParseTLV.mjs ================================================ /** * Parse TLV tests. * * @author gchq77703 [] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Parse TLV: LengthValue", input: "\x05\x48\x6f\x75\x73\x65\x04\x72\x6f\x6f\x6d\x04\x64\x6f\x6f\x72", 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), recipeConfig: [ { "op": "Parse TLV", "args": [0, 1, false] } ] }, { name: "Parse TLV: LengthValue with BER", input: "\x05\x48\x6f\x75\x73\x65\x04\x72\x6f\x6f\x6d\x04\x64\x6f\x6f\x72", 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), recipeConfig: [ { "op": "Parse TLV", "args": [0, 4, true] // length value is patently wrong, should be ignored by BER. } ] }, { name: "Parse TLV: KeyLengthValue", input: "\x04\x05\x48\x6f\x75\x73\x65\x05\x04\x72\x6f\x6f\x6d\x42\x04\x64\x6f\x6f\x72", 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), recipeConfig: [ { "op": "Parse TLV", "args": [1, 1, false] } ] }, { name: "Parse TLV: KeyLengthValue with BER", input: "\x04\x05\x48\x6f\x75\x73\x65\x05\x04\x72\x6f\x6f\x6d\x42\x04\x64\x6f\x6f\x72", 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), recipeConfig: [ { "op": "Parse TLV", "args": [1, 4, true] // length value is patently wrong, should be ignored by BER. } ] } ]); ================================================ FILE: tests/operations/tests/ParseUDP.mjs ================================================ /** * Parse UDP tests. * * @author h345983745 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Parse UDP: No Data - JSON", input: "04 89 00 35 00 2c 01 01", expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\"}", recipeConfig: [ { op: "Parse UDP", args: ["Hex"], }, { op: "JSON Minify", args: [], }, ], }, { name: "Parse UDP: With Data - JSON", input: "04 89 00 35 00 2c 01 01 02 02", expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\",\"Data\":\"0x0202\"}", recipeConfig: [ { op: "Parse UDP", args: ["Hex"], }, { op: "JSON Minify", args: [], }, ], }, { name: "Parse UDP: Not Enough Bytes", input: "04 89 00", expectedOutput: "Need 8 bytes for a UDP Header", recipeConfig: [ { op: "Parse UDP", args: ["Hex"], }, { op: "JSON Minify", args: [], }, ], } ]); ================================================ FILE: tests/operations/tests/ParseX509CRL.mjs ================================================ /** * Parse X.509 CRL tests. * * @author robinsandhu * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const IN_CRL_PEM_RSA = `-----BEGIN X509 CRL----- MIID7jCCAdYCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN MjQwODI1MTE0OTEwWhcNMjQwOTI0MTE0OTEwWjA1MDMCAhAAFw0yNDA4MjUwMzIz MDhaMB4wCgYDVR0VBAMKAQYwEAYDVR0XBAkGByqGSM44AgOgggEnMIIBIzAJBgNV HRIEAjAAMH0GA1UdIwR2MHSAFLjJrf2oUFTVhW40i0xgL7BJtodGoUakRDBCMQsw CQYDVQQGEwJVSzEPMA0GA1UECAwGTG9uZG9uMQswCQYDVQQKDAJCQjEVMBMGA1UE AwwMVGVzdCBSb290IENBghQ3XUv2vXwRfMxGGv/XLywm+B5LPTAtBgNVHS4EJjAk MCKgIKAehhxodHRwOi8vZXhhbXBsZS5jb20vZGVsdGEtY3JsMFsGA1UdHwRUMFIw IaAfoB2GG2h0dHA6Ly9leGFtcGxlLmNvbS9mdWxsLWNybDAhoB+gHYYbbGRhcDov L2V4YW1wbGUuY29tL2Z1bGwtY3JsMAqgCKAGhwR/AAABMAsGA1UdFAQEAgIePDAN BgkqhkiG9w0BAQsFAAOCAgEAAxsr+9nELUVWhFekwy6GsqH8xOf6EqGjRaEdX49W mB40m2VajOkK8UHGoVyZzoDI2r/c8OPXUtbpK0fpvEl3SZU5j/C8JbZaZFFrEGeH fSEqdVHFjohpawNcG41Qs+YT21TBqH1hD5yVI7gjVvfKICRfxDpl5oGClxBCVOSV gVtLbe9q44uCBJ1kUkoc9Vz47Hv7JyckgqVXkORWHt2SFNALxlMEzOEQTpuC5Kcb 4i7hTCUF+kpkIvr02LJImq0Aaqzs6cC/DcdJiRPPyfaN8fQryFv76gg9i8zZcb6c W42rvumiyw+7nnZfmq53webr5fCHaXhZk47ASOJD6GC5cX9rje1qGRgULXRhqcvK n319s2iXj3FStDDorKGgsCV2zYmotX17ExB98CcCgBE52zMtRZilwhOGeh8mx3qT l0W2B8uKKAq5BMmiziSBzQt700JPiruURZXbQ1fH1n7pKP6wGEh2e9TfQMlN20hE I+CMt+1bG0Bpt5AfiwE8UykQ/WvpVxdJrgj0JM0yA37KfC8XD+cmavJ5/grorbj3 t0zBdK7bl+Y45VU/5/mX5ZR3O3ea1RclPM3hKMREfPneOlpan6r3dVwFqEN/TeTu 46vuDeKaEr3yJkOFfy0lSYPhPhzhU5vDR5ibxqvwxZNznI2AdTnZLEf8LRqnTVo1 qx0= -----END X509 CRL-----`; const OUT_CRL_PEM_RSA = `Certificate Revocation List (CRL): Version: 2 (0x1) Signature Algorithm: SHA256withRSA Issuer: C = UK ST = London O = BB CN = Test Root CA Last Update: Sun, 25 Aug 2024 11:49:10 GMT Next Update: Tue, 24 Sep 2024 11:49:10 GMT CRL extensions: 2.5.29.46: Unsupported CRL extension. Try openssl CLI. X509v3 Authority Key Identifier: keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46 DirName:/C=UK/ST=London/O=BB/CN=Test Root CA serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D X509v3 CRL Distribution Points: Full Name: URI:http://example.com/full-crl Full Name: URI:ldap://example.com/full-crl Full Name: IP:127.0.0.1 X509v3 CRL Number: 1E3C issuerAltName: Unsupported CRL extension. Try openssl CLI. Revoked Certificates: Serial Number: 1000 Revocation Date: Sun, 25 Aug 2024 03:23:08 GMT CRL entry extensions: X509v3 CRL Reason Code: Certificate Hold Hold Instruction Code: Hold Instruction Reject Signature Value: 03:1b:2b:fb:d9:c4:2d:45:56:84:57:a4:c3:2e:86:b2:a1:fc: c4:e7:fa:12:a1:a3:45:a1:1d:5f:8f:56:98:1e:34:9b:65:5a: 8c:e9:0a:f1:41:c6:a1:5c:99:ce:80:c8:da:bf:dc:f0:e3:d7: 52:d6:e9:2b:47:e9:bc:49:77:49:95:39:8f:f0:bc:25:b6:5a: 64:51:6b:10:67:87:7d:21:2a:75:51:c5:8e:88:69:6b:03:5c: 1b:8d:50:b3:e6:13:db:54:c1:a8:7d:61:0f:9c:95:23:b8:23: 56:f7:ca:20:24:5f:c4:3a:65:e6:81:82:97:10:42:54:e4:95: 81:5b:4b:6d:ef:6a:e3:8b:82:04:9d:64:52:4a:1c:f5:5c:f8: ec:7b:fb:27:27:24:82:a5:57:90:e4:56:1e:dd:92:14:d0:0b: c6:53:04:cc:e1:10:4e:9b:82:e4:a7:1b:e2:2e:e1:4c:25:05: fa:4a:64:22:fa:f4:d8:b2:48:9a:ad:00:6a:ac:ec:e9:c0:bf: 0d:c7:49:89:13:cf:c9:f6:8d:f1:f4:2b:c8:5b:fb:ea:08:3d: 8b:cc:d9:71:be:9c:5b:8d:ab:be:e9:a2:cb:0f:bb:9e:76:5f: 9a:ae:77:c1:e6:eb:e5:f0:87:69:78:59:93:8e:c0:48:e2:43: e8:60:b9:71:7f:6b:8d:ed:6a:19:18:14:2d:74:61:a9:cb:ca: 9f:7d:7d:b3:68:97:8f:71:52:b4:30:e8:ac:a1:a0:b0:25:76: cd:89:a8:b5:7d:7b:13:10:7d:f0:27:02:80:11:39:db:33:2d: 45:98:a5:c2:13:86:7a:1f:26:c7:7a:93:97:45:b6:07:cb:8a: 28:0a:b9:04:c9:a2:ce:24:81:cd:0b:7b:d3:42:4f:8a:bb:94: 45:95:db:43:57:c7:d6:7e:e9:28:fe:b0:18:48:76:7b:d4:df: 40:c9:4d:db:48:44:23:e0:8c:b7:ed:5b:1b:40:69:b7:90:1f: 8b:01:3c:53:29:10:fd:6b:e9:57:17:49:ae:08:f4:24:cd:32: 03:7e:ca:7c:2f:17:0f:e7:26:6a:f2:79:fe:0a:e8:ad:b8:f7: b7:4c:c1:74:ae:db:97:e6:38:e5:55:3f:e7:f9:97:e5:94:77: 3b:77:9a:d5:17:25:3c:cd:e1:28:c4:44:7c:f9:de:3a:5a:5a: 9f:aa:f7:75:5c:05:a8:43:7f:4d:e4:ee:e3:ab:ee:0d:e2:9a: 12:bd:f2:26:43:85:7f:2d:25:49:83:e1:3e:1c:e1:53:9b:c3: 47:98:9b:c6:ab:f0:c5:93:73:9c:8d:80:75:39:d9:2c:47:fc: 2d:1a:a7:4d:5a:35:ab:1d`; const IN_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE = `-----BEGIN X509 CRL----- MIID9jCCAd4CAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN MjQwODI1MTIwODU2WhcNMjQwOTI0MTIwODU2WjA9MDsCAhAAFw0yNDA4MjUxMjA4 NDhaMCYwCgYDVR0VBAMKAQEwGAYDVR0YBBEYDzIwMjQwODI1MDAwMDAwWqCCAScw ggEjMAkGA1UdEgQCMAAwfQYDVR0jBHYwdIAUuMmt/ahQVNWFbjSLTGAvsEm2h0ah RqREMEIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQIDAZMb25kb24xCzAJBgNVBAoMAkJC MRUwEwYDVQQDDAxUZXN0IFJvb3QgQ0GCFDddS/a9fBF8zEYa/9cvLCb4Hks9MC0G A1UdLgQmMCQwIqAgoB6GHGh0dHA6Ly9leGFtcGxlLmNvbS9kZWx0YS1jcmwwWwYD VR0fBFQwUjAhoB+gHYYbaHR0cDovL2V4YW1wbGUuY29tL2Z1bGwtY3JsMCGgH6Ad hhtsZGFwOi8vZXhhbXBsZS5jb20vZnVsbC1jcmwwCqAIoAaHBH8AAAEwCwYDVR0U BAQCAh49MA0GCSqGSIb3DQEBCwUAA4ICAQByLp7JWQmB1NhlLACH6zFOe31yCTVy xJQtgujtSri1LNu6IwzBGsKBQIl3ucwMxPvoZzlujNLmshUT3nSogV0/5n1q0Gyj 5Yiz2iw8mmKJLmGZ9Oz3QoGxgFww0/0x/VwRHuS2hw+A7JB8tO/2nW3oTclvS55l R+VtkDjUN58+Yl2SQksvb3qD6bHHJTCaP7Dskls0fdBIoYIDvZejrTYSSzTX/Kw4 735P0GBMhj7zVF8azGz2PFpSISg4huJMyp7EDKZf2c2dnkuwmEUlPQEBLX25j/Il 81OxfVVFja+wUagaGtjEPGy5gsU8zFwkWhjaD5PGBbZvnT+EDsOtJPU7Ot/sBHfz XqUtMrfmz/S/GsQ+QCpnBvarBy9QYuk9M0ePBGy33CUQpjPULxuJJVAHxNoetHCv 7udng2Pi4D8vDNfzbMwHt7HurMo0CsSju+cL4rnIfsz02RrD9WC84KxBLWkqC7Hi IKGIpF740Yc4BliVE1HDaOKyI6FEft5asj3OgXwmBw7pVlxSNWACaA2vOFkdN/V5 XZZjVJdRJxkgEfCvsJVenFp8ND6gmJmWum7tqM5ytmiXjPtejsPpVq4IclG+Yhnr tFQ9TDEuCrNsRIGGGDodyXq1+kGXY0w8RqGEb7J4Og/M6r4LMAKPkO7e0nEibTqX d2igvR2e5p+yKw== -----END X509 CRL-----`; const OUT_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE = `Certificate Revocation List (CRL): Version: 2 (0x1) Signature Algorithm: SHA256withRSA Issuer: C = UK ST = London O = BB CN = Test Root CA Last Update: Sun, 25 Aug 2024 12:08:56 GMT Next Update: Tue, 24 Sep 2024 12:08:56 GMT CRL extensions: 2.5.29.46: Unsupported CRL extension. Try openssl CLI. X509v3 Authority Key Identifier: keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46 DirName:/C=UK/ST=London/O=BB/CN=Test Root CA serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D X509v3 CRL Distribution Points: Full Name: URI:http://example.com/full-crl Full Name: URI:ldap://example.com/full-crl Full Name: IP:127.0.0.1 X509v3 CRL Number: 1E3D issuerAltName: Unsupported CRL extension. Try openssl CLI. Revoked Certificates: Serial Number: 1000 Revocation Date: Sun, 25 Aug 2024 12:08:48 GMT CRL entry extensions: X509v3 CRL Reason Code: Key Compromise Invalidity Date: Sun, 25 Aug 2024 00:00:00 GMT Signature Value: 72:2e:9e:c9:59:09:81:d4:d8:65:2c:00:87:eb:31:4e:7b:7d: 72:09:35:72:c4:94:2d:82:e8:ed:4a:b8:b5:2c:db:ba:23:0c: c1:1a:c2:81:40:89:77:b9:cc:0c:c4:fb:e8:67:39:6e:8c:d2: e6:b2:15:13:de:74:a8:81:5d:3f:e6:7d:6a:d0:6c:a3:e5:88: b3:da:2c:3c:9a:62:89:2e:61:99:f4:ec:f7:42:81:b1:80:5c: 30:d3:fd:31:fd:5c:11:1e:e4:b6:87:0f:80:ec:90:7c:b4:ef: f6:9d:6d:e8:4d:c9:6f:4b:9e:65:47:e5:6d:90:38:d4:37:9f: 3e:62:5d:92:42:4b:2f:6f:7a:83:e9:b1:c7:25:30:9a:3f:b0: ec:92:5b:34:7d:d0:48:a1:82:03:bd:97:a3:ad:36:12:4b:34: d7:fc:ac:38:ef:7e:4f:d0:60:4c:86:3e:f3:54:5f:1a:cc:6c: f6:3c:5a:52:21:28:38:86:e2:4c:ca:9e:c4:0c:a6:5f:d9:cd: 9d:9e:4b:b0:98:45:25:3d:01:01:2d:7d:b9:8f:f2:25:f3:53: b1:7d:55:45:8d:af:b0:51:a8:1a:1a:d8:c4:3c:6c:b9:82:c5: 3c:cc:5c:24:5a:18:da:0f:93:c6:05:b6:6f:9d:3f:84:0e:c3: ad:24:f5:3b:3a:df:ec:04:77:f3:5e:a5:2d:32:b7:e6:cf:f4: bf:1a:c4:3e:40:2a:67:06:f6:ab:07:2f:50:62:e9:3d:33:47: 8f:04:6c:b7:dc:25:10:a6:33:d4:2f:1b:89:25:50:07:c4:da: 1e:b4:70:af:ee:e7:67:83:63:e2:e0:3f:2f:0c:d7:f3:6c:cc: 07:b7:b1:ee:ac:ca:34:0a:c4:a3:bb:e7:0b:e2:b9:c8:7e:cc: f4:d9:1a:c3:f5:60:bc:e0:ac:41:2d:69:2a:0b:b1:e2:20:a1: 88:a4:5e:f8:d1:87:38:06:58:95:13:51:c3:68:e2:b2:23:a1: 44:7e:de:5a:b2:3d:ce:81:7c:26:07:0e:e9:56:5c:52:35:60: 02:68:0d:af:38:59:1d:37:f5:79:5d:96:63:54:97:51:27:19: 20:11:f0:af:b0:95:5e:9c:5a:7c:34:3e:a0:98:99:96:ba:6e: ed:a8:ce:72:b6:68:97:8c:fb:5e:8e:c3:e9:56:ae:08:72:51: be:62:19:eb:b4:54:3d:4c:31:2e:0a:b3:6c:44:81:86:18:3a: 1d:c9:7a:b5:fa:41:97:63:4c:3c:46:a1:84:6f:b2:78:3a:0f: cc:ea:be:0b:30:02:8f:90:ee:de:d2:71:22:6d:3a:97:77:68: a0:bd:1d:9e:e6:9f:b2:2b`; const IN_CRL_PEM_RSA_CRL_EXTENSIONS = `-----BEGIN X509 CRL----- MIIE0DCCArgCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN MjQwODI1MTIzNzEwWhcNMjQwOTI0MTIzNzEwWjA9MDsCAhAAFw0yNDA4MjUxMjA4 NDhaMCYwCgYDVR0VBAMKAQEwGAYDVR0YBBEYDzIwMjQwODI1MDAwMDAwWqCCAgEw ggH9MIHiBgNVHRIEgdowgdegFAYEKgMEBaAMFgpDdXN0b21OYW1lgQ5jYUBleGFt cGxlLmNvbYYSaHR0cDovL2V4YW1wbGUuY29tgg5jYS5leGFtcGxlLmNvbYcEwKgB AaSBhDCBgTELMAkGA1UEBhMCVVMxFTATBgNVBAgMDEV4YW1wbGVTdGF0ZTEUMBIG A1UEBwwLRXhhbXBsZUNpdHkxEzARBgNVBAoMCkV4YW1wbGVPcmcxFDASBgNVBAsM C0V4YW1wbGVVbml0MRowGAYDVQQDDBFFeGFtcGxlQ29tbW9uTmFtZTB9BgNVHSME djB0gBS4ya39qFBU1YVuNItMYC+wSbaHRqFGpEQwQjELMAkGA1UEBhMCVUsxDzAN BgNVBAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBD QYIUN11L9r18EXzMRhr/1y8sJvgeSz0wLQYDVR0uBCYwJDAioCCgHoYcaHR0cDov L2V4YW1wbGUuY29tL2RlbHRhLWNybDBbBgNVHR8EVDBSMCGgH6AdhhtodHRwOi8v ZXhhbXBsZS5jb20vZnVsbC1jcmwwIaAfoB2GG2xkYXA6Ly9leGFtcGxlLmNvbS9m dWxsLWNybDAKoAigBocEfwAAATALBgNVHRQEBAICHkIwDQYJKoZIhvcNAQELBQAD ggIBAF/9L4aGmId2igw7+MfDxokevIJkJX/MkmHpXBl1b4hL85FGD7OPCmn47VzC Wejlc/AQB7mWyUugvrVEq/FiCO8a8Fieyjw5uCYz0eiNnuvHVRGM2mOEkiA0I/rn F5AFB1YfCFGXPyRkXNRbOBE91mhOzh1H9PX2qVnj5l3KsPE/7YuteacR0TkfkRJa BXLic+5F/CCV/J/iYR7LncuLUlhBfsosG/ucHL70EytlfX6CBWY3kBbmj7nd497T QG392+m9xp7MIsJAS+3qEzwJAfni6zUV0fWh/ucOl8BIjHEh97VqI3+8yzhdXfkF 2gkfpkqJQY0+5OO1VSRYTlQNld3QjN/VVJjatfHyaXfPCx4VEKW1kWYo+0zxO4SL SB/+S/o99bCeNy1MXqEvy5HoDwFHePXGsAEPHWPdj7EWm7g9T/Fl1iSR6hpohvDD K4LaGdVhzvCraLIh8H7XW3KztvZvDQejYQAgADW0UO0rFHJ1XXhKYSqXNGnfDt+3 cRpt2XxSxt5HJtHlatiI25PuBMNWV2Zod4RHB/8UEvs1KC7dcwkAiCEY+E3o/zkC rdZ/8XtNf5a4WSN/D7pPsfsO6SE+7lxkJ+UQcZLXAz8b5ArPTlWt2HdJIBEVs25K FAkizyldhnAcNHFk7XN94eTLNeD6hUbFL9pNHiSmKu5A9YW0 -----END X509 CRL-----`; const OUT_CRL_PEM_RSA_CRL_EXTENSIONS = `Certificate Revocation List (CRL): Version: 2 (0x1) Signature Algorithm: SHA256withRSA Issuer: C = UK ST = London O = BB CN = Test Root CA Last Update: Sun, 25 Aug 2024 12:37:10 GMT Next Update: Tue, 24 Sep 2024 12:37:10 GMT CRL extensions: 2.5.29.46: Unsupported CRL extension. Try openssl CLI. X509v3 Authority Key Identifier: keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46 DirName:/C=UK/ST=London/O=BB/CN=Test Root CA serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D X509v3 CRL Distribution Points: Full Name: URI:http://example.com/full-crl Full Name: URI:ldap://example.com/full-crl Full Name: IP:127.0.0.1 X509v3 CRL Number: 1E42 X509v3 Issuer Alternative Name: OtherName:1.2.3.4.5::CustomName EMAIL:ca@example.com URI:http://example.com DNS:ca.example.com IP:192.168.1.1 DIR:/C=US/ST=ExampleState/L=ExampleCity/O=ExampleOrg/OU=ExampleUnit/CN=ExampleCommonName Revoked Certificates: Serial Number: 1000 Revocation Date: Sun, 25 Aug 2024 12:08:48 GMT CRL entry extensions: X509v3 CRL Reason Code: Key Compromise Invalidity Date: Sun, 25 Aug 2024 00:00:00 GMT Signature Value: 5f:fd:2f:86:86:98:87:76:8a:0c:3b:f8:c7:c3:c6:89:1e:bc: 82:64:25:7f:cc:92:61:e9:5c:19:75:6f:88:4b:f3:91:46:0f: b3:8f:0a:69:f8:ed:5c:c2:59:e8:e5:73:f0:10:07:b9:96:c9: 4b:a0:be:b5:44:ab:f1:62:08:ef:1a:f0:58:9e:ca:3c:39:b8: 26:33:d1:e8:8d:9e:eb:c7:55:11:8c:da:63:84:92:20:34:23: fa:e7:17:90:05:07:56:1f:08:51:97:3f:24:64:5c:d4:5b:38: 11:3d:d6:68:4e:ce:1d:47:f4:f5:f6:a9:59:e3:e6:5d:ca:b0: f1:3f:ed:8b:ad:79:a7:11:d1:39:1f:91:12:5a:05:72:e2:73: ee:45:fc:20:95:fc:9f:e2:61:1e:cb:9d:cb:8b:52:58:41:7e: ca:2c:1b:fb:9c:1c:be:f4:13:2b:65:7d:7e:82:05:66:37:90: 16:e6:8f:b9:dd:e3:de:d3:40:6d:fd:db:e9:bd:c6:9e:cc:22: c2:40:4b:ed:ea:13:3c:09:01:f9:e2:eb:35:15:d1:f5:a1:fe: e7:0e:97:c0:48:8c:71:21:f7:b5:6a:23:7f:bc:cb:38:5d:5d: f9:05:da:09:1f:a6:4a:89:41:8d:3e:e4:e3:b5:55:24:58:4e: 54:0d:95:dd:d0:8c:df:d5:54:98:da:b5:f1:f2:69:77:cf:0b: 1e:15:10:a5:b5:91:66:28:fb:4c:f1:3b:84:8b:48:1f:fe:4b: fa:3d:f5:b0:9e:37:2d:4c:5e:a1:2f:cb:91:e8:0f:01:47:78: f5:c6:b0:01:0f:1d:63:dd:8f:b1:16:9b:b8:3d:4f:f1:65:d6: 24:91:ea:1a:68:86:f0:c3:2b:82:da:19:d5:61:ce:f0:ab:68: b2:21:f0:7e:d7:5b:72:b3:b6:f6:6f:0d:07:a3:61:00:20:00: 35:b4:50:ed:2b:14:72:75:5d:78:4a:61:2a:97:34:69:df:0e: df:b7:71:1a:6d:d9:7c:52:c6:de:47:26:d1:e5:6a:d8:88:db: 93:ee:04:c3:56:57:66:68:77:84:47:07:ff:14:12:fb:35:28: 2e:dd:73:09:00:88:21:18:f8:4d:e8:ff:39:02:ad:d6:7f:f1: 7b:4d:7f:96:b8:59:23:7f:0f:ba:4f:b1:fb:0e:e9:21:3e:ee: 5c:64:27:e5:10:71:92:d7:03:3f:1b:e4:0a:cf:4e:55:ad:d8: 77:49:20:11:15:b3:6e:4a:14:09:22:cf:29:5d:86:70:1c:34: 71:64:ed:73:7d:e1:e4:cb:35:e0:fa:85:46:c5:2f:da:4d:1e: 24:a6:2a:ee:40:f5:85:b4`; TestRegister.addTests([ { name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature", input: IN_CRL_PEM_RSA, expectedOutput: OUT_CRL_PEM_RSA, recipeConfig: [ { "op": "Parse X.509 CRL", "args": ["PEM"] } ] }, { name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature, CRL Reason and Invalidity Date", input: IN_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE, expectedOutput: OUT_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE, recipeConfig: [ { "op": "Parse X.509 CRL", "args": ["PEM"] } ] }, { name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature and CRL Extensions", input: IN_CRL_PEM_RSA_CRL_EXTENSIONS, expectedOutput: OUT_CRL_PEM_RSA_CRL_EXTENSIONS, recipeConfig: [ { "op": "Parse X.509 CRL", "args": ["PEM"] } ] }, ]); ================================================ FILE: tests/operations/tests/PowerSet.mjs ================================================ /** * Power Set tests. * * @author d98762625 * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Power set: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "Power Set", args: [","], }, ], }, { name: "Power set", input: "1 2 4", expectedOutput: "\n4\n2\n1\n2 4\n1 4\n1 2\n1 2 4\n", recipeConfig: [ { op: "Power Set", args: [" "], }, ], }, ]); ================================================ FILE: tests/operations/tests/Protobuf.mjs ================================================ /** * Protobuf tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Protobuf Decode: no schema", input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200", expectedOutput: JSON.stringify({ "1": 28, "2": "You", "3": "Me", "4": 43, "5": { "1": "abc123", "2": {} } }, null, 4), recipeConfig: [ { "op": "From Hex", "args": ["Auto"] }, { "op": "Protobuf Decode", "args": ["", false, false] } ] }, { name: "Protobuf Decode: partial schema, no unknown fields", input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200", expectedOutput: JSON.stringify({ "Apple": [ 28 ], "Carrot": [ "Me" ], "Banana": "You" }, null, 4), recipeConfig: [ { "op": "From Hex", "args": ["Auto"] }, { "op": "Protobuf Decode", "args": [ `message Test { repeated fixed32 Apple = 1; optional string Banana = 2; repeated string Carrot = 3; }`, false, false ] } ] }, { name: "Protobuf Decode: partial schema, show unknown fields", input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200", expectedOutput: JSON.stringify({ "Test": { "Apple": [ 28 ], "Carrot": [ "Me" ], "Banana": "You" }, "Unknown Fields": { "4": 43, "5": { "1": "abc123", "2": {} } } }, null, 4), recipeConfig: [ { "op": "From Hex", "args": ["Auto"] }, { "op": "Protobuf Decode", "args": [ `message Test { repeated fixed32 Apple = 1; optional string Banana = 2; repeated string Carrot = 3; }`, true, false ] } ] }, { name: "Protobuf Decode: full schema, no unknown fields", input: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ff00000000000000", expectedOutput: JSON.stringify({ "Apple": [ 28 ], "Carrot": [ "Me" ], "Banana": "You", "Date": 43, "Elderberry": { "Fig": "abc123", "Grape": {} }, "Huckleberry": 255 }, null, 4), recipeConfig: [ { "op": "From Hex", "args": ["Auto"] }, { "op": "Protobuf Decode", "args": [ `message Test { repeated fixed32 Apple = 1; optional string Banana = 2; repeated string Carrot = 3; optional int32 Date = 4; optional subTest Elderberry = 5; optional fixed64 Huckleberry = 6; } message subTest { optional string Fig = 1; optional subSubTest Grape = 2; } message subSubTest {}`, false, false ] } ] }, { name: "Protobuf Decode: partial schema, show unknown fields, show types", input: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ba32a96cc10200003801", expectedOutput: JSON.stringify({ "Test": { "Carrot (string)": [ "Me" ], "Banana (string)": "You", "Date (int32)": 43, "Imbe (Options)": "Option1" }, "Unknown Fields": { "field #1: 32-Bit (e.g. fixed32, float)": 28, "field #5: L-delim (e.g. string, message)": { "field #1: L-delim (e.g. string, message)": "abc123", "field #2: L-delim (e.g. string, message)": {} }, "field #6: 64-Bit (e.g. fixed64, double)": 3029774971578 } }, null, 4), recipeConfig: [ { "op": "From Hex", "args": ["Auto"] }, { "op": "Protobuf Decode", "args": [ `message Test { optional string Banana = 2; repeated string Carrot = 3; optional int32 Date = 4; optional Options Imbe = 7; } message subTest { optional string Fig = 1; optional subSubTest Grape = 2; } message subSubTest {} enum Options { Option0 = 0; Option1 = 1; Option2 = 2; }`, true, true ] } ] }, { name: "Protobuf Encode", input: JSON.stringify({ "Apple": [ 28 ], "Banana": "You", "Carrot": [ "Me" ], "Date": 43, "Elderberry": { "Fig": "abc123", "Grape": {} }, "Huckleberry": [3029774971578], "Imbe": 1 }, null, 4), expectedOutput: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ba32a96cc10200003801", recipeConfig: [ { "op": "Protobuf Encode", "args": [ `message Test { repeated fixed32 Apple = 1; optional string Banana = 2; repeated string Carrot = 3; optional int32 Date = 4; optional subTest Elderberry = 5; repeated fixed64 Huckleberry = 6; optional Options Imbe = 7; } message subTest { optional string Fig = 1; optional subSubTest Grape = 2; } message subSubTest {} enum Options { Option0 = 0; Option1 = 1; Option2 = 2; }` ] }, { "op": "To Hex", "args": [ "None", 0 ] } ] }, { name: "Protobuf Encode: incomplete schema", input: JSON.stringify({ "Apple": [ 28 ], "Banana": "You", "Carrot": [ "Me" ], "Date": 43, "Elderberry": { "Fig": "abc123", "Grape": {} }, "Huckleberry": [3029774971578], "Imbe": 1 }, null, 4), expectedOutput: "1203596f75202b2a0a0a06616263313233120031ba32a96cc1020000", recipeConfig: [ { "op": "Protobuf Encode", "args": [ `message Test { optional string Banana = 2; optional int32 Date = 4; optional subTest Elderberry = 5; repeated fixed64 Huckleberry = 6; } message subTest { optional string Fig = 1; optional subSubTest Grape = 2; } message subSubTest {} enum Options { Option0 = 0; Option1 = 1; Option2 = 2; }` ] }, { "op": "To Hex", "args": [ "None", 0 ] } ] }, ]); ================================================ FILE: tests/operations/tests/PubKeyFromCert.mjs ================================================ /** * Public Key from Certificate * * @author cplussharp * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const RSA_CERT = `-----BEGIN CERTIFICATE----- MIIBfTCCASegAwIBAgIUeisK5Nwss2DGg5PCs4uSxxXyyNkwDQYJKoZIhvcNAQEL BQAwEzERMA8GA1UEAwwIUlNBIHRlc3QwHhcNMjExMTE5MTcyMDI2WhcNMzExMTE3 MTcyMDI2WjATMREwDwYDVQQDDAhSU0EgdGVzdDBcMA0GCSqGSIb3DQEBAQUAA0sA MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB 757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAGjUzBRMB0GA1UdDgQWBBRO+jvkqq5p pnQgwMMnRoun6e7eiTAfBgNVHSMEGDAWgBRO+jvkqq5ppnQgwMMnRoun6e7eiTAP BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAR/5HAZM5qBhU/ezDUIFx gmUGoFbIb5kJD41YCnaSdrgWglh4He4melSs42G/oxBBjuCJ0bUpqWnLl+lJkv1z IA== -----END CERTIFICATE-----`; const RSA_PUBKEY = `-----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ== -----END PUBLIC KEY-----`; const EC_P256_CERT = `-----BEGIN CERTIFICATE----- MIIBfzCCASWgAwIBAgIUK4H8J3Hr7NpRLPrACj8Pje4JJJ0wCgYIKoZIzj0EAwIw FTETMBEGA1UEAwwKUC0yNTYgdGVzdDAeFw0yMTExMTkxNzE5NDVaFw0zMTExMTcx NzE5NDVaMBUxEzARBgNVBAMMClAtMjU2IHRlc3QwWTATBgcqhkjOPQIBBggqhkjO PQMBBwNCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC6yBwATwfrzXR+QnxmZM2IIJr qwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7po1MwUTAdBgNVHQ4EFgQU/SxodXrpkybM gcIgkxnRKd7HMzowHwYDVR0jBBgwFoAU/SxodXrpkybMgcIgkxnRKd7HMzowDwYD VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBU9PrOa/kXCpTTBInRf/sN ac2iDHmbdpWzcXI+xLKNYAIhAIRR1LRSHVwOTLQ/iBXd+8LCkm5aTB27RW46LN80 ylxt -----END CERTIFICATE-----`; const EC_P256_PUBKEY = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== -----END PUBLIC KEY-----`; const DSA_CERT = `-----BEGIN CERTIFICATE----- MIIEXzCCBA2gAwIBAgIUYYcPJB8UQLzUnqkGJvs3J4RI0OgwCwYJYIZIAWUDBAMC MBMxETAPBgNVBAMMCERTQSBUZXN0MB4XDTIzMTAxNTAwMjEzNVoXDTMzMTAxMjAw MjEzNVowEzERMA8GA1UEAwwIRFNBIFRlc3QwggNCMIICNQYHKoZIzjgEATCCAigC ggEBALoLV+uz7vMYZCIuwXNkgZawvDgZAG1T7IiG030WgqesRNncuoUQOmAJCiuN zkjVNSY08rabex/RIkWILvxP91SlzhA9t9+dp87p238ecxGa1sD2re+y35RP7IxN T33633NtwGItZ3BqqAhoMmuwwwxau0E8zwYodTTlwTRp4QVPpMH1eJCUBeEzcWP5 ZZ1lRNhR5M2TqzSU3ya5/4c3a9rI86h9VIVgw8yVvw3y6yclzjALm2ntD5riskdM Z6mMkfYQwEbIGRTELX6A7LZ0lX1CislenF9ASb2E4g2nGcMQ0uSGzA4W9mf6wwmP S6iwX5+Qu/i6jCm5i37fQ1H5HHUCHQDA+UnPHM6PZEgfFen8djZpl/cl05MpWk+d nikFAoIBADXOTpBw0WA+UihxDG+6qqM05kxVMYmz6IRZ/06ffZSGVFN6Bx1i0s3v kzM5V8GsKpkKkSk7V8fTQnAIIlMmt1Y7ff+ng7+TfYotMrvvEYlolYK06J2WWoUA 8iKp8+n58vdoky+xZmuGmcvCAojVDbEeU2wEqYE1PzrHCSOoOiKB2P4fOhyuF+qx E8nkzURIg2RmSSkqWOkXiWyKyfpUaB+4cEisp4ThENEPmdntE1vLh2r7EOIxpE5D 0NAy2wFKqe3ljfgE6XsPZKgVAguRDVpzdmL6WDY7DM/BcS726vx+kX55QDkszvec raNirnir2QrB/a0JQjF6Y62yGmG7GF8DggEFAAKCAQBpN+w0N0b5IIAspXnlJ9yu B6ORk3j/5rZ+DUtTzW1YAJI6xjTcFQvN7FpVLkmLtXKUXF04R+sdGJ7VFwOb0rba L5vQzrqNkBrbgSzuzeloiG+7OLA6VeQtNbQh6OurrZFi9gY+qA5ciT9kQXyrHudV Xu956NDrooRxmv6JIVFvToaNiwe2vcgdkALw8HUbLFYof4SAE9jgU8EpxTp02e8H zvVSVa6yj1nnGhpzLPlEqF8TZvs9pTg2kIk3/zvWojMJoPyTALfbTjbAeiFMMeKN K/CKOOJj23AVAZxpMSR6cUbrIcRdKDnhCTVkkxXUecAIUs6Mk10kSfkuiGl9LjKj o1MwUTAdBgNVHQ4EFgQUE+xZdvgiDIFWKQskMYnNaZ3iPHAwHwYDVR0jBBgwFoAU E+xZdvgiDIFWKQskMYnNaZ3iPHAwDwYDVR0TAQH/BAUwAwEB/zALBglghkgBZQME AwIDPwAwPAIcZbtf4+bjXEGQqNs6IglLrOgIjYF46q7qCNfXmQIcMKUtH3S6sDJE 3ds9eL+oC+HPFlfUNfUiU30aDA== -----END CERTIFICATE-----`; const DSA_PUBKEY = `-----BEGIN PUBLIC KEY----- MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4GQBt U+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4QPbff nafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtBPM8G KHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOofVSF YMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJXpxf QEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0AwPlJ zxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqjNOZM VTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdWO33/ p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2xHlNs BKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgfuHBI rKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1ac3Zi +lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhfA4IB BQACggEAaTfsNDdG+SCALKV55SfcrgejkZN4/+a2fg1LU81tWACSOsY03BULzexa VS5Ji7VylFxdOEfrHRie1RcDm9K22i+b0M66jZAa24Es7s3paIhvuziwOlXkLTW0 Iejrq62RYvYGPqgOXIk/ZEF8qx7nVV7veejQ66KEcZr+iSFRb06GjYsHtr3IHZAC 8PB1GyxWKH+EgBPY4FPBKcU6dNnvB871UlWuso9Z5xoacyz5RKhfE2b7PaU4NpCJ N/871qIzCaD8kwC32042wHohTDHijSvwijjiY9twFQGcaTEkenFG6yHEXSg54Qk1 ZJMV1HnACFLOjJNdJEn5LohpfS4yow== -----END PUBLIC KEY-----`; const ED25519_CERT = `-----BEGIN CERTIFICATE----- MIIBQjCB9aADAgECAhRjPJhrdNco5LzpsIs0vSLLaZaZ0DAFBgMrZXAwFzEVMBMG A1UEAwwMRWQyNTUxOSBUZXN0MB4XDTIzMTAxNTAwMjMwOFoXDTMzMTAxMjAwMjMw OFowFzEVMBMGA1UEAwwMRWQyNTUxOSBUZXN0MCowBQYDK2VwAyEAELP6AflXwsuZ 5q4NDIO0LP2iCdKRvds4nwsUmRhOw3ijUzBRMB0GA1UdDgQWBBRfxS9q0IemWxkH 4mwAwzr9dQx2xzAfBgNVHSMEGDAWgBRfxS9q0IemWxkH4mwAwzr9dQx2xzAPBgNV HRMBAf8EBTADAQH/MAUGAytlcANBAI/+03iVq4yJ+DaLVs61w41cVX2UxKvquSzv lllkpkclM9LH5dLrw4ArdTjS9zAjzY/02WkphHhICHXt3KqZTwI= -----END CERTIFICATE-----`; /* const ED25519_PUBKEY = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAELP6AflXwsuZ5q4NDIO0LP2iCdKRvds4nwsUmRhOw3g= -----END PUBLIC KEY-----`; */ const ED448_CERT = `-----BEGIN CERTIFICATE----- MIIBijCCAQqgAwIBAgIUZaCS7zEjOnQ7O4KUFym6fJF5vl8wBQYDK2VxMBUxEzAR BgNVBAMMCkVkNDQ4IFRlc3QwHhcNMjMxMDE1MDAyMzI1WhcNMzMxMDEyMDAyMzI1 WjAVMRMwEQYDVQQDDApFZDQ0OCBUZXN0MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/Ov BTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRlEHQwXsNYLZTtY2Jra6AWhbVYYaEAo1Mw UTAdBgNVHQ4EFgQUJFrepAf9YXrmDMSAzrMeYQmosd0wHwYDVR0jBBgwFoAUJFre pAf9YXrmDMSAzrMeYQmosd0wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXEDcwA+YiZj puFr2aogfV1qg/ixk7qLi25BbKVNR6+7PEUjo7+4yBn9qnLbAHUGnHn7E96pSey9 VkLqpoDNMRcM3Eb6h3AJpQM0oxGj8q9arjDXqJkXgaO2e0tVn8KKVfy7S8qO72Kd rWzZowcOjnWKhXm7JgA= -----END CERTIFICATE-----`; /* const ED448_PUBKEY = `-----BEGIN PUBLIC KEY----- MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/OvBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRl EHQwXsNYLZTtY2Jra6AWhbVYYaEA -----END PUBLIC KEY-----` */ TestRegister.addTests([ { name: "Public Key from Certificate: Missing footer", input: RSA_CERT.substring(0, RSA_CERT.length / 2), expectedOutput: "PEM footer '-----END CERTIFICATE-----' not found", recipeConfig: [ { op: "Public Key from Certificate", args: [], } ], }, // test RSA certificate { name: "Public Key from Certificate: RSA", input: RSA_CERT, expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Certificate", args: [], } ], }, // test EC certificate { name: "Public Key from Certificate: EC", input: EC_P256_CERT, expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Certificate", args: [], } ], }, // test DSA certificate { name: "Public Key from Certificate: DSA", input: DSA_CERT, expectedOutput: (DSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Certificate", args: [], } ], }, // test EdDSA certificates { name: "Public Key from Certificate: Ed25519", input: ED25519_CERT, expectedOutput: "Unsupported public key type", recipeConfig: [ { op: "Public Key from Certificate", args: [], } ], }, { name: "Public Key from Certificate: Ed448", input: ED448_CERT, expectedOutput: "Unsupported public key type", recipeConfig: [ { op: "Public Key from Certificate", args: [], } ], }, // test multi-input { name: "Public Key from Certificate: Multiple certificates", input: RSA_CERT + "\n" + EC_P256_CERT, expectedOutput: (RSA_PUBKEY + "\n" + EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Certificate", args: [], } ], } ]); ================================================ FILE: tests/operations/tests/PubKeyFromPrivKey.mjs ================================================ /** * Public Key from Private Key * * @author cplussharp * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; const RSA_PRIVKEY_PKCS1 = `-----BEGIN RSA PRIVATE KEY----- MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p 6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o= -----END RSA PRIVATE KEY-----`; const RSA_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY----- MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA8qvQOnph0i3M5+Tp ruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe+e3yy0ys/nS3qOrBZDYSMx 2SPp+wIDAQABAkA4lSkzSW/fowBBHdYDCwUXsM7L4ueKC14K9o1aI1XFnqMHIHlB vuxqWX9CW72wUjRnCuVVwpLSSV157slNiUtZAiEA/dQoR862lOBPW1iZ2FqqPGox /EGibRGL6u31qOXOzq8CIQD0vzkHz8advunoB/v/CmZp8vbvs5XhfjEF/MxH5NTM dQIgGr5UtCQidpMfqVcvdm0vDIh/1b0wmN9FZ65EuNPlsz8CIEXsrDqTHkfLV8C3 J0QCkKgMhoiNYnD+QLtZMnQH6NRRAiBXp3A1LKPR95qR4Pz3SCYh36W+X/D/ehRe JzOq+X832g== -----END PRIVATE KEY-----`; const RSA_PUBKEY = `-----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ== -----END PUBLIC KEY-----`; const EC_P256_PRIVKEY_SEC1 = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49 AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== -----END EC PRIVATE KEY-----`; const EC_P256_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC 6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p -----END PRIVATE KEY-----`; const EC_P256_PUBKEY = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== -----END PUBLIC KEY-----`; const DSA_PRIVKEY_TRAD = `-----BEGIN DSA PRIVATE KEY----- MIIDTQIBAAKCAQEAugtX67Pu8xhkIi7Bc2SBlrC8OBkAbVPsiIbTfRaCp6xE2dy6 hRA6YAkKK43OSNU1JjTytpt7H9EiRYgu/E/3VKXOED23352nzunbfx5zEZrWwPat 77LflE/sjE1Pffrfc23AYi1ncGqoCGgya7DDDFq7QTzPBih1NOXBNGnhBU+kwfV4 kJQF4TNxY/llnWVE2FHkzZOrNJTfJrn/hzdr2sjzqH1UhWDDzJW/DfLrJyXOMAub ae0PmuKyR0xnqYyR9hDARsgZFMQtfoDstnSVfUKKyV6cX0BJvYTiDacZwxDS5IbM Dhb2Z/rDCY9LqLBfn5C7+LqMKbmLft9DUfkcdQIdAMD5Sc8czo9kSB8V6fx2NmmX 9yXTkylaT52eKQUCggEANc5OkHDRYD5SKHEMb7qqozTmTFUxibPohFn/Tp99lIZU U3oHHWLSze+TMzlXwawqmQqRKTtXx9NCcAgiUya3Vjt9/6eDv5N9ii0yu+8RiWiV grTonZZahQDyIqnz6fny92iTL7Fma4aZy8ICiNUNsR5TbASpgTU/OscJI6g6IoHY /h86HK4X6rETyeTNREiDZGZJKSpY6ReJbIrJ+lRoH7hwSKynhOEQ0Q+Z2e0TW8uH avsQ4jGkTkPQ0DLbAUqp7eWN+ATpew9kqBUCC5ENWnN2YvpYNjsMz8FxLvbq/H6R fnlAOSzO95yto2KueKvZCsH9rQlCMXpjrbIaYbsYXwKCAQBpN+w0N0b5IIAspXnl J9yuB6ORk3j/5rZ+DUtTzW1YAJI6xjTcFQvN7FpVLkmLtXKUXF04R+sdGJ7VFwOb 0rbaL5vQzrqNkBrbgSzuzeloiG+7OLA6VeQtNbQh6OurrZFi9gY+qA5ciT9kQXyr HudVXu956NDrooRxmv6JIVFvToaNiwe2vcgdkALw8HUbLFYof4SAE9jgU8EpxTp0 2e8HzvVSVa6yj1nnGhpzLPlEqF8TZvs9pTg2kIk3/zvWojMJoPyTALfbTjbAeiFM MeKNK/CKOOJj23AVAZxpMSR6cUbrIcRdKDnhCTVkkxXUecAIUs6Mk10kSfkuiGl9 LjKjAhwpK4MOpkKEu+y308fZ+yZXypZW2m9Y/wOT0L4g -----END DSA PRIVATE KEY-----`; const DSA_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY----- MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4 GQBtU+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4Q PbffnafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtB PM8GKHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOo fVSFYMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJ XpxfQEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0A wPlJzxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqj NOZMVTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdW O33/p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2x HlNsBKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgf uHBIrKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1a c3Zi+lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhf BB4CHCkrgw6mQoS77LfTx9n7JlfKllbab1j/A5PQviA= -----END PRIVATE KEY-----`; const DSA_PUBKEY = `-----BEGIN PUBLIC KEY----- MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4GQBt U+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4QPbff nafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtBPM8G KHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOofVSF YMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJXpxf QEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0AwPlJ zxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqjNOZM VTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdWO33/ p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2xHlNs BKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgfuHBI rKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1ac3Zi +lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhfA4IB BQACggEAaTfsNDdG+SCALKV55SfcrgejkZN4/+a2fg1LU81tWACSOsY03BULzexa VS5Ji7VylFxdOEfrHRie1RcDm9K22i+b0M66jZAa24Es7s3paIhvuziwOlXkLTW0 Iejrq62RYvYGPqgOXIk/ZEF8qx7nVV7veejQ66KEcZr+iSFRb06GjYsHtr3IHZAC 8PB1GyxWKH+EgBPY4FPBKcU6dNnvB871UlWuso9Z5xoacyz5RKhfE2b7PaU4NpCJ N/871qIzCaD8kwC32042wHohTDHijSvwijjiY9twFQGcaTEkenFG6yHEXSg54Qk1 ZJMV1HnACFLOjJNdJEn5LohpfS4yow== -----END PUBLIC KEY-----`; const ED25519_PRIVKEY = `-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIC18vtoHINC8Mo9dTIqOrBs3J28ZvHrwzRq57g2kpV98 -----END PRIVATE KEY-----`; /* const ED25519_PUBKEY = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAELP6AflXwsuZ5q4NDIO0LP2iCdKRvds4nwsUmRhOw3g= -----END PUBLIC KEY-----`; */ const ED448_PRIVKEY = `-----BEGIN PRIVATE KEY----- MEcCAQAwBQYDK2VxBDsEOWdGJ06bDcWznJhBoQqPeTfsCe+AvBv1n7KfIGYzR4tv 1kcwHnbxlemnCMgqvbrRXaLuFUBysUZThA== -----END PRIVATE KEY-----`; /* const ED448_PUBKEY = `-----BEGIN PUBLIC KEY----- MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/OvBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRl EHQwXsNYLZTtY2Jra6AWhbVYYaEA -----END PUBLIC KEY-----`; */ TestRegister.addTests([ { name: "Public Key from Private Key: Missing footer", input: RSA_PRIVKEY_PKCS1.substring(0, RSA_PRIVKEY_PKCS1.length / 2), expectedOutput: "PEM footer '-----END RSA PRIVATE KEY-----' not found", recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, // test RSA { name: "Public Key from Private Key: RSA PKCS#1", input: RSA_PRIVKEY_PKCS1, expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, { name: "Public Key from Private Key: RSA PKCS#8", input: RSA_PRIVKEY_PKCS8, expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, // test EC certificate { name: "Public Key from Private Key: EC SEC1", input: EC_P256_PRIVKEY_SEC1, expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, { name: "Public Key from Private Key: EC PKCS#8", input: EC_P256_PRIVKEY_PKCS8, expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, // test DSA { name: "Public Key from Private Key: DSA Traditional", input: DSA_PRIVKEY_TRAD, expectedOutput: (DSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, { name: "Public Key from Private Key: DSA PKCS#8", input: DSA_PRIVKEY_PKCS8, expectedOutput: "DSA Private Key in PKCS#8 is not supported", recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, // test EdDSA { name: "Public Key from Private Key: Ed25519", input: ED25519_PRIVKEY, expectedOutput: "Unsupported key type: Error: malformed PKCS8 private key(code:004)", recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, { name: "Public Key from Private Key: Ed448", input: ED448_PRIVKEY, expectedOutput: "Unsupported key type: Error: malformed PKCS8 private key(code:004)", recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], }, // test multi-input { name: "Public Key from Private Key: Multiple keys", input: RSA_PRIVKEY_PKCS8 + "\n" + EC_P256_PRIVKEY_PKCS8, expectedOutput: (RSA_PUBKEY + "\n" + EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), recipeConfig: [ { op: "Public Key from Private Key", args: [], } ], } ]); ================================================ FILE: tests/operations/tests/RAKE.mjs ================================================ /** * RAKE, Rapid Automatic Keyword Extraction tests. * * @author sw5678 * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "RAKE: Basic Example", "input": "test1 test2. test2", "expectedOutput": "Scores: , Keywords: \n3.5, test1 test2\n1.5, test2", "recipeConfig": [ { "op": "RAKE", "args": ["\\s", "\\.\\s|\\n", "i,me,my,myself,we,our"] }, ], } ]); ================================================ FILE: tests/operations/tests/RC6.mjs ================================================ /** * RC6 cipher tests. * * Test vectors from the IETF draft: * "Test Vectors for RC6 and RC5" * https://datatracker.ietf.org/doc/html/draft-krovetz-rc6-rc5-vectors-00 * * @author Medjedtxm * @copyright Crown Copyright 2026 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ // ============================================================ // IETF TEST VECTORS - RC6-8/12/4 // ============================================================ { name: "RC6-8/12/4: IETF vector encrypt", input: "00010203", expectedOutput: "aefc4612", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "00010203", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 8, 12 ] } ] }, { name: "RC6-8/12/4: IETF vector decrypt", input: "aefc4612", expectedOutput: "00010203", recipeConfig: [ { op: "RC6 Decrypt", args: [ { string: "00010203", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 8, 12 ] } ] }, // ============================================================ // IETF TEST VECTORS - RC6-16/16/8 // ============================================================ { name: "RC6-16/16/8: IETF vector encrypt", input: "0001020304050607", expectedOutput: "2ff0b68eaeffad5b", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "0001020304050607", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 16, 16 ] } ] }, { name: "RC6-16/16/8: IETF vector decrypt", input: "2ff0b68eaeffad5b", expectedOutput: "0001020304050607", recipeConfig: [ { op: "RC6 Decrypt", args: [ { string: "0001020304050607", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 16, 16 ] } ] }, // ============================================================ // IETF TEST VECTORS - RC6-32/20/16 (AES standard) // ============================================================ { name: "RC6-32/20/16: IETF vector encrypt (AES standard)", input: "000102030405060708090a0b0c0d0e0f", expectedOutput: "3a96f9c7f6755cfe46f00e3dcd5d2a3c", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 32, 20 ] } ] }, { name: "RC6-32/20/16: IETF vector decrypt (AES standard)", input: "3a96f9c7f6755cfe46f00e3dcd5d2a3c", expectedOutput: "000102030405060708090a0b0c0d0e0f", recipeConfig: [ { op: "RC6 Decrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 32, 20 ] } ] }, // ============================================================ // IETF TEST VECTORS - RC6-64/24/24 // ============================================================ { name: "RC6-64/24/24: IETF vector encrypt", input: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", expectedOutput: "c002de050bd55e5d36864ab9853338e6dc4a1326c6bdaaeb1bc9e4fd67886617", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 64, 24 ] } ] }, { name: "RC6-64/24/24: IETF vector decrypt", input: "c002de050bd55e5d36864ab9853338e6dc4a1326c6bdaaeb1bc9e4fd67886617", expectedOutput: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", recipeConfig: [ { op: "RC6 Decrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 64, 24 ] } ] }, // ============================================================ // IETF TEST VECTORS - RC6-128/28/32 // ============================================================ { name: "RC6-128/28/32: IETF vector encrypt", input: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", expectedOutput: "4ed87c64baffecd4303ee6a79aafaef575b351c024272be70a70b4a392cfc157dba52d529a79e83845bf43d67545383aed3dbf4f0d23640e44cbf6cdaa034dcb", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 128, 28 ] } ] }, { name: "RC6-128/28/32: IETF vector decrypt", input: "4ed87c64baffecd4303ee6a79aafaef575b351c024272be70a70b4a392cfc157dba52d529a79e83845bf43d67545383aed3dbf4f0d23640e44cbf6cdaa034dcb", expectedOutput: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", recipeConfig: [ { op: "RC6 Decrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 128, 28 ] } ] }, // ============================================================ // IETF TEST VECTORS - RC6-24/4/0 (non-power-of-2) // ============================================================ { name: "RC6-24/4/0: IETF non-standard vector encrypt (w=24, empty key)", input: "000102030405060708090a0b", expectedOutput: "0177982579be2ee3303269b9", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 24, 4 ] } ] }, { name: "RC6-24/4/0: IETF non-standard vector decrypt (w=24, empty key)", input: "0177982579be2ee3303269b9", expectedOutput: "000102030405060708090a0b", recipeConfig: [ { op: "RC6 Decrypt", args: [ { string: "", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 24, 4 ] } ] }, // ============================================================ // IETF TEST VECTORS - RC6-80/4/12 (non-power-of-2) // ============================================================ { name: "RC6-80/4/12: IETF non-standard vector encrypt (w=80)", input: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627", expectedOutput: "26d9d6128601d06dec3817d401f1c0ff715473543875da417c2116d1e87c919a49311b00b4e17962", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "000102030405060708090a0b", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 80, 4 ] } ] }, { name: "RC6-80/4/12: IETF non-standard vector decrypt (w=80)", input: "26d9d6128601d06dec3817d401f1c0ff715473543875da417c2116d1e87c919a49311b00b4e17962", expectedOutput: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627", recipeConfig: [ { op: "RC6 Decrypt", args: [ { string: "000102030405060708090a0b", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 80, 4 ] } ] }, // ============================================================ // ADDITIONAL KEY SIZE TESTS - RC6-32 (192-bit and 256-bit keys) // ============================================================ { name: "RC6-32/20/24: 192-bit key encrypt", input: "000102030405060708090a0b0c0d0e0f", expectedOutput: "a68a14ff1342262a2bbd21f7966615eb", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 32, 20 ] } ] }, { name: "RC6-32/20/32: 256-bit key encrypt", input: "000102030405060708090a0b0c0d0e0f", expectedOutput: "921c3ecd43d9426a90089334d67aea2e", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Hex", "NO", 32, 20 ] } ] }, // ============================================================ // ROUND-TRIP TESTS - One per word size to verify encrypt/decrypt // ============================================================ { name: "RC6-8 Round-trip: CBC mode", input: "Hello World!", expectedOutput: "Hello World!", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "mysecret", option: "UTF8" }, { string: "abcd", option: "UTF8" }, "CBC", "Raw", "Hex", "PKCS5", 8, 12 ] }, { op: "RC6 Decrypt", args: [ { string: "mysecret", option: "UTF8" }, { string: "abcd", option: "UTF8" }, "CBC", "Hex", "Raw", "PKCS5", 8, 12 ] } ] }, { name: "RC6-16 Round-trip: CBC mode", input: "The quick brown fox", expectedOutput: "The quick brown fox", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "secretkey1234567", option: "UTF8" }, { string: "initvec!", option: "UTF8" }, "CBC", "Raw", "Hex", "PKCS5", 16, 16 ] }, { op: "RC6 Decrypt", args: [ { string: "secretkey1234567", option: "UTF8" }, { string: "initvec!", option: "UTF8" }, "CBC", "Hex", "Raw", "PKCS5", 16, 16 ] } ] }, { name: "RC6-32 Round-trip: CBC mode", input: "The quick brown fox jumps over the lazy dog", expectedOutput: "The quick brown fox jumps over the lazy dog", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "aabbccddeeff00112233445566778899", option: "Hex" }, { string: "00112233445566778899aabbccddeeff", option: "Hex" }, "CBC", "Raw", "Hex", "PKCS5", 32, 20 ] }, { op: "RC6 Decrypt", args: [ { string: "aabbccddeeff00112233445566778899", option: "Hex" }, { string: "00112233445566778899aabbccddeeff", option: "Hex" }, "CBC", "Hex", "Raw", "PKCS5", 32, 20 ] } ] }, { name: "RC6-64 Round-trip: CBC mode", input: "RC6 with 64-bit words is powerful!", expectedOutput: "RC6 with 64-bit words is powerful!", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, "CBC", "Raw", "Hex", "PKCS5", 64, 24 ] }, { op: "RC6 Decrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, "CBC", "Hex", "Raw", "PKCS5", 64, 24 ] } ] }, { name: "RC6-128 Round-trip: ECB mode", input: "RC6 with 128-bit words provides massive block size for testing purposes!", expectedOutput: "RC6 with 128-bit words provides massive block size for testing purposes!", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Raw", "Hex", "PKCS5", 128, 28 ] }, { op: "RC6 Decrypt", args: [ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Raw", "PKCS5", 128, 28 ] } ] }, // ============================================================ // STREAM MODES TEST - Verify CFB/OFB/CTR work correctly // ============================================================ { name: "RC6-32 Round-trip: CTR mode", input: "CTR mode test message", expectedOutput: "CTR mode test message", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "00112233445566778899aabbccddeeff", option: "Hex" }, { string: "00000000000000000000000000000001", option: "Hex" }, "CTR", "Raw", "Hex", "PKCS5", 32, 20 ] }, { op: "RC6 Decrypt", args: [ { string: "00112233445566778899aabbccddeeff", option: "Hex" }, { string: "00000000000000000000000000000001", option: "Hex" }, "CTR", "Hex", "Raw", "PKCS5", 32, 20 ] } ] }, // ============================================================ // CUSTOM ROUNDS TEST - Verify non-standard round count works // ============================================================ { name: "RC6-32 Round-trip: Custom 8 rounds", input: "Testing custom rounds", expectedOutput: "Testing custom rounds", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "00112233445566778899aabbccddeeff", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Raw", "Hex", "PKCS5", 32, 8 ] }, { op: "RC6 Decrypt", args: [ { string: "00112233445566778899aabbccddeeff", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Raw", "PKCS5", 32, 8 ] } ] }, // ============================================================ // EDGE CASE TEST - Padding boundary // ============================================================ { name: "RC6-32 Round-trip: Exact block size input", input: "1234567890123456", expectedOutput: "1234567890123456", recipeConfig: [ { op: "RC6 Encrypt", args: [ { string: "00112233445566778899aabbccddeeff", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Raw", "Hex", "PKCS5", 32, 20 ] }, { op: "RC6 Decrypt", args: [ { string: "00112233445566778899aabbccddeeff", option: "Hex" }, { string: "", option: "Hex" }, "ECB", "Hex", "Raw", "PKCS5", 32, 20 ] } ] } ]); ================================================ FILE: tests/operations/tests/RSA.mjs ================================================ /** * RSA tests. * * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; import {ASCII_TEXT, UTF8_TEXT, ALL_BYTES} from "../../samples/Ciphers.mjs"; const PEM_PRIV_2048 = `-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAwfaUOpUEutKyU3wkCv6kYunz4MqxzSuTSckRz1IxwZtwIiqq +ejkM6ioXPyGadfFNvG0JVOgr1q4KQglq0vXaYG57HZ8iinXnHgy1vr8i+fWYITB RMrEDySaQh3sxVj8NudPDoTIxZwUcIUu/N53pUmI08ADxXPA+ZymPyZhZyxrj5Jq 2O2QuRu+R7K44NDweP/rETbGo5+QAPydm6UqBzTky/ohv6EGhjyqnaskTWwLWK6P dKva8rEMb8nNJvhoTJDLYUfNjB7DFnWxgWuR/KVkXGAHX99J/wh6QTS+bsyJ2/Mw Df6NWdh3iP7msLNl/GqL+HunhHjrthvvWlODDwIDAQABAoIBAApKwLvJC3q0UmUO qcTxlRxwiJHNf5jA7qxUIH9NP7mju1P8ypy/KFi7Ys+oUKOOIPdU5Pe0E8sqN6pp tcH8oL4G9awf72TPapLxZ9UzdTIhR6VQdgbl8XhSO2M1vkoMejmZlX7SOesOaKE9 1+vwDA43tCx0PF7+UOeN0d549WMphvw3VkSInO/MYpobCGra4YdrhYOhFMyLEGgA zCyVUOxi538tyyFtK2EEQdcMtvVA6SECjF4xD/qrme0LelIj/L1Uhiu+SOzYt4y+ QLHL6zhJVfOejWxjeI7BhodkTV2D53n4svfizRgyYEb6iLPW3nlMYIlAksYaxxB9 nR3sMHECgYEA9RU+8J5A8RnBcwnlc2X1xEW2PN7+A1MeWPQwFqRwIokgvGbCtwjG PwwNUYJCTBhfGhsISeCBOSYrDGTHsNH+tqFW2zlq61BolYl56jb1KgWzMOX8dak4 sgXIuBbvyuFNk08VMIzwcA76ka/Iuu/nN9ZOM2UYpdpGG+CTOoIFULECgYEAyppm I+yAtrUn/BFmwmC8va4vqXlBFjvdkfX/71ywCpHIouLucMV7bILJu0nSCpmL1A7R DT6qo0p5g+Dxl/+O2VyC5D89PBvcuT1+HtEZGLOoKZnojbSrwDApGbzQi57GoQR6 /SRjsdAmoelY8PFz2s2ZLJ4NkrZXYvkT1Tu8/78CgYEA4MAvC/HUlEWORbTZmk3y Z5+WU5QbVWkv91tXjiwWOVWPk7aY8ck2JDMlM45ExgvDiuknXLhpSMNbzu3MwraQ 42JpiHjLOChxAFEmYEct5O99OGZwcmZQ+9CaFVfTZzXeMizfvbpB9EGIP3n4lpXS cD4zUKZxSAc3K/FyksERpsECgYEAhQPXeVBltQ68oKaAE6/VWqcIjbiY/dLyBkk+ 7dSpk1bhJefdadaN0NERRtARgXoLrn7Hy21QNILJwsaldwiGrbgqC1Zlipg0Ur3H ls3rLyeMiTuNzbNHa5dy9H3dYT0t5Tr+0EHa3jvtkTGVfiLX0FhZb0yZVrA2MTmc RsvAqxsCgYAgXy4qytgfzo5/bBt306NbtMEW3dWBWF77HAz4N1LynKZRUrAAK4rz BVmXFUaNQOg0q8WJG+iFF79u2UnL8iZ5GoPMcpvifsZgef1OHnQnFrfyXSr0fXIm xq8eZS0DpLvKGffCW03B9VDRHanE37Tng8lbgOtaufuVzFa1bCuLUA== -----END RSA PRIVATE KEY-----`; const PEM_PUB_2048 = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwfaUOpUEutKyU3wkCv6k Yunz4MqxzSuTSckRz1IxwZtwIiqq+ejkM6ioXPyGadfFNvG0JVOgr1q4KQglq0vX aYG57HZ8iinXnHgy1vr8i+fWYITBRMrEDySaQh3sxVj8NudPDoTIxZwUcIUu/N53 pUmI08ADxXPA+ZymPyZhZyxrj5Jq2O2QuRu+R7K44NDweP/rETbGo5+QAPydm6Uq BzTky/ohv6EGhjyqnaskTWwLWK6PdKva8rEMb8nNJvhoTJDLYUfNjB7DFnWxgWuR /KVkXGAHX99J/wh6QTS+bsyJ2/MwDf6NWdh3iP7msLNl/GqL+HunhHjrthvvWlOD DwIDAQAB -----END PUBLIC KEY-----`; TestRegister.addTests([ { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, ASCII", input: ASCII_TEXT, expectedOutput: ASCII_TEXT, recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, UTF-8", input: UTF8_TEXT.substr(0, 100), expectedOutput: UTF8_TEXT.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, All bytes", input: ALL_BYTES.substr(0, 100), expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, ASCII", input: ASCII_TEXT, expectedOutput: ASCII_TEXT, recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, UTF-8", input: UTF8_TEXT.substr(0, 100), expectedOutput: UTF8_TEXT.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, All bytes", input: ALL_BYTES.substr(0, 100), expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, ASCII", input: ASCII_TEXT, expectedOutput: ASCII_TEXT, recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, UTF-8", input: UTF8_TEXT.substr(0, 100), expectedOutput: UTF8_TEXT.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, All bytes", input: ALL_BYTES.substr(0, 100), expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, ASCII", input: ASCII_TEXT, expectedOutput: ASCII_TEXT, recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, UTF-8", input: UTF8_TEXT.substr(0, 80), expectedOutput: UTF8_TEXT.substr(0, 80), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, All bytes", input: ALL_BYTES.substr(0, 100), expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, ASCII", input: ASCII_TEXT.substr(0, 100), expectedOutput: ASCII_TEXT.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, UTF-8", input: UTF8_TEXT.substr(0, 60), expectedOutput: UTF8_TEXT.substr(0, 60), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] } ] }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, All bytes", input: ALL_BYTES.substr(0, 100), expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] }, { "op": "RSA Decrypt", "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] } ] }, ]); ================================================ FILE: tests/operations/tests/Rabbit.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Rabbit: RFC Test vector, without IV 1", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "b15754f036a5d6ecf56b45261c4af70288e8d815c59c0c397b696c4789c68aa7f416a1c3700cd451da68d1881673d696", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": ""}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: RFC Test vector, without IV 2", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "3d2df3c83ef627a1e97fc38487e2519cf576cd61f4405b8896bf53aa8554fc19e5547473fbdb43508ae53b20204d4c5e", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "912813292e3d36fe3bfc62f1dc51c3ac"}, {"option": "Hex", "string": ""}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: RFC Test vector, without IV 3", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "0cb10dcda041cdac32eb5cfd02d0609b95fc9fca0f17015a7b7092114cff3ead9649e5de8bfc7f3f924147ad3a947428", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "8395741587e0c733e9e9ab01c09b0043"}, {"option": "Hex", "string": ""}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: RFC Test vector, with IV 1", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "c6a7275ef85495d87ccd5d376705b7ed5f29a6ac04f5efd47b8f293270dc4a8d2ade822b29de6c1ee52bdb8a47bf8f66", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": "0000000000000000"}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: RFC Test vector, with IV 2", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "1fcd4eb9580012e2e0dccc9222017d6da75f4e10d12125017b2499ffed936f2eebc112c393e738392356bdd012029ba7", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": "c373f575c1267e59"}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: RFC Test vector, with IV 3", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "445ad8c805858dbf70b6af23a151104d96c8f27947f42c5baeae67c6acc35b039fcbfc895fa71c17313df034f01551cb", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": "a6eb561ad2f41727"}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: generated stream should be XORed with the input", input: "cedda96c054e3ddd93da7ed05e2a4b7bdb0c00fe214f03502e2708b2c2bfc77aa2311b0b9af8aa78d119f92b26db0a6b", expectedOutput: "7f8afd9c33ebeb3166b13bf64260bc7953e4d8ebe4d30f69554e64f54b794ddd5627bac8eaf47e290b7128a330a8dcfd", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": ""}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: least significant bits should be used for the last block", input: "0000000000000000", expectedOutput: "f56b45261c4af702", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": ""}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: invalid key length", input: "", expectedOutput: "Invalid key length: 8 bytes (expected: 16)", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "0000000000000000"}, {"option": "Hex", "string": ""}, "Big", "Hex", "Hex" ] } ] }, { name: "Rabbit: invalid IV length", input: "", expectedOutput: "Invalid IV length: 4 bytes (expected: 0 or 8)", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": "00000000"}, "Big", "Hex", "Hex" ] } ] }, { // this testcase is taken from the first example on Crypto++ Wiki // https://www.cryptopp.com/wiki/Rabbit name: "Rabbit: little-endian mode (Crypto++ compatible)", input: "Rabbit stream cipher test", expectedOutput: "1ae2d4edcf9b6063b00fd6fda0b223aded157e77031cf0440b", recipeConfig: [ { "op": "Rabbit", "args": [ {"option": "Hex", "string": "23c2731e8b5469fd8dabb5bc592a0f3a"}, {"option": "Hex", "string": "712906405ef03201"}, "Little", "Raw", "Hex" ] } ] }, ]); ================================================ FILE: tests/operations/tests/Regex.mjs ================================================ /** * StrUtils tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Regex: non-HTML op", input: "/<>", expectedOutput: "/<>", recipeConfig: [ { "op": "Regular expression", "args": ["User defined", "", true, true, false, false, false, false, "Highlight matches"] }, { "op": "Remove whitespace", "args": [true, true, true, true, true, false] } ], }, { name: "Regex: Dot matches all", input: "Hello\nWorld", expectedOutput: "Hello\nWorld", recipeConfig: [ { "op": "Regular expression", "args": ["User defined", ".+", true, true, true, false, false, false, "List matches"] } ], }, { name: "Regex: Astral off", input: "𝌆😆", expectedOutput: "", recipeConfig: [ { "op": "Regular expression", "args": ["User defined", "\\pS", true, true, false, false, false, false, "List matches"] } ], }, { name: "Regex: Astral on", input: "𝌆😆", expectedOutput: "𝌆\n😆", recipeConfig: [ { "op": "Regular expression", "args": ["User defined", "\\pS", true, true, false, false, true, false, "List matches"] } ], } ]); ================================================ FILE: tests/operations/tests/Register.mjs ================================================ /** * Register tests * * @author tlwr [toby@toby.codes] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Register: RC4 key", input: "http://malwarez.biz/beacon.php?key=0e932a5c&data=8db7d5ebe38663a54ecbb334e3db11", expectedOutput: "zNu5y53uBoU2rm7qhq9ijjnVHSlJ9PJ/zpp+xL/to8qIBzkDwKzUNQ==", recipeConfig: [ { op: "Register", args: ["key=([\\da-f]*)", true, false] }, { op: "RC4", args: [ { "option": "Hex", "string": "$R0" }, "Hex", "Latin1" ] }, { op: "To Base64", args: ["A-Za-z0-9+/="] } ] }, { name: "Register: AES key", input: "51e201d463698ef5f717f71f5b4712af20be674b3bff53d38546396ee61daac4908e319ca3fcf7089bfb6b38ea99e781d26e577ba9dd6f311a39420b8978e93014b042d44726caedf5436eaf652429c0df94b521676c7c2ce812097c277273c7c72cd89aec8d9fb4a27586ccf6aa0aee224c34ba3bfdf7aeb1ddd477622b91e72c9e709ab60f8daf731ec0cc85ce0f746ff1554a5a3ec291ca40f9e629a872592d988fdd834534aba79c1ad1676769a7c010bf04739ecdb65d95302371d629d9e37e7b4a361da468f1ed5358922d2ea752dd11c366f3017b14aa011d2af03c44f95579098a15e3cf9b4486f8ffe9c239f34de7151f6ca6500fe4b850c3f1c02e801caf3a24464614e42801615b8ffaa07ac8251493ffda7de5ddf3368880c2b95b030f41f8f15066add071a66cf60e5f46f3a230d397b652963a21a53f", 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." "Why, what did she tell you?" "I don't know, I didn't listen."`, recipeConfig: [ { op: "Register", args: ["(.{32})", true, false] }, { op: "Drop bytes", args: [0, 32, false] }, { op: "AES Decrypt", args: [ { "option": "Hex", "string": "1748e7179bd56570d51fa4ba287cc3e5" }, { "option": "Hex", "string": "$R0" }, "CTR", "Hex", "Raw", { "option": "Hex", "string": "" }, { "option": "Hex", "string": "" } ] } ] } ]); ================================================ FILE: tests/operations/tests/RisonEncodeDecode.mjs ================================================ /** * @author sg5506844 [sg5506844@gmail.com] * @copyright Crown Copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Rison Encode: Encoding example 1", input: JSON.stringify({ any: "json", yes: true }), expectedOutput: "(any:json,yes:!t)", recipeConfig: [ { op: "Rison Encode", args: ["Encode"] } ] }, { name: "Rison Encode: Encoding example 2", input: JSON.stringify({ supportsObjects: true, ints: 435 }), expectedOutput: "ints:435,supportsObjects:!t", recipeConfig: [ { op: "Rison Encode", args: ["Encode Object"] } ] }, { name: "Rison Encode: Encoding example 3", input: JSON.stringify(["A", "B", { supportsObjects: true }]), expectedOutput: "A,B,(supportsObjects:!t)", recipeConfig: [ { op: "Rison Encode", args: ["Encode Array"] } ] }, { name: "Rison Encode: Object for an array", input: JSON.stringify({ supportsObjects: true, ints: 435 }), expectedOutput: "Rison Encode - rison.encode_array expects an array argument", expectedError: "Rison Encode - rison.encode_array expects an array argument", recipeConfig: [ { op: "Rison Encode", args: ["Encode Array"] } ] }, { name: "Rison Decode: Decoding example 1", input: "(any:json,yes:!t)", expectedOutput: JSON.stringify({ any: "json", yes: true }, null, 4), recipeConfig: [ { op: "Rison Decode", args: ["Decode"] } ] } ]); ================================================ FILE: tests/operations/tests/Rotate.mjs ================================================ /** * Rotate tests. * * @author Matt C [matt@artemisbot.uk] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Rotate left: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["Space"] }, { op: "Rotate left", args: [1, false], }, { op: "To Hex", args: ["Space"] } ], }, { name: "Rotate left: normal", input: "61 62 63 31 32 33", expectedOutput: "c2 c4 c6 62 64 66", recipeConfig: [ { op: "From Hex", args: ["Space"] }, { op: "Rotate left", args: [1, false], }, { op: "To Hex", args: ["Space"] } ], }, { name: "Rotate left: carry", input: "61 62 63 31 32 33", expectedOutput: "85 89 8c c4 c8 cd", recipeConfig: [ { op: "From Hex", args: ["Space"] }, { op: "Rotate left", args: [2, true], }, { op: "To Hex", args: ["Space"] } ], }, { name: "Rotate right: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["Space"] }, { op: "Rotate right", args: [1, false], }, { op: "To Hex", args: ["Space"] } ], }, { name: "Rotate right: normal", input: "61 62 63 31 32 33", expectedOutput: "b0 31 b1 98 19 99", recipeConfig: [ { op: "From Hex", args: ["Space"] }, { op: "Rotate right", args: [1, false], }, { op: "To Hex", args: ["Space"] } ], }, { name: "Rotate right: carry", input: "61 62 63 31 32 33", expectedOutput: "d8 58 98 cc 4c 8c", recipeConfig: [ { op: "From Hex", args: ["Space"] }, { op: "Rotate right", args: [2, true], }, { op: "To Hex", args: ["Space"] } ], }, { name: "ROT13: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "ROT13", args: [true, true, true, 13] }, ], }, { name: "ROT13: no shift amount", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", recipeConfig: [ { op: "ROT13", args: [true, true, true, 0] }, ], }, { name: "ROT13: normal", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "Gur Dhvpx Oebja Sbk Whzcrq Bire Gur Ynml Qbt. 3456789012", recipeConfig: [ { op: "ROT13", args: [true, true, true, 13] }, ], }, { name: "ROT13: negative shift amount", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "Gur Dhvpx Oebja Sbk Whzcrq Bire Gur Ynml Qbt. 7890123456", recipeConfig: [ { op: "ROT13", args: [true, true, true, -13] }, ], }, { name: "ROT13: full loop", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 6789012345", recipeConfig: [ { op: "ROT13", args: [true, true, true, 26] }, ], }, { name: "ROT13: full loop (negative shift amount)", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 4567890123", recipeConfig: [ { op: "ROT13", args: [true, true, true, -26] }, ], }, { name: "ROT13: lowercase only", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "Tur Qhvpx Bebja Fbk Jhzcrq Oire Tur Lnml Dbt. 0123456789", recipeConfig: [ { op: "ROT13", args: [true, false, false, 13] }, ], }, { name: "ROT13: uppercase only", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "Ghe Duick Orown Sox Wumped Bver Ghe Yazy Qog. 0123456789", recipeConfig: [ { op: "ROT13", args: [false, true, false, 13] }, ], }, { name: "ROT13: numbers only", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 5678901234", recipeConfig: [ { op: "ROT13", args: [false, false, true, 5] }, ], }, { name: "ROT13: numbers only (negative shift amount)", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 5678901234", recipeConfig: [ { op: "ROT13", args: [false, false, true, 5] }, ], }, { name: "ROT13: numbers only loop", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", recipeConfig: [ { op: "ROT13", args: [false, false, true, 10] }, ], }, { name: "ROT13: numbers only loop (negative shift amount)", input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", recipeConfig: [ { op: "ROT13", args: [false, false, true, -10] }, ], }, { name: "ROT47: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "ROT47", args: [47] }, ], }, { name: "ROT47: normal", input: "The Quick Brown Fox Jumped Over The Lazy Dog.", expectedOutput: "%96 \"F:4< qC@H? u@I yF>A65 ~G6C %96 {2KJ s@8]", recipeConfig: [ { op: "ROT47", args: [47] }, ], }, { name: "ROT47: full loop", input: "The Quick Brown Fox Jumped Over The Lazy Dog.", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog.", recipeConfig: [ { op: "ROT47", args: [94] }, ], }, { name: "ROT8000: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "ROT8000", args: [] }, ], }, { name: "ROT8000: normal", input: "The Quick Brown Fox Jumped Over The Lazy Dog.", expectedOutput: "籝籱籮 籚籾籲籬籴 籋类籸粀籷 籏籸粁 籓籾籶籹籮籭 籘籿籮类 籝籱籮 籕籪粃粂 籍籸籰簷", recipeConfig: [ { op: "ROT8000", args: [] }, ], }, { name: "ROT8000: backward", input: "籝籱籮 籚籾籲籬籴 籋类籸粀籷 籏籸粁 籓籾籶籹籮籭 籘籿籮类 籝籱籮 籕籪粃粂 籍籸籰簷", expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog.", recipeConfig: [ { op: "ROT8000", args: [] }, ], }, ]); ================================================ FILE: tests/operations/tests/SIGABA.mjs ================================================ /** * SIGABA machine tests * * @author hettysymes * @copyright hettysymes 2020 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "SIGABA: encrypt test 1", input: "HELLO WORLD TESTING THE SIGABA MACHINE", expectedOutput: "ULBECJCZJBJFVUDWAVRGRBMPSQHOTTNVQEESKN", recipeConfig: [ { "op": "SIGABA", "args": [ "BHKWECJDOVAYLFMITUGXRNSPZQ", true, "G", "CDTAKGQOZXLVJYHSWMIBPRUNEF", false, "L", "WAXHJZMBVDPOLTUYRCQFNSGKEI", false, "I", "HUSCWIMJQXDALVGBFTOYZKRPNE", false, "T", "RTLSMNKXFVWQUZGCHEJBYDAIPO", false, "B", "GHAQBRJWDMNZTSKLOUXYPFIECV", false, "N", "VFLGEMTCXZIQDYAKRPBONHWSUJ", true, "Q", "ZQCAYHRJNXPFLKIOTBUSVWMGDE", false, "B", "EZVSWPCTULGAOFDJNBIYMXKQHR", false, "J", "ELKSGDXMVYJUZNCAROQBPWHITF", false, "R", "3891625740", "3", "6297135408", "1", "2389715064", "8", "9264351708", "6", "9573086142", "6", "Encrypt" ] } ] }, { name: "SIGABA: encrypt test 2", input: "PCRPJZWSPNOHMWANBFBEIVZOXDQESPYDEFBNTHXLSICIRPKUATJVDUQFLZOKGHHHDUDIBRKUHVCGAGLBWVGFFXNDHKPFSPSCIIPCXUFRRHNYWIJFEJWQSGMSNJHWSLPKVXHUQUWIURHDIHIUTWGQFIYLTKEZAUESWYEKIWXUSSXWXBEHCXCUDQWKCISVPKXJVPOIJZWTUGKAORBMKBAQUZOPTSUSYZRROWQUYKNCLHVIHEGWCCONGVHEKCEXVYIPNILIXTXDELNGLJGMEQKKQJWZLPNXPOGIOSVAEAJYKWYJXXGKKPLVYAZGDCMNHMPLCYWDQSRBEMVVVZVFYJMRYGHJOTDOEQVRQOVXOGOVYGTXETFHAYELRYVDGWOFVGAOWPMHQYRZMNXVTAHWSKZLJDFVQPZGMHZWFNOBHSZHEDAEXIFCEEJYZDOEFOQWCXTKPJRUEITKHVCITCLKBUFNAFBYXELAYPBRGGGOCCAGLXXJXTSWCJHMHQPVUIBAGBDKAGEEEPKRGGICJQXSYHBNNAKGYODRAUWAEYHWCKHEQIBAONWQJYQCIFKDTOCTJMBJULWKMSNNMPXINHZQWUMJQLQKIPVZVRGYPCJJZMENWTFTUSPCSPRXHMZPCHCNQTUDCOUJHRKYQIUWWVEVVRYFDIYRQISNGPMQLNMCNMVBEWHNCUODHAGEVEUMKVZLEIKYAMPGVVSBYNRJMFCATDXTQCYXIBCXXKYEYHHYERQGQWZTWCEJBFQLRFFCIVVSZUKGLOTLNGLQNTIKTBBWVFMONUFKRLCJASEKUEEDDQDIVQMFRSJRNHYZJODFHSCJSDAIRUXOSDNFUFUFMNZYQIEGRUXKUPCHENUEZHRKYHDRJYSHLZNYRBWVXORMJMJRIRNSAJQRUMPCXUDFYRGKEAXQXJHPEWNIYIDURDGWIFEMSOFYYCFRZGMZXJNTLTJBBSZIULQSOMEVGCTCVXUHTIEHSPOPQYCJLPAJAPQPAQXE", expectedOutput: "GMEXPPCMFGKUVGXZHVTCKXRSTJUYWNOKFVELWAHHSJBXGOEXCMLOVSIMCDMGEYMWWTFDUMCDUJEZITNPVVBGQDJEVHJXSKJAAUZWBELMSPUTXCUYPDTJCQXEBGWPWRSQLSNFMASCTJZDSFNKDDTAXLRGUPKCBNXMZPADJSFGGNYKRPYBNTYPTGVPACBEINILNACWFVKMJPGCEZFROEYYKTGYSQYMFSGVDOJJONNYEYSCCIXWLKUSJZDRVAQSNUWHMDJVDNNMPGOYRGQRSBGSPQKGCTFZQWSOXBWSQZDCRQJQAWZDPQEILGMMABIMCDPNSKAFCLPQGIRJCMGQREBEUHBYREXFABFMVZTZBDUMASVNUMHIYRSZLGNZFMVAIABLCUZLJLKKZPWEXDHYZFVSNRLCLNDRKLKSWRHQVQJRTHCNFZXDEXSLAXXOGMFVSGCJGAWOLGDMTLWSFNTCUVCCEACINRZAZZOGLEHHXLPHVKILBBJDPOOCILQKKGODSXOBDPZZDXHJLLBOBVFCHJVMUBUZZIKGCWGCYGXVEHHIJGPEQERWEZLILQNHPHALFKFMGADNELGBKILKIUETGDCBQUEOECWVFNOXTJKUYPWBNEKYSIKMVSAMBZGLIKDAOELRSTKFASEKABTUCPSFEGXXQGDFPSPVOLBHGLZSLLWCABSRKZDQQRKVCKXDGTIHPDNMPDZEXYFYKXZTPJPLYOFNLWAGKJEOHOYLMZELXIDWWNXPKEPUCKNNNHJLFYHPQNHMMCGMUPHSUSYYIVWTIMFKKKTFPGFTLTWWSQBRBMGBTZXPVULKNZIIKVTYLJFISGPTLZFTCLGNZOMVKZOIMUDGXRDDSVFRHRYWBEWHYLCUISYMRWAZZAQPJYXZQQKZLILOSHXUTQJFPTXQSREKSUDZTLGUDLUGOJMQHJRJHXCHQTKJULTWWQOXIRFRQEYBPJPEKXFIRMNATWNFBADOSIJVZYRYDBHDAEDJUVDHLDAU", recipeConfig: [ { "op": "SIGABA", "args": [ "YCHLQSUGBDIXNZKERPVJTAWFOM", true, "A", "INPXBWETGUYSAOCHVLDMQKZJFR", false, "B", "WNDRIOZPTAXHFJYQBMSVEKUCGL", false, "C", "TZGHOBKRVUXLQDMPNFWCJYEIAS", false, "D", "YWTAHRQJVLCEXUNGBIPZMSDFOK", true, "E", "QSLRBTEKOGAICFWYVMHJNXZUDP", false, "F", "CHJDQIGNBSAKVTUOXFWLEPRMZY", false, "G", "CDFAJXTIMNBEQHSUGRYLWZKVPO", true, "H", "XHFESZDNRBCGKQIJLTVMUOYAPW", false, "I", "EZJQXMOGYTCSFRIUPVNADLHWBK", false, "J", "7591482630", "0", "3810592764", "1", "4086153297", "2", "3980526174", "3", "6497135280", "4", "Encrypt"] } ] }, { name: "SIGABA: decrypt test", input: "AKDHFWAYSLHJDKXEVMJJHGKFTQBZPJPJILOVHMBYOAGBZVLLTQUOIKXFPUFNILBDPCAELMAPSXTLMUEGSDTNUDWGZDADBFELWWHKVPRZNDATDPYEHIDMTGAGPDEZYXFSASVKSBMXVOJQXRMHDBWUNZDTIIIVKHJYPIEUHAJCNBXNLGVFADEWIKXDJZBUTGOQBCQZWYKRVEENWRWWRYDNOAPGMODTPTUJZCLUCRDILJABNTBTWUEIJSJRQBUVCOUJJDWFMNNUHXBDFYXLGUMXQEAWSVHBXQGEOOGPYRVOAJLAIYIOHHEXACDTAWWCBGQRNPERSIKHTXPXKBUNACZLFZTRBMBBDDGKNBIQMFHZROCZZBGNZSJKDRRWPEQHLCFADNPWPWSLPIFNKBWQPMARUERGWUUODXSCOJQECGHIZRFRNRSXWSFWKISHHTUFRVXLHCQWGBMRDHCYDSVNIDDRSTODCGJSSBLUYOBGEWFOVKOZBJTYCAKMZECUGLJGTSZJNBOLTMUZRRSIGGRQHLRPMGLINASSMZOBNACKUMSFNIZAUFCPFXXOOTJQWWLZOFLGZLHJCWZJCRJKVOUDLNMKQATGVTOFHACAEKFLRWRTTMVRXHYGOTYPNBMUSKDAKXFCICUOVSWXGPQOYUUWTWRPQMEQCSDJMMJKELIHGEDYKWOVHVPUAIBFGAODXODXVFIIZIGWRZSBTIGXVHFABMMOPGVMLGHQQXNOEJRDLOBGUOWSELBHERZFSBLUODMOGIBNVGVGQYDBTKLOPNKZZNGLTTGZYYXIBAHZJDCILZXKNSJDHXWTYQLFHTUINTYSBPIXOPLOQHSAHGQPYUWYNPKMRBBBYIICCBBJRKWVLBIDBBEKJCXHLPUBMIGBUFYDPOCSRUNZOKMKJHMYFJZWFNHQZOGGRTNNUVLMRLDSAJIECTYCJKBYVNAXGCMGNVFJEDSATZQDQTYRBPLZKHAXMOVJZEDKINXKBUVWXXHTYUFO", expectedOutput: "KTSOYDGMLPMVXEAJIATXCNQFXHBNCBXIJOCQGCQBRQSBYFOOEVPVXACBMIUIRNVMJHREKRHBSXJFSMWCKTTCYXJOFSJCQECXXCHTEGPEYSMYDHCSMODUAVBNLILYUIBBIXJCXXNQPCERRSMJTPQLMOXSKTRPWOFUSWXOYRJLBIJGIOYTEAEJEGGYAGSXNHNQTETANPWEGATHSBFLHCVHVIJUAKDVGQCWUSIFFFVAJYPJAFUYDXSLGPGESOUAYXBQIIOXWTXNOXLNCGWSUKVIBMOUGNHORYLSNVNNJLKKFDUAEISOLBLCXYHMDGVBVVVIKDLTMTDVWWJBXWXROVTJBXXKXLEWTTISKIUMYSACVUGGNANMCGUMFNQUXDLTHJNYTFIQEPKQQQSSROYJOILJYQXICXACWGOHCSHENXJILOMIIFCIOUDXDCINIVKIRJCVHWXSFQXMNRBJJWTPXNJADEOPEJBLKHKXNTORIRVRLXUXXAMKMODBXNLQCVJXVOTBRHXBBVJHPFEQFCRXYRRXHXPTXXSUESUTHUGOWQYQPQFPXQPVGEIRPQNKXXMBHIPECRUWFEWJUTYIKSMJSRQIQAIAMXTGDXSJIABHIGKUPJBCHWMVYTMQNQYGDHCNMBSVTPXNFRELFXXQYIOLCDEXDXDVSINICOXRMNSPICPQMOBIDJCNBJKXFAVMUXOXHERJIBIXLMXXULDXKXXHAQDXEXIWXOEEUGKSUGCMRWJDPYCYKXTPCOXMURAJCPRXKFJAJALERWRHVMFHOGMFHXGSXQDPJCJNXRQFGHKRCYTEBJDHPCMYFEAPWSVVMMBVUJJMCAAYURHUPVQVJYDCSNMQEMNIFEXYXIIXBVRVILXAUCBDXRJHGPKPYXHPPPNVSBBCDRLVVIYPKAKYIXTJVYDGVPHXULWMADBEICNIFKWUOOHEFNANDKOXMCVBVORLQYNXLULOEGVGWNKNMOHYVRSYSOVYGAKCGAWKGAIAQNQR", recipeConfig: [ { "op": "SIGABA", "args": [ "YCHLQSUGBDIXNZKERPVJTAWFOM", true, "A", "INPXBWETGUYSAOCHVLDMQKZJFR", false, "B", "WNDRIOZPTAXHFJYQBMSVEKUCGL", false, "C", "TZGHOBKRVUXLQDMPNFWCJYEIAS", false, "D", "YWTAHRQJVLCEXUNGBIPZMSDFOK", true, "E", "QSLRBTEKOGAICFWYVMHJNXZUDP", false, "F", "CHJDQIGNBSAKVTUOXFWLEPRMZY", false, "G", "CDFAJXTIMNBEQHSUGRYLWZKVPO", true, "H", "XHFESZDNRBCGKQIJLTVMUOYAPW", false, "I", "EZJQXMOGYTCSFRIUPVNADLHWBK", false, "J", "7591482630", "0", "3810592764", "1", "4086153297", "2", "3980526174", "3", "6497135280", "4", "Decrypt"] } ] } ]); ================================================ FILE: tests/operations/tests/SM2.mjs ================================================ /** * SM2 Tests * * @author flakjacket95 [dflack95@gmail.com] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; /* Plaintexts */ const SMALL_PLAIN = "I am a small plaintext"; const LARGE_PLAIN = "I am a larger plaintext, that will require the encryption KDF to generate a much larger key to properly encrypt me"; /* Test Key Parameters */ const PUBLIC_X = "f7d903cab7925066c31150a92b31e548e63f954f92d01eaa0271fb2a336baef8"; const PUBLIC_Y = "fb0c45e410ef7a6cdae724e6a78dbff52562e97ede009e762b667d9b14adea6c"; const PRIVATE_K = "e74a72505084c3269aa9b696d603e3e08c74c6740212c11a31e26cdfe08bdf6a"; const CURVE = "sm2p256v1"; /* Decryption Test Ciphertext*/ const CIPHERTEXT_1 = "9a31bc0adb4677cdc4141479e3949572a55c3e6fb52094721f741c2bd2e179aaa87be6263bc1be602e473be3d5de5dce97f8248948b3a7e15f9f67f64aef21575e0c05e6171870a10ff9ab778dbef24267ad90e1a9d47d68f757d57c4816612e9829f804025dea05a511cda39371c22a2828f976f72e"; const CIPHERTEXT_2 = "d3647d68568a2e7a4f8e843286be7bf2b4d80256697d19a73df306ae1a7e6d0364d942e23d2340606e7a2502a838b132f9242587b2ea7e4c207e87242eea8cae68f5ff4da2a95a7f6d350608ae5b6777e1d925bf9c560087af84aba7befba713130106ddb4082d803811bca3864594722f3198d58257fe4ba37f4aa540adf4cb0568bddd2d8140ad3030deea0a87e3198655cc4d22bfc3d73b1c4afec2ff15d68c8d1298d97132cace922ee8a4e41ca288a7e748b77ca94aa81dc283439923ae7939e00898e16fe5111fbe1d928d152b216a"; const CIPHERTEXT_3 = "5f340eeb4398fa8950ee3408d0e3fe34bf7728c9fdb060c94b916891b5c693610274160b52a7132a2bf16ad5cdb57d1e00da2f3ddbd55350729aa9c268b53e40c05ccce9912daa14406e8c132e389484e69757350be25351755dcc6c25c94b3c1a448b2cf8c2017582125eb6cf782055b199a875e966"; const CIPHERTEXT_4 = "0649bac46c3f9fd7fb3b2be4bff27414d634651efd02ca67d8c802bbc5468e77d035c39b581d6b56227f5d87c0b4efbea5032c0761139295ae194b9f1fce698f2f4b51d89fa5554171a1aad2e61fe9de89831aec472ecc5ab178ebf4d2230c1fb94fca03e536b87b9eba6db71ba9939260a08ffd230ca86cb45cf754854222364231bdb8b873791d63ad57a4b3fa5b6375388dc879373f5f1be9051bc5072a8afbec5b7b034e4907aa5bb4b6b1f50e725d09cb6a02e07ce20263005f6c9157ce05d3ea739d231d4f09396fb72aa680884d78"; TestRegister.addTests([ { name: "SM2 Decrypt: Small Input; Format One", input: CIPHERTEXT_1, expectedOutput: SMALL_PLAIN, recipeConfig: [ { "op": "SM2 Decrypt", "args": [PRIVATE_K, "C1C3C2", CURVE] } ] }, { name: "SM2 Decrypt: Large Input; Format One", input: CIPHERTEXT_2, expectedOutput: LARGE_PLAIN, recipeConfig: [ { "op": "SM2 Decrypt", "args": [PRIVATE_K, "C1C3C2", CURVE] } ] }, { name: "SM2 Decrypt: Small Input; Format Two", input: CIPHERTEXT_3, expectedOutput: SMALL_PLAIN, recipeConfig: [ { "op": "SM2 Decrypt", "args": [PRIVATE_K, "C1C2C3", CURVE] } ] }, { name: "SM2 Decrypt: Large Input; Format Two", input: CIPHERTEXT_4, expectedOutput: LARGE_PLAIN, recipeConfig: [ { "op": "SM2 Decrypt", "args": [PRIVATE_K, "C1C2C3", CURVE] } ] }, { name: "SM2 Encrypt And Decrypt: Small Input; Format One", input: SMALL_PLAIN, expectedOutput: SMALL_PLAIN, recipeConfig: [ { "op": "SM2 Encrypt", "args": [PUBLIC_X, PUBLIC_Y, "C1C3C2", CURVE], }, { "op": "SM2 Decrypt", "args": [PRIVATE_K, "C1C3C2", CURVE] } ] }, { name: "SM2 Encrypt And Decrypt: Large Input; Format One", input: LARGE_PLAIN, expectedOutput: LARGE_PLAIN, recipeConfig: [ { "op": "SM2 Encrypt", "args": [PUBLIC_X, PUBLIC_Y, "C1C3C2", CURVE], }, { "op": "SM2 Decrypt", "args": [PRIVATE_K, "C1C3C2", CURVE] } ] }, { name: "SM2 Encrypt And Decrypt: Small Input; Format Two", input: SMALL_PLAIN, expectedOutput: SMALL_PLAIN, recipeConfig: [ { "op": "SM2 Encrypt", "args": [PUBLIC_X, PUBLIC_Y, "C1C2C3", CURVE], }, { "op": "SM2 Decrypt", "args": [PRIVATE_K, "C1C2C2", CURVE] } ] }, { name: "SM2 Encrypt And Decrypt: Large Input; Format Two", input: LARGE_PLAIN, expectedOutput: LARGE_PLAIN, recipeConfig: [ { "op": "SM2 Encrypt", "args": [PUBLIC_X, PUBLIC_Y, "C1C2C3", CURVE], }, { "op": "SM2 Decrypt", "args": [PRIVATE_K, "C1C2C3", CURVE] } ] }, ]); ================================================ FILE: tests/operations/tests/SM4.mjs ================================================ /** * SM4 crypto tests. * * Test data used from IETF draft-ribose-cfrg-sm4-09, see: * https://tools.ietf.org/id/draft-ribose-cfrg-sm4-09.html * * @author swesven * @copyright 2021 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; /* Cleartexts */ const 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"; const 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"; /* Keys */ const KEY_1 = "01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10"; const KEY_2 = "fe dc ba 98 76 54 32 10 01 23 45 67 89 ab cd ef"; /* IV */ const IV = "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"; /* Ciphertexts */ const 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"; const 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"; /* With PKCS#7 padding */ const 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"; const 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"; const 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"; const 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"; const 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"; const 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"; const 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"; const 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"; const 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"; const 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"; TestRegister.addTests([ { name: "SM4 Encrypt: ECB 1, No padding", input: TWO_BLOCK_PLAIN, expectedOutput: ECB_1, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_1, option: "Hex"}, {string: "", option: "Hex"}, "ECB/NoPadding", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: ECB 2, No padding", input: TWO_BLOCK_PLAIN, expectedOutput: ECB_2, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_2, option: "Hex"}, {string: "", option: "Hex"}, "ECB/NoPadding", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: ECB 1, With padding", input: TWO_BLOCK_PLAIN, expectedOutput: ECB_1P, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_1, option: "Hex"}, {string: "", option: "Hex"}, "ECB", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: ECB 2, With padding", input: TWO_BLOCK_PLAIN, expectedOutput: ECB_2P, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_2, option: "Hex"}, {string: "", option: "Hex"}, "ECB", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: CBC 1", input: TWO_BLOCK_PLAIN, expectedOutput: CBC_1, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_1, option: "Hex"}, {string: IV, option: "Hex"}, "CBC/NoPadding", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: CBC 2", input: TWO_BLOCK_PLAIN, expectedOutput: CBC_2, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_2, option: "Hex"}, {string: IV, option: "Hex"}, "CBC/NoPadding", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: OFB1", input: TWO_BLOCK_PLAIN, expectedOutput: OFB_1, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_1, option: "Hex"}, {string: IV, option: "Hex"}, "OFB", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: OFB2", input: TWO_BLOCK_PLAIN, expectedOutput: OFB_2, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_2, option: "Hex"}, {string: IV, option: "Hex"}, "OFB", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: CFB1", input: TWO_BLOCK_PLAIN, expectedOutput: CFB_1, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_1, option: "Hex"}, {string: IV, option: "Hex"}, "CFB", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: CFB2", input: TWO_BLOCK_PLAIN, expectedOutput: CFB_2, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_2, option: "Hex"}, {string: IV, option: "Hex"}, "CFB", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: CTR1", input: FOUR_BLOCK_PLAIN, expectedOutput: CTR_1, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_1, option: "Hex"}, {string: IV, option: "Hex"}, "CTR", "Hex", "Hex"] } ] }, { name: "SM4 Encrypt: CTR1", input: FOUR_BLOCK_PLAIN, expectedOutput: CTR_2, recipeConfig: [ { "op": "SM4 Encrypt", "args": [{string: KEY_2, option: "Hex"}, {string: IV, option: "Hex"}, "CTR", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: ECB 1", input: ECB_1, expectedOutput: TWO_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_1, option: "Hex"}, {string: "", option: "Hex"}, "ECB/NoPadding", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: ECB 2", input: ECB_2, expectedOutput: TWO_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_2, option: "Hex"}, {string: "", option: "Hex"}, "ECB/NoPadding", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: CBC 1", input: CBC_1, expectedOutput: TWO_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_1, option: "Hex"}, {string: IV, option: "Hex"}, "CBC/NoPadding", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: CBC 2", input: CBC_2, expectedOutput: TWO_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_2, option: "Hex"}, {string: IV, option: "Hex"}, "CBC/NoPadding", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: OFB1", input: TWO_BLOCK_PLAIN, expectedOutput: OFB_1, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_1, option: "Hex"}, {string: IV, option: "Hex"}, "OFB", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: OFB2", input: OFB_2, expectedOutput: TWO_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_2, option: "Hex"}, {string: IV, option: "Hex"}, "OFB", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: CFB1", input: CFB_1, expectedOutput: TWO_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_1, option: "Hex"}, {string: IV, option: "Hex"}, "CFB", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: CFB2", input: CFB_2, expectedOutput: TWO_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_2, option: "Hex"}, {string: IV, option: "Hex"}, "CFB", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: CTR1", input: CTR_1, expectedOutput: FOUR_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_1, option: "Hex"}, {string: IV, option: "Hex"}, "CTR", "Hex", "Hex"] } ] }, { name: "SM4 Decrypt: CTR1", input: CTR_2, expectedOutput: FOUR_BLOCK_PLAIN, recipeConfig: [ { "op": "SM4 Decrypt", "args": [{string: KEY_2, option: "Hex"}, {string: IV, option: "Hex"}, "CTR", "Hex", "Hex"] } ] }, ]); ================================================ FILE: tests/operations/tests/SQLBeautify.mjs ================================================ /** * SQLBeautify tests. * * @author GCHQDeveloper581 * @copyright Crown Copyright 2026 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "SQL Beautify - basic", input: "SELECT MONTH, ID, RAIN_I, TEMP_F FROM STATS;", expectedOutput: `SELECT MONTH, ID, RAIN_I, TEMP_F FROM STATS;`, recipeConfig: [ { op: "SQL Beautify", args: [" "], }, ], }, { name: "SQL Beautify - upsert", 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;", expectedOutput: `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;`, recipeConfig: [ { op: "SQL Beautify", args: [" "], }, ], }, ]); ================================================ FILE: tests/operations/tests/Salsa20.mjs ================================================ /** * Salsa20 tests. * * @author joostrijneveld [joost@joostrijneveld.nl] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Salsa20: no key", input: "", expectedOutput: `Invalid key length: 0 bytes. Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`, recipeConfig: [ { "op": "Salsa20", "args": [ {"option": "Hex", "string": ""}, {"option": "Hex", "string": ""}, 0, "20", "Hex", "Hex", ] } ], }, { name: "Salsa20: no nonce", input: "", expectedOutput: `Invalid nonce length: 0 bytes. Salsa20 uses a nonce of 8 bytes (64 bits).`, recipeConfig: [ { "op": "Salsa20", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": ""}, 0, "20", "Hex", "Hex", ] } ], }, { name: "Salsa20: ECRYPT Set 1 vector# 0", 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", 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", recipeConfig: [ { "op": "Salsa20", "args": [ {"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"}, {"option": "Hex", "string": "00:00:00:00:00:00:00:00"}, 0, "20", "Hex", "Hex", ] } ], }, { name: "Salsa20: ECRYPT Set 6 vector# 3", 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", 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", recipeConfig: [ { "op": "Salsa20", "args": [ {"option": "Hex", "string": "0F:62:B5:08:5B:AE:01:54:A7:FA:4D:A0:F3:46:99:EC"}, {"option": "Hex", "string": "28:8F:F6:5D:C4:2B:92:F9"}, 0, "20", "Hex", "Hex", ] } ], }, ]); ================================================ FILE: tests/operations/tests/SeqUtils.mjs ================================================ /** * SeqUtils tests. * * @author Chris van Marle * @copyright Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "SeqUtils - Numeric sort photos", input: "Photo-1.jpg\nPhoto-4.jpg\nPhoto-2.jpg\nPhoto-3.jpg", expectedOutput: "Photo-1.jpg\nPhoto-2.jpg\nPhoto-3.jpg\nPhoto-4.jpg", recipeConfig: [ { "op": "Sort", "args": ["Line feed", false, "Numeric"] } ], }, { name: "SeqUtils - Numeric sort CVE IDs", 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", 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", recipeConfig: [ { "op": "Sort", "args": ["Comma", true, "Numeric"] } ], }, { name: "SeqUtils - Hexadecimal sort", 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", 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", recipeConfig: [ { "op": "Sort", "args": ["Comma", false, "Numeric (hexadecimal)"] } ], }, ]); ================================================ FILE: tests/operations/tests/SetDifference.mjs ================================================ /** * Set Difference tests. * * @author d98762625 * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Set Difference", input: "1 2 3 4 5\n\n3 4 5 6 7", expectedOutput: "1 2", recipeConfig: [ { op: "Set Difference", args: ["\n\n", " "], }, ], }, { name: "Set Difference: wrong sample count", input: "1 2 3 4 5_3_4 5 6 7", expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?", recipeConfig: [ { op: "Set Difference", args: [" ", "_"], }, ], }, { name: "Set Difference: item delimiter", input: "1;2;3;4;5\n\n3;4;5;6;7", expectedOutput: "1;2", recipeConfig: [ { op: "Set Difference", args: ["\n\n", ";"], }, ], }, { name: "Set Difference: sample delimiter", input: "1;2;3;4;5===3;4;5;6;7", expectedOutput: "1;2", recipeConfig: [ { op: "Set Difference", args: ["===", ";"], }, ], }, ]); ================================================ FILE: tests/operations/tests/SetIntersection.mjs ================================================ /** * Set Intersection tests. * * @author d98762625 * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Set Intersection", input: "1 2 3 4 5\n\n3 4 5 6 7", expectedOutput: "3 4 5", recipeConfig: [ { op: "Set Intersection", args: ["\n\n", " "], }, ], }, { name: "Set Intersection: only one set", input: "1 2 3 4 5 6 7 8", expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?", recipeConfig: [ { op: "Set Intersection", args: ["\n\n", " "], }, ], }, { name: "Set Intersection: item delimiter", input: "1-2-3-4-5\n\n3-4-5-6-7", expectedOutput: "3-4-5", recipeConfig: [ { op: "Set Intersection", args: ["\n\n", "-"], }, ], }, { name: "Set Intersection: sample delimiter", input: "1-2-3-4-5z3-4-5-6-7", expectedOutput: "3-4-5", recipeConfig: [ { op: "Set Intersection", args: ["z", "-"], }, ], } ]); ================================================ FILE: tests/operations/tests/SetUnion.mjs ================================================ /** * Set Union tests. * * @author d98762625 * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Set Union: Nothing", input: "\n\n", expectedOutput: "", recipeConfig: [ { op: "Set Union", args: ["\n\n", " "], }, ], }, { name: "Set Union", input: "1 2 3 4 5\n\n3 4 5 6 7", expectedOutput: "1 2 3 4 5 6 7", recipeConfig: [ { op: "Set Union", args: ["\n\n", " "], }, ], }, { name: "Set Union: invalid sample number", input: "1 2 3 4 5\n\n3 4 5 6 7\n\n1", expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?", recipeConfig: [ { op: "Set Union", args: ["\n\n", " "], }, ], }, { name: "Set Union: item delimiter", input: "1,2,3,4,5\n\n3,4,5,6,7", expectedOutput: "1,2,3,4,5,6,7", recipeConfig: [ { op: "Set Union", args: ["\n\n", ","], }, ], }, { name: "Set Union: sample delimiter", input: "1 2 3 4 5whatever3 4 5 6 7", expectedOutput: "1 2 3 4 5 6 7", recipeConfig: [ { op: "Set Union", args: ["whatever", " "], }, ], }, ]); ================================================ FILE: tests/operations/tests/Shuffle.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "Shuffle empty", "input": "", "expectedOutput": "", "recipeConfig": [ { "op": "Shuffle", "args": ["Comma"] } ] }, { "name": "Shuffle bytes", "input": "12345678", "expectedOutput": "31 32 33 34 35 36 37 38", "recipeConfig": [ { "op": "Shuffle", "args": ["Nothing (separate chars)"] }, { "op": "To Hex", "args": ["Space", 0] }, { "op": "Sort", "args": ["Space", false, "Alphabetical (case sensitive)"] } ] }, { "name": "Shuffle lines", "input": "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf\n", "expectedOutput": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf", "recipeConfig": [ { "op": "Shuffle", "args": ["Line feed"] }, { "op": "Sort", "args": ["Line feed", false, "Alphabetical (case sensitive)"] } ] } ]); ================================================ FILE: tests/operations/tests/SplitColourChannels.mjs ================================================ /** * Colour channel split tests. * * @author Matt C matt@artemisbot.uk * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; // Base 85 encoded const 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="; const 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=="; TestRegister.addTests([ { name: "Split Colour Channels: Default (JPEG)", input: testCard, expectedOutput: testCardSplit, recipeConfig: [ { "op": "From Base64", "args": ["A-Za-z0-9+/=", true] }, { "op": "Split Colour Channels", "args": [] }, { "op": "To Base64", "args": ["A-Za-z0-9+/="] } ] } ]); ================================================ FILE: tests/operations/tests/StrUtils.mjs ================================================ /** * StrUtils tests. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Diff, basic usage", input: "testing23\n\ntesting123", expectedOutput: "testing123", recipeConfig: [ { "op": "Diff", "args": ["\\n\\n", "Character", true, true, false, false] } ], }, { name: "Diff added with subtraction, basic usage", input: "testing23\n\ntesting123", expectedOutput: "1", recipeConfig: [ { "op": "Diff", "args": ["\\n\\n", "Character", true, true, true, false] } ], }, { name: "Diff removed with subtraction, basic usage", input: "testing123\n\ntesting3", expectedOutput: "12", recipeConfig: [ { "op": "Diff", "args": ["\\n\\n", "Character", true, true, true, false] } ], }, { name: "Head 0", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", 0] } ], }, { name: "Head 1", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [1].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", 1] } ], }, { name: "Head 2", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [1, 2].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", 2] } ], }, { name: "Head 6", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [1, 2, 3, 4, 5, 6].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", 6] } ], }, { name: "Head big", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [1, 2, 3, 4, 5, 6].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", 100] } ], }, { name: "Head all but 1", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [1, 2, 3, 4, 5].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", -1] } ], }, { name: "Head all but 2", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [1, 2, 3, 4].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", -2] } ], }, { name: "Head all but 6", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", -6] } ], }, { name: "Head all but big", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [].join("\n"), recipeConfig: [ { "op": "Head", "args": ["Line feed", -100] } ], }, { name: "Tail 0", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", 0] } ], }, { name: "Tail 1", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [6].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", 1] } ], }, { name: "Tail 2", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [5, 6].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", 2] } ], }, { name: "Tail 6", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [1, 2, 3, 4, 5, 6].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", 6] } ], }, { name: "Tail big", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [1, 2, 3, 4, 5, 6].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", 100] } ], }, { name: "Tail all but 1", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [2, 3, 4, 5, 6].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", -1] } ], }, { name: "Tail all but 2", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [3, 4, 5, 6].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", -2] } ], }, { name: "Tail all but 6", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", -6] } ], }, { name: "Tail all but big", input: [1, 2, 3, 4, 5, 6].join("\n"), expectedOutput: [].join("\n"), recipeConfig: [ { "op": "Tail", "args": ["Line feed", -100] } ], }, { name: "Escape String: single quotes", input: "Escape 'these' quotes.", expectedOutput: "Escape \\'these\\' quotes.", recipeConfig: [ { "op": "Escape string", "args": ["Special chars", "Single", false, true, false] } ], }, { name: "Escape String: double quotes", input: "Hello \"World\"!", expectedOutput: "Hello \\\"World\\\"!", recipeConfig: [ { "op": "Escape string", "args": ["Special chars", "Double", false, true, false] } ], }, { name: "Escape String: special characters", input: "Fizz & buzz\n\ttabbed newline\rcarriage returned line\nbackspace character: \"\" form feed character: \" \"", expectedOutput: "Fizz & buzz\\n\\ttabbed newline\\rcarriage returned line\\nbackspace character: \\\"\\b\\\" form feed character: \\\"\\f\\\"", recipeConfig: [ { "op": "Escape string", "args": ["Special chars", "Double", false, true, false] } ], }, { name: "Unescape String: quotes", input: "Hello \\\"World\\\"! Escape \\'these\\' quotes.", expectedOutput: "Hello \"World\"! Escape 'these' quotes.", recipeConfig: [ { "op": "Unescape string", "args": [] } ], }, { name: "Unescape String: special characters", input: "Fizz \x26 buzz\\n\\ttabbed newline\\rcarriage returned line\\nbackspace character: \\\"\\b\\\" form feed character: \\\"\\f\\\"", expectedOutput: "Fizz & buzz\n\ttabbed newline\rcarriage returned line\nbackspace character: \"\" form feed character: \" \"", recipeConfig: [ { "op": "Unescape string", "args": [] } ], }, { name: "Escape String: complex", input: "null\0backspace\btab\tnewline\nverticaltab\vformfeed\fcarriagereturn\rdoublequote\"singlequote'hex\xa9unicode\u2665codepoint\u{1D306}", expectedOutput: "null\\0backspace\\btab\\tnewline\\nverticaltab\\x0bformfeed\\fcarriagereturn\\rdoublequote\"singlequote\\'hex\\xa9unicode\\u2665codepoint\\u{1d306}", recipeConfig: [ { "op": "Escape string", "args": ["Special chars", "Single", false, true, false] } ], }, { name: "Unescape String: complex", input: "null\\0backspace\\btab\\tnewline\\nverticaltab\\vformfeed\\fcarriagereturn\\rdoublequote\\\"singlequote\\'hex\\xa9unicode\\u2665codepoint\\u{1D306}", expectedOutput: "null\0backspace\btab\tnewline\nverticaltab\vformfeed\fcarriagereturn\rdoublequote\"singlequote'hex\xa9unicode\u2665codepoint\u{1D306}", recipeConfig: [ { "op": "Unescape string", "args": [] } ], }, ]); ================================================ FILE: tests/operations/tests/StripIPv4Header.mjs ================================================ /** * Strip IPv4 header tests. * * @author c65722 [] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Strip IPv4 header: No options, No payload", input: "450000140005400080060000c0a80001c0a80002", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip IPv4 header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip IPv4 header: No options, Payload", input: "450000140005400080060000c0a80001c0a80002ffffffffffffffff", expectedOutput: "ffffffffffffffff", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip IPv4 header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip IPv4 header: Options, No payload", input: "460000140005400080060000c0a80001c0a8000207000000", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip IPv4 header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip IPv4 header: Options, Payload", input: "460000140005400080060000c0a80001c0a8000207000000ffffffffffffffff", expectedOutput: "ffffffffffffffff", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip IPv4 header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip IPv4 header: Input length lesss than minimum header length", input: "450000140005400080060000c0a80001c0a800", expectedOutput: "Input length is less than minimum IPv4 header length", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip IPv4 header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip IPv4 header: Input length less than IHL", input: "460000140005400080060000c0a80001c0a80000", expectedOutput: "Input length is less than IHL", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip IPv4 header", args: [], }, { op: "To Hex", args: ["None", 0] } ] } ]); ================================================ FILE: tests/operations/tests/StripTCPHeader.mjs ================================================ /** * Strip TCP header tests. * * @author c65722 [] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Strip TCP header: No options, No payload", input: "7f900050000fa4b2000cb2a45010bff100000000", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip TCP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip TCP header: No options, Payload", input: "7f900050000fa4b2000cb2a45010bff100000000ffffffffffffffff", expectedOutput: "ffffffffffffffff", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip TCP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip TCP header: Options, No payload", input: "7f900050000fa4b2000cb2a47010bff100000000020405b404020000", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip TCP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip TCP header: Options, Payload", input: "7f900050000fa4b2000cb2a47010bff100000000020405b404020000ffffffffffffffff", expectedOutput: "ffffffffffffffff", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip TCP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip TCP header: Input length less than minimum header length", input: "7f900050000fa4b2000cb2a45010bff1000000", expectedOutput: "Need at least 20 bytes for a TCP Header", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip TCP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip TCP header: Input length less than data offset", input: "7f900050000fa4b2000cb2a47010bff100000000", expectedOutput: "Input length is less than data offset", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip TCP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] } ]); ================================================ FILE: tests/operations/tests/StripUDPHeader.mjs ================================================ /** * Strip UDP header tests. * * @author c65722 [] * @copyright Crown Copyright 2024 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Strip UDP header: No payload", input: "8111003500000000", expectedOutput: "", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip UDP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip UDP header: Payload", input: "8111003500080000ffffffffffffffff", expectedOutput: "ffffffffffffffff", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip UDP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] }, { name: "Strip UDP header: Input length less than header length", input: "81110035000000", expectedOutput: "Need 8 bytes for a UDP Header", recipeConfig: [ { op: "From Hex", args: ["None"] }, { op: "Strip UDP header", args: [], }, { op: "To Hex", args: ["None", 0] } ] } ]); ================================================ FILE: tests/operations/tests/Subsection.mjs ================================================ /** * Subsection Tests. * * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Subsection: nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "Subsection", "args": ["", true, true, false], }, ], }, { name: "Subsection, Full Merge: nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "Subsection", "args": ["", true, true, false], }, { "op": "Merge", "args": [true], }, ], }, { name: "Subsection, Partial Merge: nothing", input: "", expectedOutput: "", recipeConfig: [ { "op": "Subsection", "args": ["", true, true, false], }, { "op": "Merge", "args": [false], }, ], }, { name: "Subsection, Full Merge: Base64 with Hex", input: "SGVsbG38675629ybGQ=", expectedOutput: "Hello World", recipeConfig: [ { "op": "Subsection", "args": ["386756", true, true, false], }, { "op": "From Hex", "args": ["Auto"], }, { "op": "Merge", "args": [true], }, { "op": "From Base64", "args": ["A-Za-z0-9+/=", true, false], }, ], }, { name: "Subsection, Partial Merge: Base64 with Hex surrounded by binary data.", input: "000000000SGVsbG38675629ybGQ=0000000000", expectedOutput: "000000000Hello World0000000000", recipeConfig: [ { "op": "Subsection", "args": ["SGVsbG38675629ybGQ=", true, true, false], }, { "op": "Subsection", "args": ["386756", true, true, false], }, { "op": "From Hex", "args": ["Auto"], }, { "op": "Merge", "args": [false], }, { "op": "From Base64", "args": ["A-Za-z0-9+/=", true, false], }, ], }, ]); ================================================ FILE: tests/operations/tests/SwapCase.mjs ================================================ /** * @author mikecat * @copyright Crown Copyright 2023 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "Swap Case: basic example", "input": "Hello, World!", "expectedOutput": "hELLO, wORLD!", "recipeConfig": [ { "op": "Swap case", "args": [ ], }, ], }, { "name": "Swap Case: empty input", "input": "", "expectedOutput": "", "recipeConfig": [ { "op": "Swap case", "args": [ ], }, ], }, ]); ================================================ FILE: tests/operations/tests/SymmetricDifference.mjs ================================================ /** * Symmetric difference tests. * * @author d98762625 * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Symmetric Difference", input: "1 2 3 4 5\n\n3 4 5 6 7", expectedOutput: "1 2 6 7", recipeConfig: [ { op: "Symmetric Difference", args: ["\n\n", " "], }, ], }, { name: "Symmetric Difference: wrong sample count", input: "1 2\n\n3 4 5\n\n3 4 5 6 7", expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?", recipeConfig: [ { op: "Symmetric Difference", args: ["\n\n", " "], }, ], }, { name: "Symmetric Difference: item delimiter", input: "a_b_c_d_e\n\nc_d_e_f_g", expectedOutput: "a_b_f_g", recipeConfig: [ { op: "Symmetric Difference", args: ["\n\n", "_"], }, ], }, { name: "Symmetric Difference: sample delimiter", input: "a_b_c_d_eAAAAAc_d_e_f_g", expectedOutput: "a_b_f_g", recipeConfig: [ { op: "Symmetric Difference", args: ["AAAAA", "_"], }, ], }, ]); ================================================ FILE: tests/operations/tests/TakeNthBytes.mjs ================================================ /** * @author Oshawk [oshawk@protonmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; /** * Take nth bytes tests */ TestRegister.addTests([ { name: "Take nth bytes: Nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "Take nth bytes", args: [4, 0, false], }, ], }, { name: "Take nth bytes: Nothing (apply to each line)", input: "", expectedOutput: "", recipeConfig: [ { op: "Take nth bytes", args: [4, 0, true], }, ], }, { name: "Take nth bytes: Basic single line", input: "0123456789", expectedOutput: "048", recipeConfig: [ { op: "Take nth bytes", args: [4, 0, false], }, ], }, { name: "Take nth bytes: Basic single line (apply to each line)", input: "0123456789", expectedOutput: "048", recipeConfig: [ { op: "Take nth bytes", args: [4, 0, true], }, ], }, { name: "Take nth bytes: Complex single line", input: "0123456789", expectedOutput: "59", recipeConfig: [ { op: "Take nth bytes", args: [4, 5, false], }, ], }, { name: "Take nth bytes: Complex single line (apply to each line)", input: "0123456789", expectedOutput: "59", recipeConfig: [ { op: "Take nth bytes", args: [4, 5, true], }, ], }, { name: "Take nth bytes: Basic multi line", input: "01234\n56789", expectedOutput: "047", recipeConfig: [ { op: "Take nth bytes", args: [4, 0, false], }, ], }, { name: "Take nth bytes: Basic multi line (apply to each line)", input: "01234\n56789", expectedOutput: "04\n59", recipeConfig: [ { op: "Take nth bytes", args: [4, 0, true], }, ], }, { name: "Take nth bytes: Complex multi line", input: "01234\n56789", expectedOutput: "\n8", recipeConfig: [ { op: "Take nth bytes", args: [4, 5, false], }, ], }, { name: "Take nth bytes: Complex multi line (apply to each line)", input: "012345\n6789ab", expectedOutput: "5\nb", recipeConfig: [ { op: "Take nth bytes", args: [4, 5, true], }, ], } ]); ================================================ FILE: tests/operations/tests/Template.mjs ================================================ /** * @author kendallgoto [k@kgo.to] * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { "name": "Template: Simple Print", "input": "{}", "expectedOutput": "Hello, world!", "recipeConfig": [ { "op": "Template", "args": ["Hello, world!"] } ] }, { "name": "Template: Print Basic Variables", "input": "{\"one\": 1, \"two\": 2}", "expectedOutput": "1 2", "recipeConfig": [ { "op": "Template", "args": ["{{ one }} {{ two }}"] } ] }, { "name": "Template: Partials", "input": "{\"users\":[{\"name\":\"Someone\",\"age\":25},{\"name\":\"Someone Else\",\"age\":32}]}", "expectedOutput": "Name: Someone\nAge: 25\n\nName: Someone Else\nAge: 32\n\n", "recipeConfig": [ { "op": "Template", "args": ["{{#*inline \"user\"}}\nName: {{ name }}\nAge: {{ age }}\n{{/inline}}\n{{#each users}}\n{{> user}}\n\n{{/each}}"] } ] }, { "name": "Template: Disallow XSS", "input": "{\"test\": \"\"}", "expectedOutput": "<script></script>", "recipeConfig": [ { "op": "Template", "args": ["{{ test }}"] } ] } ]); ================================================ FILE: tests/operations/tests/TextEncodingBruteForce.mjs ================================================ /** * Text Encoding Brute Force tests. * * @author Cynser * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Text Encoding Brute Force - Encode", input: "Булкі праз ляніва сабаку.", expectedMatch: /Windows-1251 Cyrillic \(1251\).{1,10}Булкі праз ляніва сабаку\./, recipeConfig: [ { op: "Text Encoding Brute Force", args: ["Encode"], }, ], }, { name: "Text Encoding Brute Force - Decode", input: "Áóëê³ ïðàç ëÿí³âà ñàáàêó.", expectedMatch: /Windows-1251 Cyrillic \(1251\).{1,10}Булкі праз ляніва сабаку\./, recipeConfig: [ { op: "Text Encoding Brute Force", args: ["Decode"], }, ], } ]); ================================================ FILE: tests/operations/tests/TextIntegerConverter.mjs ================================================ /** * Text-Integer Conversion tests. * * @author p-leriche [philip.leriche@cantab.net] * * @copyright Crown Copyright 2025 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Text-Integer Conversion quoted string to decimal", input: "\"ABC\"", expectedOutput: "4276803", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Decimal"], }, ], }, { name: "Text-Integer Conversion quoted string to hexadecimal", input: "\"ABC\"", expectedOutput: "0x414243", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Hexadecimal"], }, ], }, { name: "Text-Integer Conversion single quoted string to decimal", input: "'Hello'", expectedOutput: "310939249775", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Decimal"], }, ], }, { name: "Text-Integer Conversion decimal to string", input: "4276803", expectedOutput: "ABC", recipeConfig: [ { op: "Text-Integer Conversion", args: ["String"], }, ], }, { name: "Text-Integer Conversion hexadecimal to string", input: "0x48656C6C6F", expectedOutput: "Hello", recipeConfig: [ { op: "Text-Integer Conversion", args: ["String"], }, ], }, { name: "Text-Integer Conversion round-trip string.decimal.string", input: "\"Test\"", expectedOutput: "Test", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Decimal"], }, { op: "Text-Integer Conversion", args: ["String"], }, ], }, { name: "Text-Integer Conversion round-trip string.hex.string", input: "\"CyberChef\"", expectedOutput: "CyberChef", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Hexadecimal"], }, { op: "Text-Integer Conversion", args: ["String"], }, ], }, { name: "Text-Integer Conversion implicit round trip string-string Latin-1", input: "U+00FF", expectedOutput: "U+00FF", // U+00FF (Latin small letter y with diaeresis) recipeConfig: [ { op: "Unescape Unicode Characters", args: ["U+"], }, { op: "Text-Integer Conversion", args: ["String"], }, { op: "Escape Unicode Characters", args: ["U+", false, 4, true], }, ], }, { name: "Text-Integer Conversion unquoted text to decimal", input: "Hi", expectedOutput: "18537", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Decimal"], }, ], }, { name: "Text-Integer Conversion single character", input: "\"A\"", expectedOutput: "65", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Decimal"], }, ], }, { name: "Text-Integer Conversion hex to decimal conversion", input: "0xFF", expectedOutput: "255", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Decimal"], }, ], }, { name: "Text-Integer Conversion decimal to hex conversion", input: "255", expectedOutput: "0xff", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Hexadecimal"], }, ], }, { name: "Text-Integer Conversion large number to string", input: "113091951015816448506195587157728348242683688608116", expectedOutput: "Mary had a little cat", recipeConfig: [ { op: "Text-Integer Conversion", args: ["String"], }, ], }, { name: "Text-Integer Conversion whitespace handling (quoted)", input: "\" test \"", expectedOutput: "2314978187545944096", recipeConfig: [ { op: "Text-Integer Conversion", args: ["Decimal"], }, ], }, { name: "Text-Integer Conversion non-Latin1 character in input", input: "61 ce 93 61", expectedOutput: `Character at position 1 exceeds Latin-1 range (0-255). Only ASCII and Latin-1 characters are supported.`, recipeConfig: [ { "op": "From Hex", "args": ["Auto"] }, { op: "Text-Integer Conversion", args: ["Decimal"], }, ], }, ]); ================================================ FILE: tests/operations/tests/ToFromInsensitiveRegex.mjs ================================================ /** * To/From Case Insensitive Regex tests. * * @author masq [github.cyberchef@masq.cc] * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "To Case Insensitive Regex: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "From Case Insensitive Regex: nothing", input: "", expectedOutput: "", recipeConfig: [ { op: "From Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: simple test", input: "S0meth!ng", expectedOutput: "[sS]0[mM][eE][tT][hH]![nN][gG]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "From Case Insensitive Regex: simple test", input: "[sS]0[mM][eE][tT][hH]![nN][Gg] [wr][On][g]?", expectedOutput: "s0meth!nG [wr][On][g]?", recipeConfig: [ { op: "From Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [A-Z] -> [A-Za-z]", input: "[A-Z]", expectedOutput: "[A-Za-z]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [a-z] -> [A-Za-z]", input: "[a-z]", expectedOutput: "[A-Za-z]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [H-d] -> [A-DH-dh-z]", input: "[H-d]", expectedOutput: "[A-DH-dh-z]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [!-D] -> [!-Da-d]", input: "[!-D]", expectedOutput: "[!-Da-d]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [%-^] -> [%-^a-z]", input: "[%-^]", expectedOutput: "[%-^a-z]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [K-`] -> [K-`k-z]", input: "[K-`]", expectedOutput: "[K-`k-z]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [[-}] -> [[-}A-Z]", input: "[[-}]", expectedOutput: "[[-}A-Z]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [b-}] -> [b-}B-Z]", input: "[b-}]", expectedOutput: "[b-}B-Z]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [<-j] -> [<-z]", input: "[<-j]", expectedOutput: "[<-z]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: [^-j] -> [A-J^-j]", input: "[^-j]", expectedOutput: "[A-J^-j]", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: not simple test", input: "Mozilla[A-Z0-9]+[A-Z]Mozilla[0-9whatA-Z][H-d][!-H][a-~](.)+", 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](.)+", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], }, { name: "To Case Insensitive Regex: erroneous test", input: "Mozilla[A-Z", expectedOutput: "Invalid Regular Expression (Please note this version of node does not support look behinds).", recipeConfig: [ { op: "To Case Insensitive Regex", args: [], }, ], } ]); ================================================ FILE: tests/operations/tests/TranslateDateTimeFormat.mjs ================================================ /** * Translate DateTime Format tests. * * @author Cynser * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { name: "Translate DateTime Format", input: "01/04/1999 22:33:01", expectedOutput: "Thursday 1st April 1999 22:33:01 +00:00 UTC", recipeConfig: [ { op: "Translate DateTime Format", args: ["Standard date and time", "DD/MM/YYYY HH:mm:ss", "UTC", "dddd Do MMMM YYYY HH:mm:ss Z z", "UTC"], }, ], }, { name: "Translate DateTime Format: invalid input", input: "1234567890", expectedMatch: /Invalid format./, recipeConfig: [ { op: "Translate DateTime Format", args: ["Standard date and time", "DD/MM/YYYY HH:mm:ss", "UTC", "dddd Do MMMM YYYY HH:mm:ss Z z", "UTC"], }, ], }, { name: "Translate DateTime Format: timezone conversion", input: "01/04/1999 22:33:01", expectedOutput: "Thursday 1st April 1999 17:33:01 -05:00 EST", recipeConfig: [ { op: "Translate DateTime Format", args: ["Standard date and time", "DD/MM/YYYY HH:mm:ss", "UTC", "dddd Do MMMM YYYY HH:mm:ss Z z", "US/Eastern"], }, ], }, { name: "Translate DateTime Format: automatic input format", input: "1999-04-01 22:33:01", expectedOutput: "Thursday 1st April 1999 22:33:01 +00:00 UTC", recipeConfig: [ { op: "Translate DateTime Format", args: ["Automatic", "", "UTC", "dddd Do MMMM YYYY HH:mm:ss Z z", "UTC"], }, ], } ]); ================================================ FILE: tests/operations/tests/Typex.mjs ================================================ /** * Typex machine tests. * @author s2224834 * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { // Unlike Enigma we're not verifying against a real machine here, so this is just a test // to catch inadvertent breakage. name: "Typex: basic", input: "hello world, this is a test message.", expectedOutput: "VIXQQ VHLPN UCVLA QDZNZ EAYAT HWC", recipeConfig: [ { "op": "Typex", "args": [ "MCYLPQUVRXGSAOWNBJEZDTFKHI