#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
#endif

#define BOOST_TEST_NO_MAIN

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <boost/test/unit_test.hpp>
#include <boost/assign/list_of.hpp>

#include "base32.hh"
#include "base64.hh"
#include "dnssec.hh"
#include "dnssecinfra.hh"
#include "misc.hh"

#include <openssl/opensslv.h> // OPENSSL_VERSION_MAJOR

#include <unordered_map>

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables): Boost stuff.
BOOST_AUTO_TEST_SUITE(test_signers)

struct SignerParams
{
  std::string iscMap;
  std::string dsSHA1;
  std::string dsSHA256;
  std::string dsSHA384;
  std::vector<uint8_t> signature;
  std::string zoneRepresentation;
  std::string name;
  std::string rfcMsgDump;
  std::string rfcB64Signature;
  int bits;
  uint16_t flags;
  uint16_t rfcFlags;
  uint8_t algorithm;
  bool isDeterministic;
  std::string pem;
};

// clang-format off
static const SignerParams rsaSha256SignerParams = SignerParams
{
  .iscMap = "Algorithm: 8\n"
            "Modulus: qtunSiHnYq4XRLBehKAw1Glxb+48oIpAC7w3Jhpj570bb2uHt6orWGqnuyRtK8oqUi2ABoV0PFm8+IPgDMEdCQ==\n"
            "PublicExponent: AQAB\n"
            "PrivateExponent: MiItniUAngXzMeaGdWgDq/AcpvlCtOCcFlVt4TJRKkfp8DNRSxIxG53NNlOFkp1W00iLHqYC2GrH1qkKgT9l+Q==\n"
            "Prime1: 3sZmM+5FKFy5xaRt0n2ZQOZ2C+CoKzVil6/al9LmYVs=\n"
            "Prime2: xFcNWSIW6v8dDL2JQ1kxFDm/8RVeUSs1BNXXnvCjBGs=\n"
            "Exponent1: WuUwhjfN1+4djlrMxHmisixWNfpwI1Eg7Ss/UXsnrMk=\n"
            "Exponent2: vfMqas1cNsXRqP3Fym6D2Pl2BRuTQBv5E1B/ZrmQPTk=\n"
            "Coefficient: Q10z43cA3hkwOkKsj5T0W5jrX97LBwZoY5lIjDCa4+M=\n",

  .dsSHA1 = "1506 8 1 "
            "172a500b374158d1a64ba3073cdbbc319b2fdf2c",

  .dsSHA256 = "1506 8 2 "
              "253b099ff47b02c6ffa52695a30a94c6681c56befe0e71a5077d6f79514972f9",

  .dsSHA384 = "1506 8 4 "
              "22ea940600dc2d9a98b1126c26ac0dc5c91b31eb50fe784b"
              "36ad675e9eecfe6573c1f85c53b6bc94580f3ac443d13c4c",

  /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
  .signature = {
    0x93, 0x93, 0x5f, 0xd8, 0xa1, 0x2b, 0x4c, 0x0b, 0xf3, 0x67, 0x42, 0x13, 0x52,
    0x00, 0x35, 0xdc, 0x09, 0xe0, 0xdf, 0xe0, 0x3e, 0xc2, 0xcf, 0x64, 0xab, 0x9f,
    0x9f, 0x51, 0x5f, 0x5c, 0x27, 0xbe, 0x13, 0xd6, 0x17, 0x07, 0xa6, 0xe4, 0x3b,
    0x63, 0x44, 0x85, 0x06, 0x13, 0xaa, 0x01, 0x3c, 0x58, 0x52, 0xa3, 0x98, 0x20,
    0x65, 0x03, 0xd0, 0x40, 0xc8, 0xa0, 0xe9, 0xd2, 0xc0, 0x03, 0x5a, 0xab
  },

  .zoneRepresentation = "256 3 8 "
                        "AwEAAarbp0oh52KuF0SwXoSgMNRpcW/uPKCKQAu8NyYaY+"
                        "e9G29rh7eqK1hqp7skbSvKKlItgAaFdDxZvPiD4AzBHQk=",

  .name = "rsa.",

  .rfcMsgDump = "",
  .rfcB64Signature = "",

  .bits = 512,
  .flags = 256,
  .rfcFlags = 0,

  .algorithm = DNSSEC::RSASHA256,
  .isDeterministic = true,

#if OPENSSL_VERSION_MAJOR >= 3
  // OpenSSL 3.0.0 uses a generic key interface which stores the key PKCS#8-encoded.
  .pem = "-----BEGIN PRIVATE KEY-----\n"
         "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqtunSiHnYq4XRLBe\n"
         "hKAw1Glxb+48oIpAC7w3Jhpj570bb2uHt6orWGqnuyRtK8oqUi2ABoV0PFm8+IPg\n"
         "DMEdCQIDAQABAkAyIi2eJQCeBfMx5oZ1aAOr8Bym+UK04JwWVW3hMlEqR+nwM1FL\n"
         "EjEbnc02U4WSnVbTSIsepgLYasfWqQqBP2X5AiEA3sZmM+5FKFy5xaRt0n2ZQOZ2\n"
         "C+CoKzVil6/al9LmYVsCIQDEVw1ZIhbq/x0MvYlDWTEUOb/xFV5RKzUE1dee8KME\n"
         "awIgWuUwhjfN1+4djlrMxHmisixWNfpwI1Eg7Ss/UXsnrMkCIQC98ypqzVw2xdGo\n"
         "/cXKboPY+XYFG5NAG/kTUH9muZA9OQIgQ10z43cA3hkwOkKsj5T0W5jrX97LBwZo\n"
         "Y5lIjDCa4+M=\n"
         "-----END PRIVATE KEY-----\n"
#else
  .pem = "-----BEGIN RSA PRIVATE KEY-----\n"
         "MIIBOgIBAAJBAKrbp0oh52KuF0SwXoSgMNRpcW/uPKCKQAu8NyYaY+e9G29rh7eq\n"
         "K1hqp7skbSvKKlItgAaFdDxZvPiD4AzBHQkCAwEAAQJAMiItniUAngXzMeaGdWgD\n"
         "q/AcpvlCtOCcFlVt4TJRKkfp8DNRSxIxG53NNlOFkp1W00iLHqYC2GrH1qkKgT9l\n"
         "+QIhAN7GZjPuRShcucWkbdJ9mUDmdgvgqCs1Ypev2pfS5mFbAiEAxFcNWSIW6v8d\n"
         "DL2JQ1kxFDm/8RVeUSs1BNXXnvCjBGsCIFrlMIY3zdfuHY5azMR5orIsVjX6cCNR\n"
         "IO0rP1F7J6zJAiEAvfMqas1cNsXRqP3Fym6D2Pl2BRuTQBv5E1B/ZrmQPTkCIENd\n"
         "M+N3AN4ZMDpCrI+U9FuY61/eywcGaGOZSIwwmuPj\n"
         "-----END RSA PRIVATE KEY-----\n"
#endif
};
// clang-format on

/* ECDSA-P256-SHA256 from
 * https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h
 */
// clang-format off
static const SignerParams ecdsaSha256 = SignerParams
{
  .iscMap = "Algorithm: 13\n"
            "PrivateKey: iyLIPdk3DOIxVmmSYlmTstbtUPiVlEyDX46psyCwNVQ=\n",

  .dsSHA1 = "5345 13 1 "
            "954103ac7c43810ce9f414e80f30ab1cbe49b236",

  .dsSHA256 = "5345 13 2 "
              "bac2107036e735b50f85006ce409a19a3438cab272e70769ebda032239a3d0ca",

  .dsSHA384 = "5345 13 4 "
              "a0ac6790483872be72a258314200a88ab75cdd70f66a18a0"
              "9f0f414c074df0989fdb1df0e67d82d4312cda67b93a76c1",

  /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
  .signature = {
    0xa2, 0x95, 0x76, 0xb5, 0xf5, 0x7e, 0xbd, 0xdd, 0xf5, 0x62, 0xa2, 0xc3, 0xa4,
    0x8d, 0xd4, 0x53, 0x5c, 0xba, 0x29, 0x71, 0x8c, 0xcc, 0x28, 0x7b, 0x58, 0xf3,
    0x1e, 0x4e, 0x58, 0xe2, 0x36, 0x7e, 0xa0, 0x1a, 0xb6, 0xe6, 0x29, 0x71, 0x1b,
    0xd3, 0x8c, 0x88, 0xc3, 0xee, 0x12, 0x0e, 0x69, 0x70, 0x55, 0x99, 0xec, 0xd5,
    0xf6, 0x4f, 0x4b, 0xe2, 0x41, 0xd9, 0x10, 0x7e, 0x67, 0xe5, 0xad, 0x2f
  },

  .zoneRepresentation = "256 3 13 "
                        "8uD7C4THTM/w7uhryRSToeE/jKT78/p853RX0L5EwrZ"
                        "rSLBubLPiBw7gbvUP6SsIga5ZQ4CSAxNmYA/gZsuXzA==",

  .name = "ecdsa.",

  .rfcMsgDump = "",
  .rfcB64Signature = "",

  .bits = 256,
  .flags = 256,
  .rfcFlags = 0,

  .algorithm = DNSSEC::ECDSA256,
  .isDeterministic = false,

#if OPENSSL_VERSION_MAJOR >= 3
  // OpenSSL 3.0.0 uses a generic key interface which stores the key PKCS#8-encoded.
  .pem = "-----BEGIN PRIVATE KEY-----\n"
         "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiyLIPdk3DOIxVmmS\n"
         "YlmTstbtUPiVlEyDX46psyCwNVShRANCAATy4PsLhMdMz/Du6GvJFJOh4T+MpPvz\n"
         "+nzndFfQvkTCtmtIsG5ss+IHDuBu9Q/pKwiBrllDgJIDE2ZgD+Bmy5fM\n"
         "-----END PRIVATE KEY-----\n"
#else
  .pem = "-----BEGIN EC PRIVATE KEY-----\n"
         "MHcCAQEEIIsiyD3ZNwziMVZpkmJZk7LW7VD4lZRMg1+OqbMgsDVUoAoGCCqGSM49\n"
         "AwEHoUQDQgAE8uD7C4THTM/w7uhryRSToeE/jKT78/p853RX0L5EwrZrSLBubLPi\n"
         "Bw7gbvUP6SsIga5ZQ4CSAxNmYA/gZsuXzA==\n"
         "-----END EC PRIVATE KEY-----\n"
#endif
};
// clang-format on

/* Ed25519 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h,
 * also from rfc8080 section 6.1
 */
// clang-format off
static const SignerParams ed25519 = SignerParams{
  .iscMap = "Algorithm: 15\n"
            "PrivateKey: ODIyNjAzODQ2MjgwODAxMjI2NDUxOTAyMDQxNDIyNjI=\n",

  .dsSHA1 = "3612 15 1 "
            "501249721e1f09a79d30d5c6c4dca1dc1da4ed5d",

  .dsSHA256 = "3612 15 2 "
              "1b1c8766b2a96566ff196f77c0c4194af86aaa109c5346ff60231a27d2b07ac0",

  .dsSHA384 = "3612 15 4 "
              "d11831153af4985efbd0ae792c967eb4aff3c35488db95f7"
              "e2f85dcec74ae8f59f9a72641798c91c67c675db1d710c18",

  /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
  .signature = {
    0x0a, 0x9e, 0x51, 0x5f, 0x16, 0x89, 0x49, 0x27, 0x0e, 0x98, 0x34, 0xd3, 0x48,
    0xef, 0x5a, 0x6e, 0x85, 0x2f, 0x7c, 0xd6, 0xd7, 0xc8, 0xd0, 0xf4, 0x2c, 0x68,
    0x8c, 0x1f, 0xf7, 0xdf, 0xeb, 0x7c, 0x25, 0xd6, 0x1a, 0x76, 0x3e, 0xaf, 0x28,
    0x1f, 0x1d, 0x08, 0x10, 0x20, 0x1c, 0x01, 0x77, 0x1b, 0x5a, 0x48, 0xd6, 0xe5,
    0x1c, 0xf9, 0xe3, 0xe0, 0x70, 0x34, 0x5e, 0x02, 0x49, 0xfb, 0x9e, 0x05
  },

  .zoneRepresentation = "256 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4=",

  .name = "ed25519.",

  // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev
  // 476d6ded) by printing signature_data
  .rfcMsgDump = "00 0f 0f 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 0e 1d 07 65 78 "
                "61 6d 70 6c 65 03 63 6f 6d 00 07 65 78 61 6d 70 6c 65 03 63 6f "
                "6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 65 "
                "78 61 6d 70 6c 65 03 63 6f 6d 00 ",

  // vector verified from dnskey.py as above, and confirmed with
  // https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
  .rfcB64Signature = "oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeR"
                     "AvTdszaPD+QLs3fx8A4M3e23mRZ9VrbpMngwcrqNAg==",

  .bits = 256,
  .flags = 256,
  .rfcFlags = 257,

  .algorithm = DNSSEC::ED25519,
  .isDeterministic = true,

  .pem = "-----BEGIN PRIVATE KEY-----\n"
         "MC4CAQAwBQYDK2VwBCIEIDgyMjYwMzg0NjI4MDgwMTIyNjQ1MTkwMjA0MTQyMjYy\n"
         "-----END PRIVATE KEY-----\n"
};
// clang-format on

/* Ed448.
 */
// clang-format off
static const SignerParams ed448 = SignerParams{
  .iscMap = "Private-key-format: v1.2\n"
            "Algorithm: 16 (ED448)\n"
            "PrivateKey: xZ+5Cgm463xugtkY5B0Jx6erFTXp13rYegst0qRtNsOYnaVpMx0Z/c5EiA9x8wWbDDct/U3FhYWA\n",

  .dsSHA1 = "9712 16 1 "
            "2873e800eb2d784cdd1802f884b3c540b573eaa0",

  .dsSHA256 = "9712 16 2 "
              "9aa27306f8a04a0a6fae8affd65d6f35875dcb134c05bd7c7b61bd0dc44009cd",

  .dsSHA384 = "9712 16 4 "
              "3876e5d892d3f31725f9964a332f9b9afd791171833480f2"
              "e71af78efb985cde9900ba95315287123a5908ca8f334369",

  .signature = {
    0xb5, 0xcc, 0x21, 0x5a, 0x52, 0x21, 0x60, 0xa3, 0xb8, 0xd9, 0x3a, 0xd7, 0x05,
    0xdd, 0x4a, 0x32, 0x96, 0xce, 0x08, 0xde, 0x74, 0x5f, 0xdb, 0xde, 0x54, 0x95,
    0x97, 0x93, 0x6f, 0x3a, 0x4a, 0x34, 0x41, 0x14, 0xba, 0x99, 0x86, 0x0d, 0xe2,
    0x99, 0xf1, 0x14, 0x6a, 0x1b, 0x7a, 0xfa, 0xef, 0xab, 0x62, 0xd2, 0x71, 0x85,
    0xae, 0xd1, 0x84, 0x80, 0x00, 0x50, 0x03, 0x9e, 0x73, 0x53, 0xe8, 0x9e, 0x19,
    0xb8, 0xc0, 0xdb, 0xd4, 0xf0, 0x1e, 0x44, 0x4c, 0xb7, 0x32, 0x07, 0xda, 0x0b,
    0x64, 0x22, 0xa8, 0x63, 0xaa, 0x7a, 0x12, 0x73, 0xc9, 0x29, 0xfd, 0x50, 0x85,
    0x0f, 0x43, 0x72, 0x77, 0x86, 0xec, 0x88, 0x1a, 0x96, 0x95, 0x4a, 0x01, 0xfe,
    0xf2, 0xe6, 0x77, 0x4a, 0x2e, 0x43, 0xdd, 0x60, 0x29, 0x00,
  },

  .zoneRepresentation = "256 3 16 "
                        "3kgROaDjrh0H2iuixWBrc8g2EpBBLCdGzHmn+"
                        "G2MpTPhpj/OiBVHHSfPodx1FYYUcJKm1MDpJtIA",

  .name = "ed448.",

  // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev
  // 476d6ded) by printing signature_data
  .rfcMsgDump = "00 0f 10 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 25 f1 07 65 78 "
                "61 6d 70 6c 65 03 63 6f 6d 00 07 65 78 61 6d 70 6c 65 03 63 6f "
                "6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 65 "
                "78 61 6d 70 6c 65 03 63 6f 6d 00 ",

  // vector verified from dnskey.py as above, and confirmed with
  // https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
  .rfcB64Signature = "3cPAHkmlnxcDHMyg7vFC34l0blBhuG1qpwLmjInI8w1CMB29FkEA"
                     "IJUA0amxWndkmnBZ6SKiwZSAxGILn/NBtOXft0+Gj7FSvOKxE/07"
                     "+4RQvE581N3Aj/JtIyaiYVdnYtyMWbSNyGEY2213WKsJlwEA",

  .bits = 456,
  .flags = 256,
  .rfcFlags = 257,

  .algorithm = DNSSEC::ED448,
  .isDeterministic = true,

  .pem = "-----BEGIN PRIVATE KEY-----\n"
         "MEcCAQAwBQYDK2VxBDsEOcWfuQoJuOt8boLZGOQdCcenqxU16dd62HoLLdKkbTbD\n"
         "mJ2laTMdGf3ORIgPcfMFmww3Lf1NxYWFgA==\n"
         "-----END PRIVATE KEY-----\n"
};
// clang-format on

/* mldsa44.
 */
// clang-format off
static const SignerParams mldsa44 = SignerParams{
  // from draft-westerbaan-dnssec-mldsa-02
  .iscMap = "Private-key-format: v1.3\n"
            "Algorithm: 18 (MLDSA44)\n"
            "PrivateKey: AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=\n",

  .dsSHA1 = "59829 18 1 "
            "d8fcf493cac1fc6b7f346a4bf7dbd70b96d2301f",

  .dsSHA256 = "59829 18 2 "
              "f1b7353b1ac82e55f029a1e95dd4ac424ccd8fe5014a8b731a49f07334acd8e0",

  .dsSHA384 = "59829 18 4 "
              "1514199dc994b88ee03841e96517c45a0e7317e28240b9eb"
              "bf7c677fc8d50f7e51769511acacb5811a2d474d179db96d",

  .signature = {
    0x5f, 0x20, 0xf2, 0x5d, 0x55, 0xde, 0xc3, 0x51, 0x5a, 0x01, 0xf2, 0x3a,
    0x73, 0x3a, 0x8e, 0x7f, 0x58, 0x28, 0x5b, 0x3e, 0xb2, 0x9d, 0x7b, 0x43,
    0xb3, 0x5d, 0x5d, 0x4b, 0xa5, 0xed, 0x38, 0xb3, 0x6c, 0xa6, 0xb4, 0xd4,
    0xc1, 0x04, 0xc9, 0xfb, 0x2a, 0x1f, 0x5f, 0x59, 0xa5, 0x1e, 0xa2, 0xc6,
    0x19, 0xbb, 0x57, 0x91, 0x9b, 0xb4, 0xc1, 0xbc, 0xe5, 0x0a, 0x18, 0x9a,
    0x2d, 0x28, 0xa9, 0x08, 0x8f, 0x88, 0xac, 0xb3, 0xff, 0x71, 0xf7, 0x96,
    0x7a, 0x8b, 0xdf, 0x56, 0x97, 0xb0, 0x54, 0x59, 0x0c, 0x33, 0xd7, 0x58,
    0x3f, 0xb2, 0x65, 0x55, 0x2c, 0xc3, 0xca, 0x98, 0xe5, 0x23, 0x2f, 0x4c,
    0xe4, 0x37, 0x82, 0x10, 0x6b, 0xee, 0x40, 0xf7, 0x55, 0x17, 0x91, 0x2c,
    0xf1, 0xfc, 0x18, 0xa5, 0xb3, 0x35, 0x2f, 0x33, 0xe5, 0xde, 0xaf, 0xa1,
    0xd8, 0xdd, 0x5e, 0x43, 0x67, 0x27, 0xc6, 0xcd, 0x25, 0x3b, 0xb4, 0x33,
    0xf2, 0x51, 0x52, 0x0c, 0x60, 0xe0, 0x78, 0x65, 0x9d, 0x3a, 0x70, 0x79,
    0xf4, 0x45, 0xeb, 0xcb, 0x54, 0x99, 0xc5, 0x39, 0xe4, 0x02, 0x0e, 0xce,
    0xac, 0x6b, 0x57, 0xbb, 0xa3, 0x0e, 0xfc, 0x29, 0x99, 0x75, 0xb6, 0x68,
    0xe4, 0x2a, 0xf1, 0x1c, 0xcf, 0x2a, 0xe6, 0xba, 0x6e, 0xc7, 0x8f, 0x06,
    0xcb, 0x33, 0x02, 0xce, 0x2c, 0x29, 0xb4, 0x0d, 0xa1, 0x2d, 0xb5, 0x44,
    0x01, 0x20, 0x4f, 0xdf, 0x5f, 0x21, 0xfa, 0xe7, 0x39, 0xde, 0x4d, 0x33,
    0xda, 0xe8, 0x16, 0x5e, 0x4a, 0xde, 0x32, 0x4d, 0x43, 0x18, 0xd3, 0x48,
    0x40, 0x06, 0xb0, 0xa8, 0xb4, 0xb6, 0xb6, 0x9a, 0x50, 0x5d, 0xfe, 0xf7,
    0x60, 0x5a, 0xde, 0x54, 0x15, 0xaf, 0x58, 0x56, 0xc8, 0x51, 0x42, 0x6c,
    0x0a, 0x4e, 0xff, 0xe4, 0x6d, 0x27, 0xdb, 0x90, 0xa2, 0x30, 0x88, 0x3b,
    0x12, 0x3e, 0xf8, 0xbc, 0x13, 0x4f, 0x7d, 0xa0, 0x11, 0xd1, 0x0a, 0xce,
    0xae, 0xfd, 0x8a, 0xf6, 0xe8, 0x70, 0x25, 0xdb, 0x70, 0x8c, 0x99, 0xdd,
    0x6f, 0x19, 0x79, 0x1c, 0xa2, 0x03, 0x14, 0x45, 0xa4, 0x03, 0x9f, 0xa3,
    0x42, 0xe9, 0x6a, 0x3e, 0x08, 0x09, 0x15, 0x7f, 0x12, 0x03, 0xbf, 0x7c,
    0xab, 0xff, 0xf9, 0x2b, 0x17, 0x95, 0x31, 0xd2, 0x3c, 0x69, 0xde, 0xbe,
    0xfb, 0xc1, 0x68, 0x68, 0x7d, 0x96, 0x47, 0x49, 0x8c, 0xf1, 0x95, 0xf2,
    0x8b, 0x5a, 0x23, 0x08, 0xc1, 0xea, 0x12, 0xfd, 0xfc, 0xbf, 0xb8, 0xa8,
    0xd6, 0x9d, 0xa5, 0xb6, 0x3d, 0x61, 0xf7, 0xee, 0xbc, 0xca, 0x59, 0xc1,
    0x75, 0x88, 0x9c, 0x29, 0x56, 0x6f, 0xfc, 0xca, 0x6d, 0x75, 0x7c, 0xff,
    0x1c, 0x84, 0x18, 0x3f, 0x84, 0xa4, 0x30, 0xac, 0x47, 0x5b, 0x29, 0xae,
    0xdf, 0x50, 0x4f, 0x3b, 0xfc, 0x9d, 0x28, 0x8c, 0x49, 0x1a, 0x0c, 0x07,
    0xe3, 0x1c, 0x32, 0xab, 0xcf, 0x80, 0x5a, 0x7f, 0x86, 0xba, 0x31, 0x30,
    0xa0, 0x73, 0x26, 0x1d, 0xb0, 0x65, 0x63, 0x9b, 0xd8, 0x96, 0x48, 0xaa,
    0xcc, 0xaa, 0x65, 0x02, 0x72, 0xda, 0x71, 0x06, 0x13, 0xe5, 0xda, 0xf6,
    0x8f, 0xb9, 0x96, 0xc0, 0x5b, 0xe6, 0x1a, 0x44, 0xca, 0x9b, 0xa7, 0x94,
    0x43, 0x6f, 0xdf, 0xf9, 0x16, 0x53, 0x3b, 0x15, 0x04, 0xb1, 0xc5, 0x07,
    0x3e, 0x04, 0x65, 0x80, 0x38, 0x9a, 0x4d, 0x16, 0xf3, 0x94, 0xac, 0xc2,
    0x4f, 0xfd, 0x5b, 0x83, 0x8d, 0x2a, 0xb6, 0x34, 0x07, 0xe4, 0xaa, 0xd6,
    0xb8, 0x55, 0xfe, 0xad, 0x1e, 0xcf, 0xe2, 0x89, 0xd2, 0xea, 0xc8, 0x3e,
    0xfb, 0x57, 0x1c, 0xaa, 0x4f, 0x82, 0x96, 0x47, 0x18, 0xe8, 0xf9, 0xae,
    0xf1, 0x93, 0x17, 0xc9, 0x36, 0x02, 0x20, 0x9f, 0x14, 0x7a, 0xbe, 0x0a,
    0xfc, 0x0e, 0x2a, 0x53, 0x80, 0x65, 0xa0, 0xf8, 0x1c, 0x01, 0x50, 0x47,
    0x60, 0x44, 0x6a, 0x59, 0x51, 0x16, 0xef, 0x23, 0x94, 0x47, 0x3e, 0x51,
    0xd4, 0xde, 0x7d, 0xf2, 0x0e, 0x84, 0x9c, 0xb0, 0x30, 0xb6, 0x2b, 0xef,
    0xeb, 0x60, 0xf3, 0x8b, 0xd3, 0x09, 0x3d, 0x2a, 0x28, 0x99, 0xf5, 0x37,
    0x07, 0x87, 0x83, 0xf8, 0xe0, 0x0b, 0xb4, 0x9e, 0x8e, 0x4c, 0x42, 0x2c,
    0xdd, 0xfd, 0x68, 0x8d, 0x75, 0xea, 0xe7, 0x3d, 0x2e, 0x53, 0xfa, 0xc3,
    0x1b, 0x04, 0x47, 0x28, 0x05, 0xcc, 0x99, 0x08, 0x95, 0xad, 0x69, 0x6a,
    0x37, 0x5f, 0x44, 0x8a, 0xf8, 0x25, 0x07, 0xc4, 0x1e, 0x5a, 0xc0, 0xed,
    0x6e, 0x13, 0x91, 0x86, 0x21, 0x2f, 0x50, 0xaf, 0x99, 0xb7, 0x4a, 0xdd,
    0x50, 0xef, 0xc0, 0xd5, 0x4e, 0x18, 0x78, 0x1f, 0xa7, 0xf1, 0x98, 0x92,
    0xa5, 0x9d, 0x69, 0x49, 0x96, 0xd5, 0x9b, 0xfc, 0x0a, 0x28, 0xa7, 0x4e,
    0x25, 0x2d, 0xa6, 0x80, 0x9a, 0xaa, 0xb6, 0xda, 0x83, 0x47, 0xa7, 0x29,
    0x3b, 0x6b, 0xf0, 0xde, 0x28, 0x3c, 0x67, 0xb6, 0x14, 0x84, 0xfe, 0xe9,
    0x36, 0x9f, 0x5b, 0x81, 0x28, 0x83, 0x95, 0x79, 0x54, 0x98, 0x61, 0x5b,
    0x2e, 0x9f, 0x68, 0x75, 0x53, 0x10, 0x9d, 0x06, 0x9e, 0x87, 0xbc, 0xa6,
    0x06, 0x5b, 0x31, 0x50, 0xe4, 0xb6, 0xd4, 0x2b, 0x59, 0xb4, 0x05, 0xea,
    0x28, 0xa5, 0x84, 0xff, 0x8a, 0x76, 0x31, 0x3f, 0xe1, 0x77, 0x19, 0x26,
    0xab, 0xb2, 0xbd, 0xd9, 0x80, 0x9b, 0x75, 0x81, 0xc4, 0xa8, 0x99, 0x0d,
    0xda, 0xde, 0x1c, 0x07, 0x93, 0x0a, 0xa3, 0x65, 0x02, 0x11, 0xdf, 0x04,
    0x90, 0x89, 0xfe, 0xa1, 0x95, 0x81, 0x82, 0xee, 0x46, 0x96, 0xdb, 0x5f,
    0x49, 0x3a, 0xa0, 0xf8, 0x20, 0x50, 0x41, 0xa7, 0x18, 0xef, 0x08, 0x90,
    0x2f, 0x62, 0x18, 0x58, 0xac, 0x55, 0x4b, 0x89, 0x0e, 0xbc, 0xb8, 0x47,
    0x5c, 0xae, 0x39, 0xf6, 0x7c, 0x48, 0x59, 0xa7, 0x42, 0x34, 0x12, 0x2c,
    0x13, 0x2b, 0xf0, 0x12, 0x99, 0x35, 0x30, 0x98, 0xf0, 0x40, 0xc4, 0x53,
    0xf3, 0xca, 0x4a, 0xde, 0x9e, 0xf6, 0x91, 0x94, 0x20, 0x91, 0x37, 0xee,
    0x61, 0xec, 0x54, 0x60, 0x04, 0xa6, 0x13, 0x02, 0xce, 0xab, 0x13, 0x6e,
    0x1d, 0xc4, 0xbf, 0xb4, 0xd1, 0x76, 0x86, 0xd3, 0x3e, 0xe9, 0xd2, 0x4b,
    0x93, 0xcd, 0x3f, 0xc4, 0xe8, 0x00, 0x16, 0x78, 0xe5, 0x3e, 0xd0, 0x0e,
    0x82, 0x02, 0xc9, 0x3c, 0x68, 0xdb, 0x2a, 0x18, 0xf7, 0x2f, 0x5e, 0xed,
    0xd8, 0xee, 0xb5, 0x5e, 0x77, 0x5e, 0xc9, 0x80, 0x5f, 0x20, 0x17, 0x4b,
    0xab, 0x02, 0x4b, 0x4f, 0xe1, 0xe3, 0xd5, 0x53, 0x2b, 0xc8, 0xcf, 0x46,
    0x0e, 0xb3, 0x5b, 0xd6, 0x4d, 0xe6, 0xaa, 0xd0, 0x39, 0xcc, 0x9b, 0xae,
    0x05, 0xf5, 0xa0, 0x01, 0x12, 0x0f, 0xf0, 0x0d, 0xd0, 0xb8, 0xbb, 0x5b,
    0x94, 0xc9, 0x7d, 0x43, 0xf0, 0x10, 0xeb, 0x18, 0x44, 0x30, 0xdd, 0xff,
    0xd3, 0xff, 0x6f, 0x4c, 0xf6, 0xf2, 0x96, 0x3e, 0x56, 0x77, 0x0c, 0xdd,
    0x5f, 0xf8, 0x56, 0xa2, 0xcd, 0x5a, 0x46, 0x65, 0x8e, 0x7f, 0x0f, 0x47,
    0x43, 0x59, 0x12, 0x2c, 0xf7, 0x6b, 0x9e, 0x5b, 0x94, 0xce, 0x6d, 0x9f,
    0x53, 0x5c, 0x73, 0x00, 0xcc, 0xaf, 0xa0, 0x22, 0xc7, 0xef, 0xf0, 0x92,
    0xda, 0x45, 0x61, 0xd4, 0x89, 0x53, 0x42, 0xff, 0xc7, 0xda, 0xc0, 0xf0,
    0x30, 0x8f, 0x92, 0x5e, 0x17, 0x90, 0xb9, 0x79, 0xe4, 0xab, 0x3f, 0x0e,
    0xd5, 0x17, 0x03, 0x18, 0x91, 0x37, 0x70, 0x09, 0x8a, 0xa8, 0xfb, 0x40,
    0xb9, 0xb1, 0x2d, 0xbe, 0x9e, 0x0f, 0x10, 0x6f, 0x3b, 0xa3, 0x49, 0x62,
    0x54, 0x00, 0xa0, 0xad, 0xa7, 0x7a, 0x31, 0xad, 0x9a, 0x7f, 0xc3, 0x23,
    0x0d, 0xf6, 0xea, 0xc9, 0x1d, 0xe1, 0x31, 0x94, 0xa0, 0x2c, 0x03, 0xcc,
    0x29, 0xd6, 0x8d, 0x29, 0x52, 0x66, 0x79, 0xf5, 0xcb, 0x7f, 0x50, 0x35,
    0x39, 0xc6, 0xd5, 0xef, 0x1b, 0xb3, 0xd5, 0xf6, 0x43, 0x30, 0x12, 0x62,
    0xc4, 0x59, 0xdd, 0x5d, 0x82, 0xc4, 0x7e, 0x90, 0x16, 0x9e, 0x2b, 0x13,
    0x3f, 0x0a, 0xac, 0x43, 0x10, 0x9a, 0xa6, 0xed, 0x26, 0x3f, 0x3f, 0x16,
    0xc2, 0xa2, 0xa6, 0x9b, 0xd4, 0x4d, 0x33, 0xe6, 0x6a, 0xbe, 0xde, 0x9d,
    0x1d, 0x53, 0x6f, 0x7e, 0x50, 0x9e, 0xf7, 0xba, 0xae, 0x32, 0x22, 0x66,
    0x77, 0xd4, 0xb6, 0x47, 0xbf, 0xa5, 0x0b, 0xd8, 0xb5, 0xfb, 0xb6, 0xf3,
    0x99, 0x86, 0x9b, 0x81, 0x63, 0x69, 0x0a, 0x07, 0x2e, 0xcc, 0x69, 0x99,
    0xfc, 0x2d, 0xe8, 0x12, 0xd3, 0xeb, 0x2b, 0xaa, 0x9b, 0x5e, 0x3d, 0xec,
    0x43, 0x39, 0x2f, 0x6d, 0x76, 0x32, 0x51, 0xf0, 0xa4, 0xa6, 0x12, 0xf5,
    0x66, 0xc2, 0x21, 0x02, 0xcd, 0xa7, 0x2e, 0xcb, 0x39, 0x4b, 0xcf, 0x99,
    0x83, 0xcf, 0x5c, 0x05, 0x34, 0xc6, 0x1d, 0x49, 0x26, 0x15, 0x3a, 0xd3,
    0xdc, 0x3a, 0x67, 0x61, 0xf9, 0xe1, 0x80, 0x76, 0x0d, 0x27, 0xdb, 0x05,
    0x1b, 0x00, 0x93, 0x27, 0x4f, 0xa2, 0x84, 0x33, 0x07, 0x80, 0x3f, 0x2d,
    0xdd, 0x6a, 0xee, 0x9c, 0x46, 0x5b, 0x12, 0x42, 0x4f, 0x3c, 0xff, 0xeb,
    0x5a, 0xf3, 0x50, 0xb4, 0xa9, 0x06, 0x32, 0x66, 0xd8, 0xb3, 0xce, 0x2e,
    0x97, 0x60, 0x7b, 0x96, 0xbb, 0x9f, 0x1f, 0xd1, 0x1f, 0x0c, 0x35, 0xf3,
    0x64, 0xb2, 0x04, 0x1a, 0x19, 0xc9, 0x64, 0x93, 0x91, 0xf3, 0x97, 0x0f,
    0xb0, 0xf2, 0x77, 0x32, 0x0e, 0x80, 0x2c, 0xe5, 0x2c, 0x6a, 0x95, 0xe3,
    0xb7, 0x70, 0x25, 0xf9, 0xa0, 0xf4, 0xda, 0x70, 0x3d, 0x28, 0xfb, 0x25,
    0x5b, 0x8c, 0xf0, 0x62, 0x62, 0x0a, 0x62, 0xe4, 0xb0, 0x0b, 0x45, 0x22,
    0x1f, 0x7d, 0xf3, 0xda, 0x9f, 0xc5, 0xc3, 0x37, 0xfd, 0x00, 0x35, 0x45,
    0xba, 0x6c, 0xcf, 0x13, 0xe6, 0xc6, 0x81, 0x0d, 0x7d, 0x6b, 0x3b, 0x01,
    0x4a, 0xe0, 0x85, 0xe1, 0x5d, 0x10, 0xb7, 0x3f, 0xaf, 0xc1, 0xa1, 0x54,
    0x87, 0x00, 0xcf, 0x1b, 0xb6, 0xfc, 0xbb, 0x53, 0xc3, 0xd4, 0x42, 0xd7,
    0x38, 0x7f, 0x43, 0x90, 0xfa, 0xac, 0x5e, 0xce, 0x11, 0xc4, 0xc4, 0xef,
    0x07, 0xab, 0xb4, 0x58, 0xb3, 0xe5, 0x14, 0xe9, 0xef, 0x60, 0xb8, 0x67,
    0x24, 0xd8, 0x8d, 0x5c, 0xae, 0x01, 0x88, 0xe2, 0x81, 0xfe, 0x9f, 0xb4,
    0xa7, 0x4b, 0x6a, 0x74, 0x77, 0x83, 0x6a, 0xfe, 0x92, 0x0f, 0x29, 0x12,
    0xf4, 0x8d, 0x37, 0xd2, 0xd4, 0x96, 0x96, 0xbc, 0x49, 0x33, 0x82, 0x73,
    0xea, 0xac, 0xaa, 0x9e, 0x8d, 0x67, 0xfc, 0x94, 0xc5, 0xbc, 0x04, 0x49,
    0xb2, 0x5d, 0x8d, 0xaa, 0xd5, 0x3d, 0xfa, 0x5b, 0x15, 0xca, 0x29, 0x6b,
    0x8b, 0x11, 0xe3, 0xec, 0xd9, 0xe4, 0xd0, 0x2a, 0xd2, 0xac, 0xfa, 0xc0,
    0x22, 0xc6, 0x8a, 0xc9, 0x9a, 0x96, 0x86, 0x26, 0x7a, 0x2d, 0xaa, 0xe7,
    0xe9, 0x86, 0x52, 0xb0, 0x2b, 0x4b, 0x74, 0xa3, 0x37, 0xbd, 0x39, 0x39,
    0xea, 0x65, 0x1c, 0x19, 0x73, 0x02, 0x29, 0xb5, 0xe4, 0x32, 0xb1, 0x31,
    0x72, 0xc0, 0x7d, 0xc8, 0x01, 0x8d, 0x8d, 0x4a, 0xc5, 0x9f, 0x52, 0x05,
    0x7c, 0xf4, 0x3e, 0x82, 0x2f, 0x8f, 0x7d, 0x1d, 0x4c, 0x43, 0xbf, 0x37,
    0xda, 0x34, 0xa2, 0x7b, 0x77, 0x47, 0x1c, 0x96, 0x97, 0x7c, 0x25, 0x05,
    0x62, 0x16, 0x31, 0xe5, 0x51, 0x6c, 0x35, 0x09, 0xb9, 0x9e, 0x3c, 0xd0,
    0xc4, 0x36, 0x5a, 0xb9, 0x78, 0xce, 0xbd, 0xe1, 0x70, 0xec, 0xdc, 0x3d,
    0x52, 0xff, 0x84, 0xff, 0xc9, 0xeb, 0x30, 0xba, 0xc4, 0xba, 0x94, 0x76,
    0x1a, 0xf7, 0x81, 0xdc, 0xa9, 0xa9, 0x32, 0x6e, 0x6e, 0xaa, 0x4a, 0x1c,
    0x5c, 0x76, 0xc4, 0xed, 0xba, 0xa5, 0x97, 0xca, 0x64, 0x65, 0x25, 0x93,
    0xdc, 0x05, 0x0f, 0x7a, 0xe7, 0xd7, 0xc9, 0xcb, 0x1b, 0x9c, 0x05, 0xfd,
    0x02, 0x9c, 0xd6, 0x49, 0x52, 0x38, 0x9d, 0xd3, 0xb8, 0x2f, 0x6b, 0xdc,
    0xd8, 0x20, 0x24, 0xd9, 0xd6, 0x98, 0xd9, 0xc4, 0xe6, 0xab, 0x5b, 0x38,
    0xe8, 0x10, 0xfb, 0x60, 0x33, 0xe4, 0x12, 0x41, 0x1f, 0x07, 0x6d, 0xc5,
    0x67, 0x43, 0x89, 0x6c, 0x4a, 0x02, 0x59, 0xc1, 0x30, 0x51, 0x28, 0x86,
    0x38, 0x98, 0xd4, 0xef, 0xd1, 0x60, 0xec, 0x8a, 0xcb, 0x94, 0xd5, 0x43,
    0xff, 0xde, 0x37, 0x64, 0xb7, 0xa2, 0x19, 0x3f, 0x43, 0x3e, 0x18, 0x06,
    0x32, 0xcb, 0xce, 0x18, 0x06, 0x1c, 0x40, 0x7f, 0x6c, 0xd2, 0xdb, 0xff,
    0x95, 0xf4, 0xda, 0x36, 0xb6, 0x70, 0xb3, 0xaf, 0x8d, 0x49, 0x74, 0xd0,
    0x9d, 0x9b, 0x9b, 0xa6, 0xe7, 0xed, 0x6b, 0x6b, 0x39, 0x62, 0xf2, 0x51,
    0x7f, 0xe0, 0x7b, 0xcb, 0xe4, 0x91, 0xa9, 0x65, 0x4c, 0x86, 0x5c, 0xbd,
    0x56, 0x5c, 0x1d, 0x5e, 0xab, 0x09, 0x9e, 0xe2, 0x72, 0x7f, 0x43, 0x19,
    0x8f, 0x87, 0x03, 0x28, 0x6b, 0x5d, 0xdb, 0x4c, 0x15, 0x7b, 0x88, 0x74,
    0x2c, 0xb5, 0xfe, 0xbc, 0xe8, 0x8c, 0x14, 0xb3, 0x57, 0xb1, 0x31, 0x9c,
    0xa6, 0x4a, 0x6c, 0x26, 0x07, 0xfd, 0x46, 0x54, 0x85, 0x2e, 0x43, 0x9f,
    0x6a, 0xd5, 0xac, 0x28, 0xc3, 0x16, 0x7c, 0xb5, 0xd2, 0x31, 0x2c, 0x68,
    0xf3, 0x88, 0x0d, 0xde, 0xae, 0x21, 0xa0, 0x70, 0x4f, 0x64, 0xad, 0xd8,
    0xe2, 0xff, 0x9e, 0x30, 0xb4, 0x59, 0x9e, 0x82, 0x0f, 0x26, 0xff, 0x4a,
    0x71, 0x36, 0xb2, 0xc1, 0xf4, 0x0d, 0x1b, 0x9e, 0xf9, 0xaa, 0xf5, 0x40,
    0xea, 0xde, 0x4d, 0x10, 0xe9, 0xb0, 0xee, 0x18, 0x4f, 0xdf, 0x45, 0x26,
    0xe7, 0x7a, 0xa2, 0x0a, 0x4b, 0xf3, 0x14, 0x37, 0xe0, 0xa2, 0xe9, 0xd6,
    0xe8, 0xaa, 0x84, 0xf5, 0x64, 0xe7, 0x1b, 0x8b, 0x49, 0x1f, 0xff, 0xc5,
    0xd7, 0x4d, 0x27, 0x59, 0x1d, 0xd6, 0xe3, 0x23, 0xaf, 0x3e, 0x36, 0xcd,
    0xf2, 0xdf, 0xee, 0x20, 0x41, 0xb3, 0x3b, 0x87, 0xd1, 0xe2, 0x47, 0x1d,
    0xc0, 0xae, 0xa7, 0xbf, 0xf8, 0xc0, 0x36, 0x6b, 0xab, 0x1f, 0x25, 0x2f,
    0x09, 0x70, 0xd4, 0x2e, 0x49, 0x6b, 0x75, 0xfc, 0xe7, 0x3b, 0xd6, 0xbf,
    0x79, 0x64, 0x48, 0x26, 0x92, 0x8e, 0x96, 0xaf, 0xcd, 0x3d, 0x51, 0x1c,
    0xf0, 0x24, 0xff, 0xe0, 0xed, 0x66, 0x23, 0x84, 0x86, 0xca, 0xf2, 0x6e,
    0x09, 0x7e, 0xf0, 0xea, 0x17, 0x88, 0xc1, 0x52, 0x08, 0x1f, 0xf0, 0x30,
    0x73, 0x81, 0x6d, 0x6d, 0x97, 0xc7, 0xba, 0x84, 0x2b, 0x2c, 0xaf, 0xb5,
    0x5a, 0x3c, 0x1c, 0x3c, 0xdd, 0x83, 0x96, 0x5c, 0x07, 0xd0, 0x5f, 0x11,
    0x28, 0x85, 0x24, 0x6b, 0xea, 0x13, 0x86, 0x32, 0xef, 0xe5, 0xab, 0xee,
    0x3b, 0xd2, 0x17, 0xa2, 0x55, 0xa9, 0x26, 0x53, 0x66, 0xb3, 0x04, 0x06,
    0x47, 0xe1, 0x60, 0x44, 0xcb, 0xfe, 0x1c, 0x8d, 0x54, 0x7f, 0x17, 0xa1,
    0x5b, 0x6a, 0xe2, 0x19, 0xb8, 0xc7, 0x01, 0xed, 0xd6, 0x45, 0xfa, 0xef,
    0x2c, 0xea, 0xac, 0x42, 0x74, 0xeb, 0xa5, 0x8c, 0x3b, 0xc4, 0x60, 0xf9,
    0xa6, 0xc0, 0x86, 0xb7, 0xbd, 0x32, 0xb8, 0xbf, 0x9a, 0x40, 0x1d, 0x2c,
    0x6e, 0xa5, 0x20, 0x2a, 0xd2, 0x3a, 0x5e, 0xa3, 0x4f, 0x7c, 0x73, 0x0e,
    0x94, 0x6d, 0x04, 0xf2, 0xd3, 0x49, 0x09, 0x89, 0x7e, 0xe8, 0xbe, 0x42,
    0x73, 0x76, 0x7e, 0xca, 0x08, 0x25, 0x39, 0x4a, 0xf1, 0x49, 0x6d, 0x46,
    0xac, 0x08, 0xd1, 0x57, 0xd6, 0xe0, 0x03, 0xb6, 0x78, 0x8b, 0xa2, 0xb2,
    0x23, 0xf6, 0x8b, 0x0a, 0x49, 0x06, 0x9a, 0x25, 0x1c, 0x71, 0x8a, 0xe8,
    0x48, 0xb8, 0x5b, 0x61, 0x8f, 0x63, 0x67, 0x1c, 0xcf, 0x60, 0x68, 0xf0,
    0x37, 0x00, 0x66, 0xd7, 0xdd, 0x1d, 0x7f, 0xe9, 0x4a, 0x55, 0x99, 0xe9,
    0x58, 0xc2, 0x53, 0xd8, 0x4c, 0x86, 0x3d, 0x4d, 0xb9, 0x7c, 0x52, 0x25,
    0xe2, 0xf1, 0x34, 0xeb, 0x29, 0xd4, 0x14, 0x7d, 0x1e, 0x86, 0xff, 0x79,
    0x6f, 0x5d, 0x6a, 0xbb, 0x42, 0x4e, 0xa6, 0x02, 0x51, 0xd2, 0x33, 0x9d,
    0x03, 0x32, 0xa0, 0xc3, 0x06, 0xaf, 0x2c, 0x58, 0x02, 0xa1, 0x44, 0xc3,
    0xb2, 0x1f, 0xc5, 0x86, 0x47, 0x05, 0xa9, 0x1d, 0xe9, 0x04, 0x86, 0xb9,
    0xc4, 0x80, 0xe3, 0x01, 0xa8, 0x6b, 0xc5, 0x5c, 0x3d, 0x46, 0xdf, 0x5c,
    0xf2, 0xda, 0x0f, 0x06, 0x98, 0xa4, 0xa6, 0x4b, 0x91, 0x7a, 0xc0, 0x8c,
    0x20, 0x0a, 0x45, 0x98, 0xbd, 0x39, 0x79, 0xc4, 0x42, 0x38, 0x99, 0xcb,
    0x2d, 0x79, 0xd4, 0xc5, 0x40, 0x0d, 0x57, 0x1b, 0x56, 0x6e, 0xf3, 0x1f,
    0x4c, 0x68, 0x30, 0xd2, 0xc1, 0x50, 0x7a, 0x96, 0x67, 0xb1, 0xd1, 0x61,
    0x3b, 0xda, 0x6d, 0x09, 0x00, 0xf8, 0x5a, 0x83, 0x95, 0xa4, 0x87, 0xda,
    0xf8, 0x7f, 0x48, 0xe8, 0xb2, 0x61, 0x58, 0x0e, 0x0c, 0xce, 0x27, 0x56,
    0x6d, 0xba, 0x1c, 0xf5, 0x2b, 0xc1, 0xa2, 0xca, 0xe8, 0xfb, 0xa9, 0x85,
    0x84, 0x04, 0x3b, 0x41, 0xcf, 0x8f, 0x77, 0x86, 0xf6, 0x4a, 0x10, 0xff,
    0x2e, 0x2b, 0xa0, 0x0a, 0xf4, 0x00, 0x88, 0x6e, 0x54, 0x96, 0x0e, 0x0c,
    0xa9, 0xcc, 0x81, 0x5a, 0x6b, 0x1d, 0x11, 0x03, 0xdf, 0x37, 0x24, 0xb1,
    0x6d, 0xd0, 0x1e, 0xae, 0xbb, 0xdc, 0xb5, 0x3c, 0xfc, 0x6b, 0xa0, 0xbb,
    0xa7, 0xf7, 0x35, 0xfa, 0x1f, 0xc7, 0xb4, 0xfb, 0x82, 0x61, 0x03, 0x42,
    0x10, 0x3a, 0x1e, 0x11, 0x5f, 0xa4, 0x2d, 0xbc, 0xfd, 0x9d, 0xf2, 0x4b,
    0x01, 0x94, 0x69, 0xe8, 0x89, 0xcc, 0xd2, 0x64, 0x50, 0xfb, 0xa2, 0xee,
    0x14, 0xd5, 0x4e, 0x42, 0xd4, 0x80, 0x25, 0x45, 0x00, 0x0d, 0x15, 0x19,
    0x2e, 0x39, 0x7a, 0x81, 0x82, 0x9b, 0xb3, 0xd8, 0xea, 0xec, 0xf2, 0x14,
    0x22, 0x27, 0x29, 0x2a, 0x2c, 0x32, 0x3a, 0x4b, 0x64, 0x7c, 0x92, 0xa2,
    0xb1, 0xba, 0xc8, 0xcf, 0xe6, 0x04, 0x07, 0x12, 0x15, 0x24, 0x2e, 0x33,
    0x4a, 0x55, 0x5d, 0x60, 0x72, 0x75, 0xa7, 0xda, 0xe6, 0xe9, 0xef, 0xf1,
    0x04, 0x59, 0x94, 0x98, 0xa0, 0xaf, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x0f, 0x21, 0x34, 0x3b
  },

  // from draft-westerbaan-dnssec-mldsa-02
  .zoneRepresentation = "257 3 18 "
                        "17K0clSq4NtF55MNSpjSyX2PE5fReJ2voXAksxbpvslPyZRtQvGbeadBO7qj"
                        "PnFJy0LtURVpOsBB+suYit61/g4dhjEYSZW1ksOX0ilOLhT5CqQUujgmiZrE"
                        "P0zMrLwm6agyuVEY1ctDPL75ZgsAE44IF/YediyidMNq1VTrIqrBFi5KsBrL"
                        "oeOMTv2PgLZbMz0PcuVd/nHOnB67mInnxWEGwP1zgDoq7P6v3teqPLLO2lTR"
                        "K9jNNqeM+XWUO0er0l6ICsRS5XQu0ejRqCr6huWQx1jBWuTShA2SvKGlCQ9A"
                        "SWWX/KfYuVE/GhvabpUKqpjeRnUH1KT1pPBZkhZYLDVy9i7aiQWrNYFnDEoC"
                        "d3oz4Mpylf2PT/bRoKOnaD1l9fX3/GDaAj6CbF+SFEwC99G6EHWYdVPqk2f8"
                        "122ZC3+pnNRa/biDbUPkWfUYffBYR5cJoB6mg1k1+nBGCZDNPcG6QBupS6sd"
                        "3kGsZ6szGdysoGBI1MTu8n7hOpwX0FOPQw8tZC3CQVZg3niHfY2KvHJSOXjA"
                        "QuQoX0MZhGxEEmJCl2hEwQ5Va6IVtacZ5Z0MayqW05hZBx/cws3nUkp77a5U"
                        "6FsxjoVOj+Ky8+36yXGRKCcKr9HlBEw6T9r9n/MfkHhLjo5FlhRKDa9YZRHT"
                        "2ZYrnqla8Ze05fxg8rHtFd46W+9fib3HnZEFHZsoFudPpUUx79wcvnTUSIV/"
                        "R2vNWPIcC2U7O3ak4HamVZowJxhVXMY/dIWaq6uSXwI4YcqM0Pe62yhx9n1V"
                        "Mm10URNa1F9KG6aRGPuyyKMO7JOS7z+XcGbJrdXHEMxkexUU0hfZWMcBfD6Q"
                        "/SDATmdLkEhuk3CjGgAdMvRzl55JBnSefkd/oLdFCPil8jeDErg8Jb04jKCw"
                        "//dHi69CtxZn7arJfEaxKWQ+WG5bBVoMIRlG1PNuZ1vtWGD6BCoxXZgmFk1q"
                        "kjfDWl+/SVSQpb1N8ki5XEqud4S2BWcxZqxCRbW0sIKgnpMj5i8geMW3Z4NE"
                        "be/XNq06NwLUmwiYRJAKYYMzl7xEGbMNepegs4fBkRR0xNQbU+Mql3rLbw6n"
                        "XbZbs55Z5wHnaVfe9vLURVnDGncSK1IE47XCGfFoixTtC8C4AbPm6C3NQ+nA"
                        "6fQXRM2YFb0byIINi7Ej8E+s0bG2hd1aKxuNu/PtkzZw8JWhgLTxktCLELj6"
                        "u9/MKyRRjjLuoKXgyQTKhEeACD87DNLQuLavZ7w1W5SUAl3HsKePqA46Lb/r"
                        "UTKIUdYHgZjpSTZRrnh+wCUfkiujDp9R32Km1yeEzz3SBTkxdt+jJKUSvZSX"
                        "CjbdNKUUqGeR8Os28BRbCatkZRtKAxOymWEaKhxIiRYnWYdooxFAYLpEQ0ht"
                        "9RUioc6IswmFwhb45u0XjdVnswSg1Mr7qIKig0LxepqiauWNtjAIPSw1j99W"
                        "bD9dYqQoVnvJ6ozpXKoPNUdLC/qPM5olCrTfzyCDvo7vvBBV4Y/hU3DuyyYF"
                        "Ztg/8GshGq7EPKKbVMzQD4gVokZe8LRlFcx+QfMSTwnv/3OTCatYspoUWaAL"
                        "zlA46TjJZ49y6w5O5f2q5m2fhXP8l/xCtJWfS/i2HXhDPoawM11ukZHE2L9I"
                        "ezkFwQjP1qwksM633LfPUfhNDtaHuV6uscUzwG8NlwI9kqcIJYN7Wbpst9Tl"
                        "awqHwgOGKujzFbpZJejt76Z5NpoiAnZhUfFqll+fgeznbMBwtVhp5NuXhM8F"
                        "yDCzJCyDEg==",

  .name = "mldsa44.",

  // from draft-westerbaan-dnssec-mldsa-02, this represents "example.com. 3600 IN MX 10 mail.example.com."
  .rfcMsgDump = "00 0f 12 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 e9 b5 07 65 78 "
                "61 6d 70 6c 65 03 63 6f 6d 00 07 65 78 61 6d 70 6c 65 03 63 6f "
                "6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 65 "
                "78 61 6d 70 6c 65 03 63 6f 6d 00 ",

  // from draft-westerbaan-dnssec-mldsa-02
  .rfcB64Signature = "kdySHzwB7NftjQSAF7snCeKau3NoqpLNg16h/eHZV8L3Zpi30lkRyiS4FLMM"
                     "ZqTjzbf1A/bShg4qZpYlnfqXN8uqFWF9GEEJOgte1CFdF4GC05gEBU88Kryf"
                     "nGAcpXKafw9htDxZrqmqVSWN+1guW7HyUUFo1IuWTnZKuhZptDJkq+Ml+5ZH"
                     "y4p+2Tdwk8MH7tJlTYk/UVaM1wIXPB2YgJ++kD0zhys5c38rztcaOmMXt6ej"
                     "yAEY37Dc1Z/KsrRQZWv+XZ/CTliuh+dGJHoGuTm5KwS0us884ukWNC/wIU/S"
                     "dlGoBDVXsT163Tr6lTf8pJ4xixcKIN8nsKSFxP9j+AbaN5SofIAvp4LGIFLg"
                     "MKsRV/cqeYo8PegVD2EhAQ2/HVTO3uO8vlqLK7nWVVK2+2aYKIL2EqzjhRYK"
                     "U5DhMwS9ZgbG0niszGXpvZcNcOyABXysdVuaDjnUuamYVACOUrV786LNmt8I"
                     "WDnXWoPPMErPk5vNyHq6+ZHg79UeZpSzx0Ae/1aIfi2WEta9Or5sGItBn6vF"
                     "Wi9kJRuhuoMIXf9CLBV/LHL/PIenBxXSnr2Owg54AuSN2tmk2lDy8BfKzzvx"
                     "TOoKXx4edo96Xv6QWASAxO9JmyEvhnF3SBI6HG3fn2+k8rgJLIHpsr4pZhMh"
                     "4/SQWaojxt51nEIFi1bl7P6sAmCdMP81LSNx05hIkKcPeO33hA2VSDO7GzOE"
                     "snBOzbhUX9gbFr3aNV/Wrbs/cZMAL1I0IKG20jkmEfZ9PeKN0hXCxHJo4hPF"
                     "L2mm9ciGpuXS7oN8f7YublNTwRY8b4plScVICpyBT5UDOgezR9/+DnklL0fz"
                     "IORMTRnpD1hq4BqZMgNMwvczFg3DrSLQP/cBiKLn3toJrkSuU9aXodEqW3lh"
                     "RdMvDUqTtHgMKas5velmabpENAbixiB8n5zoENnMLV6w/13a+yOTT2WUvESg"
                     "HqF92FfQMdQl36noyewmjUFZopirCGV6AkebdVsTY27DtYkGWamLXcm3w2d6"
                     "AYV/LssvyK/Jlnw/E7YRJWkO+8PvHA2tvfQSr8fNC4ll/KHdwr8d0Q8spPcO"
                     "HMMui20XDYeprPmp64hSt4IBuiQusdm3SQsWjQvaUsg8sykZd24S/wNQiGsw"
                     "XaoG6oWYYCZupfvGc0sgb+9qxZU5fSAYKwx5LjYajruvQ5flebAtrUdLuPbG"
                     "Mb2I7Z8c4IvDmbA6ljqMK60w1XI+wU7jSWzoEaiIeAUR1aT925KFMEhmFG3k"
                     "Tr5ZPI57wM7pEI9jBME80lu7D3f4z++icSHSJ5YNa/+kp7eSIT94m4Tj7nel"
                     "mN0WnKFgzGZKnuiDGJew5FFnfB0qfvqUNUPt1rVaIr7rzBBL4j8WQHqOo17A"
                     "+0pnIqKTe1Z8MxFnPwP1eWHa3T/7JeEPSD5JFOpEWxs12twxTC42BrTCckSm"
                     "rfmksfxmJa0mfflaOPHkjahTprrItJzG1efHYCu5nP5rsclZF0hDOR1OZrgK"
                     "2IhnG1VotIPB4+/+70+uD0qcqY3L2yonxFlQS8sEmMcXi9xQTxdFG4NOk/TQ"
                     "G50Oly1tRp9UoLjwTDtlIjh71Lz9lajbAabV4WtIvd7cwaREO0kFAtzIgfJR"
                     "VMasWvUo6e93qQBThzvkCNs8ngsa0jXJL1HrERP+qkiULCDMr19FVimWmIzL"
                     "CkR9pg9WWjruY5krgdVbINUqjsyyGriPEhy2JneNWdOdFoAwkWtGbIpQhHs2"
                     "bLHpG9xPPF+ElqLmjNa76BhXv4caurHYn7K0m4NMVgDywGXoh0OGe/PoXQ4g"
                     "Ht7EbHgbCQO9V8+/1+MWw9ZrU6btOGJ2JVXeyRXYyJarn+cnPL1nWOlq7bMD"
                     "3mazOTNZPc5UENSvDL51hmd3WD71i2u9btqIzjnmSxggPHRsVcOaGXHM3aUJ"
                     "nrDtwi1EY7THlJatS+ItjWQMCDh8g/4LF9S2UWGFc21MimswWvgh1jB/4hYI"
                     "9C8PSCpAeV26dXoANntR/lLms42488dVJ1wyNGjaNNX1itiqFYsNUn3LyT3T"
                     "dVUgBwkfzO1I4UnhDIbsHJWbs7Dl/52Ei4MbpPJXnL1gMNc6SD1EkT1CeY9f"
                     "esHF20wr8tb7V+qPO2TCE26syB9lZ41OSOYgqPYK/OHyoLedQmTOFls0QMj2"
                     "F0bks3pJm/TDDMEuUdhulPatnZBNIXexqNImQUFyipcJ9W5KnD6Wr5+jyULy"
                     "VBQRpWPzipfPFACb5d5lWPtrvh4kurYt3sSdUy+WJKuYb1roxXTZJqP0QDgn"
                     "VEYL5nJnxqSRD9fx7HMRHXODkVioBFmSUgwP5XBljn/YpIgG8Ix42hyKMCti"
                     "yv1gIY3/m8cfHyj5I6xcDHUTZHyM9+KSZeipf6wUnngoZuYzP9N3Nozo8LI+"
                     "w3Mo6s/VjhmsALOYcus720s0MQY5prhkcZYUvgv9YL9R+1Fm7Kxy3cjpnGqy"
                     "WwxN6YmNw/f6C+21Dlex7+09o2ygi0M1NEZZ0FhdaBmxVxtSjbBm3uKu9taW"
                     "0zO534HXlifFkxf6GhboxbGdm1yekVIjDLnC+iodQyLwIi0vvc435Xk4GRBs"
                     "8D5Pxf3vT3tgPy5sDXbJ3lT58MekKdT/HobugDOdu0ltGenFjnKFhdJudvQ/"
                     "FFjqJk1HYnjxxdP3QYKlSHOv2ADtRqgI0VHLJmECOifYr90uWml1uzaUzK0X"
                     "Tulm8fn6lfpF3EWJYSsq1iXQWuiRw9u6dxiS02+c4Z8Nzumoh48W+z0GFy+q"
                     "ClyhqdedA6k3WZIJi919e5b24mj5rqzcgrA6KMqnTJDKh2cuoKC1fI88w774"
                     "co0XPDyg+v/RD2ET1fquDGHjeVyVBsknNZQ5lwvLeAy/uH+Ql5qECQ9WCIJP"
                     "ydZZhB906hkHZ+vch1fG+vhgMtoXhtZ4UXzQwbJBL/4wxtOau3IgWGkJEImJ"
                     "PK3KE+7phfn5YmGSjVCp8o1t2QxpwJ1ZPBuTrUWy15gruIP8e415f0UPUZjF"
                     "G+p6JqsUzaBzgZvAg9nY/vHEC0sXuC7lnqmDxr8LU9JMD77XrBccXMP199d/"
                     "10bJW8TH+yzqE4syjdUPEalQnwP/fh9us92eSdv50vr0/KPhzfWzcRwWFxof"
                     "S15zlJe3xNj+BAURHCApKjBkh5emuLy+w9zn6vn6/QsbXWp6hZWcoLO6ytLf"
                     "6/H+DhguNzs/VFVbg5SXo62wztPoAAAAAAAAAAAAAA0jNEY=",

  .bits = 256,
  .flags = 257,
  .rfcFlags = 257,

  .algorithm = DNSSEC::MLDSA44,
  .isDeterministic = false,

  // ideally this pem would only contain the seed
  .pem = "-----BEGIN PRIVATE KEY-----\n"
         "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\n"
         "-----END PRIVATE KEY-----\n"
};
// clang-format on

struct Fixture
{
  Fixture()
  {
    BOOST_TEST_MESSAGE("All available/supported algorithms:");
    auto pairs = DNSCryptoKeyEngine::listAllAlgosWithBackend(nullptr /* no structured logging */);
    for (auto const& pair : pairs) {
      BOOST_TEST_MESSAGE("  " + std::to_string(pair.first) + ": " + pair.second);
    }

    BOOST_TEST_MESSAGE("Setting up signer params:");

    addSignerParams(DNSSEC::RSASHA256, "RSA SHA256", rsaSha256SignerParams);

#ifdef HAVE_LIBCRYPTO_ECDSA
    addSignerParams(DNSSEC::ECDSA256, "ECDSA SHA256", ecdsaSha256);
#endif

// We need to have HAVE_LIBCRYPTO_ED25519 for the PEM reader/writer.
#if defined(HAVE_LIBCRYPTO_ED25519)
    addSignerParams(DNSSEC::ED25519, "ED25519", ed25519);
#endif

#if defined(HAVE_LIBCRYPTO_ED448)
    addSignerParams(DNSSEC::ED448, "ED448", ed448);
#endif

#if defined(HAVE_LIBCRYPTO_ML_DSA_44)
    addSignerParams(DNSSEC::MLDSA44, "MLDSA44", mldsa44);
#endif
  }

  void addSignerParams(const uint8_t algorithm, const std::string& name, const SignerParams& params)
  {
    BOOST_TEST_MESSAGE("  " + std::to_string(algorithm) + ": " + name + " (" + params.name + ")");
    signerParams.insert_or_assign(algorithm, params);
  }

  const std::string message{"Very good, young padawan."};
  std::unordered_map<uint8_t, struct SignerParams> signerParams;
};

static void checkRR(const SignerParams& signer)
{
  DNSKEYRecordContent drc;
  auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(nullptr /* no structured logging */, drc, signer.iscMap));
  DNSSECPrivateKey dpk;
  dpk.setKey(dcke, signer.rfcFlags);

  sortedRecords_t rrs;
  /* values taken from rfc8080 for ed25519 and ed448, rfc5933 for gost */
  DNSName qname(dpk.getAlgorithm() == DNSSEC::ECCGOST ? "www.example.net." : "example.com.");

  RRSIGRecordContent rrc;
  uint32_t expire = 1440021600;
  uint32_t inception = 1438207200;

  if (dpk.getAlgorithm() == DNSSEC::ECCGOST) {
    rrc.d_signer = DNSName("example.net.");
    inception = 946684800;
    expire = 1893456000;
    rrs.insert(DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.1"));
  }
  else {
    rrc.d_signer = qname;
    rrs.insert(DNSRecordContent::make(QType::MX, QClass::IN, "10 mail.example.com."));
  }

  rrc.d_originalttl = 3600;
  rrc.d_sigexpire = expire;
  rrc.d_siginception = inception;
  rrc.d_type = (*rrs.cbegin())->getType();
  rrc.d_labels = qname.countLabels();
  rrc.d_tag = dpk.getTag();
  rrc.d_algorithm = dpk.getAlgorithm();

  string msg = getMessageForRRSET(qname, rrc, rrs, false);

  BOOST_CHECK_EQUAL(makeHexDump(msg), signer.rfcMsgDump);

  string signature = dcke->sign(msg);

  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): Boost stuff.
  BOOST_CHECK(dcke->verify(msg, signature));

  if (signer.isDeterministic) {
    string b64 = Base64Encode(signature);
    BOOST_CHECK_EQUAL(b64, signer.rfcB64Signature);
  }
  else {
    std::string raw;
    B64Decode(signer.rfcB64Signature, raw);
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): Boost stuff.
    BOOST_CHECK(dcke->verify(msg, raw));
  }
}

static void test_generic_signer(std::shared_ptr<DNSCryptoKeyEngine> dcke, DNSKEYRecordContent& drc, const SignerParams& signer, const std::string& message)
{
  BOOST_CHECK_EQUAL(dcke->getAlgorithm(), signer.algorithm);
  BOOST_CHECK_EQUAL(dcke->getBits(), signer.bits);

  vector<string> errorMessages{};
  BOOST_CHECK_EQUAL(dcke->checkKey(errorMessages), true);
  if (!errorMessages.empty()) {
    BOOST_TEST_MESSAGE("Errors from " + dcke->getName() + " checkKey()");
    for (auto& errorMessage : errorMessages) {
      BOOST_TEST_MESSAGE("  " + errorMessage);
    }
  }

  BOOST_CHECK_EQUAL(drc.d_algorithm, signer.algorithm);

  DNSSECPrivateKey dpk;
  dpk.setKey(dcke, signer.flags);
  drc = dpk.getDNSKEY();

  BOOST_CHECK_EQUAL(drc.d_algorithm, signer.algorithm);
  BOOST_CHECK_EQUAL(drc.d_protocol, 3);
  BOOST_CHECK_EQUAL(drc.getZoneRepresentation(), signer.zoneRepresentation);

  DNSName name(signer.name);
  auto ds1 = makeDSFromDNSKey(nullptr /* no structured logging */, name, drc, DNSSEC::DIGEST_SHA1);
  if (!signer.dsSHA1.empty()) {
    BOOST_CHECK_EQUAL(ds1.getZoneRepresentation(), signer.dsSHA1);
  }

  auto ds2 = makeDSFromDNSKey(nullptr /* no structured logging */, name, drc, DNSSEC::DIGEST_SHA256);
  if (!signer.dsSHA256.empty()) {
    BOOST_CHECK_EQUAL(ds2.getZoneRepresentation(), signer.dsSHA256);
  }

  auto ds4 = makeDSFromDNSKey(nullptr /* no structured logging */, name, drc, DNSSEC::DIGEST_SHA384);
  if (!signer.dsSHA384.empty()) {
    BOOST_CHECK_EQUAL(ds4.getZoneRepresentation(), signer.dsSHA384);
  }

  auto signature = dcke->sign(message);
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): Boost stuff.
  BOOST_CHECK(dcke->verify(message, signature));

  auto signerSignature = std::string(signer.signature.begin(), signer.signature.end());
  if (signer.isDeterministic) {
    auto signatureBase64 = Base64Encode(signature);
    auto signerSignatureBase64 = Base64Encode(signerSignature);
    BOOST_CHECK_EQUAL(signatureBase64, signerSignatureBase64);
  }
  else {
    /* since the signing process is not deterministic, we can't directly compare our signature
       with the one we have. Still the one we have should also validate correctly. */
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): Boost stuff.
    BOOST_CHECK(dcke->verify(message, signerSignature));
  }

  if (!signer.rfcMsgDump.empty() && !signer.rfcB64Signature.empty()) {
    checkRR(signer);
  }
}

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables,readability-identifier-length): Boost stuff.
BOOST_FIXTURE_TEST_CASE(test_generic_signers, Fixture)
{
  for (const auto& algoSignerPair : signerParams) {
    auto signer = algoSignerPair.second;

    DNSKEYRecordContent drc;
    auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(nullptr /* no structured logging */, drc, signer.iscMap));
    test_generic_signer(dcke, drc, signer, message);

    DNSKEYRecordContent pemDRC;
    shared_ptr<DNSCryptoKeyEngine> pemKey{DNSCryptoKeyEngine::makeFromPEMString(nullptr /* no structured logging */, pemDRC, signer.algorithm, signer.pem)};

    BOOST_CHECK_EQUAL(pemKey->convertToISC(), dcke->convertToISC());

    test_generic_signer(pemKey, pemDRC, signer, message);

    auto dckePEMOutput = dcke->convertToPEMString();
    BOOST_CHECK_EQUAL(dckePEMOutput, signer.pem);

    auto pemKeyOutput = pemKey->convertToPEMString();
    BOOST_CHECK_EQUAL(pemKeyOutput, signer.pem);
  }
}

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables,readability-identifier-length): Boost stuff.
BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt)
{
  {
    // rfc5155 appendix A
    const unsigned char salt[] = {0xaa, 0xbb, 0xcc, 0xdd};
    const unsigned int iterations{12};
    const std::vector<std::pair<std::string, std::string>> namesToHashes = {
      {"example", "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom"},
      {"a.example", "35mthgpgcu1qg68fab165klnsnk3dpvl"},
      {"ai.example", "gjeqe526plbf1g8mklp59enfd789njgi"},
      {"ns1.example", "2t7b4g4vsa5smi47k61mv5bv1a22bojr"},
      {"ns2.example", "q04jkcevqvmu85r014c7dkba38o0ji5r"},
      {"w.example", "k8udemvp1j2f7eg6jebps17vp3n8i58h"},
      {"*.w.example", "r53bq7cc2uvmubfu5ocmm6pers9tk9en"},
      {"x.w.example", "b4um86eghhds6nea196smvmlo4ors995"},
      {"y.w.example", "ji6neoaepv8b5o6k4ev33abha8ht9fgc"},
      {"x.y.w.example", "2vptu5timamqttgl4luu9kg21e0aor3s"},
      {"xx.example", "t644ebqk9bibcna874givr6joj62mlhv"},
      {"2t7b4g4vsa5smi47k61mv5bv1a22bojr.example", "kohar7mbb8dc2ce8a9qvl8hon4k53uhi"},
    };

    for (const auto& [name, expectedHash] : namesToHashes) {
      auto hash = hashQNameWithSalt(std::string(reinterpret_cast<const char*>(salt), sizeof(salt)), iterations, DNSName(name));
      BOOST_CHECK_EQUAL(toBase32Hex(hash), expectedHash);
    }
  }

  {
    /* no additional iterations, very short salt */
    const unsigned char salt[] = {0xFF};
    const unsigned int iterations{0};
    const std::vector<std::pair<std::string, std::string>> namesToHashes = {
      {"example", "s9dp8o2l6jgqgg26ecobtjooe7p019cs"},
    };

    for (const auto& [name, expectedHash] : namesToHashes) {
      auto hash = hashQNameWithSalt(std::string(reinterpret_cast<const char*>(salt), sizeof(salt)), iterations, DNSName(name));
      BOOST_CHECK_EQUAL(toBase32Hex(hash), expectedHash);
    }
  }

  {
    /* only one iteration */
    const unsigned char salt[] = {0xaa, 0xbb, 0xcc, 0xdd};
    const unsigned int iterations{1};
    const std::vector<std::pair<std::string, std::string>> namesToHashes = {
      {"example", "ulddquehrj5jpf50ga76vgqr1oq40133"},
    };

    for (const auto& [name, expectedHash] : namesToHashes) {
      auto hash = hashQNameWithSalt(std::string(reinterpret_cast<const char*>(salt), sizeof(salt)), iterations, DNSName(name));
      BOOST_CHECK_EQUAL(toBase32Hex(hash), expectedHash);
    }
  }

  {
    /* 65535 iterations, long salt */
    unsigned char salt[255];
    for (unsigned char idx = 0; idx < 255; idx++) {
      salt[idx] = idx;
    };
    const unsigned int iterations{65535};
    const std::vector<std::pair<std::string, std::string>> namesToHashes = {
      {"example", "no95j4cfile8avstr7bn4aj9he18trri"},
    };

    for (const auto& [name, expectedHash] : namesToHashes) {
      auto hash = hashQNameWithSalt(std::string(reinterpret_cast<const char*>(salt), sizeof(salt)), iterations, DNSName(name));
      BOOST_CHECK_EQUAL(toBase32Hex(hash), expectedHash);
    }
  }
}

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables): Boost stuff.
BOOST_AUTO_TEST_SUITE_END()
