root/tools/tools/crypto/cryptocheck.c
/*-
 * Copyright (c) 2017 Chelsio Communications, Inc.
 * All rights reserved.
 * Copyright (c) 2021 The FreeBSD Foundation
 * Written by: John Baldwin <jhb@FreeBSD.org>
 *
 * Portions of this software were developed by Ararat River
 * Consulting, LLC under sponsorship of the FreeBSD Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 */
/*-
 * Copyright (c) 2004 Sam Leffler, Errno Consulting
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 * 3. Neither the names of the above-listed copyright holders nor the names
 *    of any contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * NO WARRANTY
 * 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 NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
 */

/*
 * A different tool for checking hardware crypto support.  Whereas
 * cryptotest is focused on simple performance numbers, this tool is
 * focused on correctness.  For each crypto operation, it performs the
 * operation once in software via OpenSSL and a second time via
 * OpenCrypto and compares the results.
 *
 * cryptocheck [-vz] [-A aad length] [-a algorithm] [-d dev] [-I IV length]
 *             [size ...]
 *
 * Options:
 *      -v      Verbose.
 *      -z      Run all algorithms on a variety of buffer sizes.
 *
 * Supported algorithms:
 *      all             Run all tests
 *      hash            Run all hash tests
 *      mac             Run all mac tests
 *      cipher          Run all cipher tests
 *      eta             Run all encrypt-then-authenticate tests
 *      aead            Run all authenticated encryption with associated data
 *                      tests
 *
 * Hashes:
 *      ripemd160       160-bit RIPEMD
 *      sha1            SHA-1
 *      sha224          224-bit SHA-2
 *      sha256          256-bit SHA-2
 *      sha384          384-bit SHA-2
 *      sha512          512-bit SHA-2
 *      blake2b         Blake2-B
 *      blake2s         Blake2-S
 *
 * MACs:
 *      ripemd160hmac   160-bit RIPEMD HMAC
 *      sha1hmac        SHA-1 HMAC
 *      sha224hmac      224-bit SHA-2 HMAC
 *      sha256hmac      256-bit SHA-2 HMAC
 *      sha384hmac      384-bit SHA-2 HMAC
 *      sha512hmac      512-bit SHA-2 HMAC
 *      gmac            128/192/256-bit GMAC
 *      gmac128         128-bit GMAC
 *      gmac192         192-bit GMAC
 *      gmac256         256-bit GMAC
 *      poly1305
 *
 * Ciphers:
 *      aes-cbc         128/192/256-bit AES-CBC
 *      aes-cbc128      128-bit AES-CBC
 *      aes-cbc192      192-bit AES-CBC
 *      aes-cbc256      256-bit AES-CBC
 *      aes-ctr         128/192/256-bit AES-CTR
 *      aes-ctr128      128-bit AES-CTR
 *      aes-ctr192      192-bit AES-CTR
 *      aes-ctr256      256-bit AES-CTR
 *      aes-xts         128/256-bit AES-XTS
 *      aes-xts128      128-bit AES-XTS
 *      aes-xts256      256-bit AES-XTS
 *      camellia-cbc    128/192/256-bit Camellia-CBC
 *      camellia-cbc128 128-bit Camellia-CBC
 *      camellia-cbc192 192-bit Camellia-CBC
 *      camellia-cbc256 256-bit Camellia-CBC
 *      chacha20
 *
 * Encrypt then Authenticate:
 *      <cipher>+<mac>
 *
 * Authenticated Encryption with Associated Data:
 *      aes-gcm         128/192/256-bit AES-GCM
 *      aes-gcm128      128-bit AES-GCM
 *      aes-gcm192      192-bit AES-GCM
 *      aes-gcm256      256-bit AES-GCM
 *      aes-ccm         128/192/256-bit AES-CCM
 *      aes-ccm128      128-bit AES-CCM
 *      aes-ccm192      192-bit AES-CCM
 *      aes-ccm256      256-bit AES-CCM
 *      chacha20-poly1305 Chacha20 (96 bit nonce) with Poly1305 per RFC 8439
 */

#include <sys/param.h>
#include <sys/sysctl.h>
#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <libutil.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <openssl/err.h>
#include <openssl/hmac.h>

#include <crypto/cryptodev.h>

struct ocf_session {
        int fd;
        int ses;
        int crid;
};

static const struct alg {
        const char *name;
        int cipher;
        int mac;
        enum { T_HASH, T_HMAC, T_GMAC, T_DIGEST, T_CIPHER, T_ETA, T_AEAD } type;
        int key_len;
        int tag_len;
        u_int iv_sizes[8];
        const EVP_CIPHER *(*evp_cipher)(void);
        const EVP_MD *(*evp_md)(void);
        int pkey;
} algs[] = {
        { .name = "ripemd160", .mac = CRYPTO_RIPEMD160, .type = T_HASH,
          .evp_md = EVP_ripemd160 },
        { .name = "sha1", .mac = CRYPTO_SHA1, .type = T_HASH,
          .evp_md = EVP_sha1 },
        { .name = "sha224", .mac = CRYPTO_SHA2_224, .type = T_HASH,
          .evp_md = EVP_sha224 },
        { .name = "sha256", .mac = CRYPTO_SHA2_256, .type = T_HASH,
          .evp_md = EVP_sha256 },
        { .name = "sha384", .mac = CRYPTO_SHA2_384, .type = T_HASH,
          .evp_md = EVP_sha384 },
        { .name = "sha512", .mac = CRYPTO_SHA2_512, .type = T_HASH,
          .evp_md = EVP_sha512 },
        { .name = "ripemd160hmac", .mac = CRYPTO_RIPEMD160_HMAC, .type = T_HMAC,
          .evp_md = EVP_ripemd160 },
        { .name = "sha1hmac", .mac = CRYPTO_SHA1_HMAC, .type = T_HMAC,
          .evp_md = EVP_sha1 },
        { .name = "sha224hmac", .mac = CRYPTO_SHA2_224_HMAC, .type = T_HMAC,
          .evp_md = EVP_sha224 },
        { .name = "sha256hmac", .mac = CRYPTO_SHA2_256_HMAC, .type = T_HMAC,
          .evp_md = EVP_sha256 },
        { .name = "sha384hmac", .mac = CRYPTO_SHA2_384_HMAC, .type = T_HMAC,
          .evp_md = EVP_sha384 },
        { .name = "sha512hmac", .mac = CRYPTO_SHA2_512_HMAC, .type = T_HMAC,
          .evp_md = EVP_sha512 },
        { .name = "blake2b", .mac = CRYPTO_BLAKE2B, .type = T_HASH,
          .evp_md = EVP_blake2b512 },
        { .name = "blake2s", .mac = CRYPTO_BLAKE2S, .type = T_HASH,
          .evp_md = EVP_blake2s256 },
        { .name = "gmac128", .mac = CRYPTO_AES_NIST_GMAC, .type = T_GMAC,
          .tag_len = AES_GMAC_HASH_LEN, .evp_cipher = EVP_aes_128_gcm },
        { .name = "gmac192", .mac = CRYPTO_AES_NIST_GMAC, .type = T_GMAC,
          .tag_len = AES_GMAC_HASH_LEN, .evp_cipher = EVP_aes_192_gcm },
        { .name = "gmac256", .mac = CRYPTO_AES_NIST_GMAC, .type = T_GMAC,
          .tag_len = AES_GMAC_HASH_LEN, .evp_cipher = EVP_aes_256_gcm },
        { .name = "poly1305", .mac = CRYPTO_POLY1305, .type = T_DIGEST,
          .key_len = POLY1305_KEY_LEN, .pkey = EVP_PKEY_POLY1305 },
        { .name = "aes-cbc128", .cipher = CRYPTO_AES_CBC, .type = T_CIPHER,
          .evp_cipher = EVP_aes_128_cbc },
        { .name = "aes-cbc192", .cipher = CRYPTO_AES_CBC, .type = T_CIPHER,
          .evp_cipher = EVP_aes_192_cbc },
        { .name = "aes-cbc256", .cipher = CRYPTO_AES_CBC, .type = T_CIPHER,
          .evp_cipher = EVP_aes_256_cbc },
        { .name = "aes-ctr128", .cipher = CRYPTO_AES_ICM, .type = T_CIPHER,
          .evp_cipher = EVP_aes_128_ctr },
        { .name = "aes-ctr192", .cipher = CRYPTO_AES_ICM, .type = T_CIPHER,
          .evp_cipher = EVP_aes_192_ctr },
        { .name = "aes-ctr256", .cipher = CRYPTO_AES_ICM, .type = T_CIPHER,
          .evp_cipher = EVP_aes_256_ctr },
        { .name = "aes-xts128", .cipher = CRYPTO_AES_XTS, .type = T_CIPHER,
          .evp_cipher = EVP_aes_128_xts },
        { .name = "aes-xts256", .cipher = CRYPTO_AES_XTS, .type = T_CIPHER,
          .evp_cipher = EVP_aes_256_xts },
        { .name = "camellia-cbc128", .cipher = CRYPTO_CAMELLIA_CBC,
          .type = T_CIPHER, .evp_cipher = EVP_camellia_128_cbc },
        { .name = "camellia-cbc192", .cipher = CRYPTO_CAMELLIA_CBC,
          .type = T_CIPHER, .evp_cipher = EVP_camellia_192_cbc },
        { .name = "camellia-cbc256", .cipher = CRYPTO_CAMELLIA_CBC,
          .type = T_CIPHER, .evp_cipher = EVP_camellia_256_cbc },
        { .name = "chacha20", .cipher = CRYPTO_CHACHA20, .type = T_CIPHER,
          .evp_cipher = EVP_chacha20 },
        { .name = "aes-gcm128", .cipher = CRYPTO_AES_NIST_GCM_16,
          .type = T_AEAD, .tag_len = AES_GMAC_HASH_LEN,
          .iv_sizes = { AES_GCM_IV_LEN }, .evp_cipher = EVP_aes_128_gcm },
        { .name = "aes-gcm192", .cipher = CRYPTO_AES_NIST_GCM_16,
          .type = T_AEAD, .tag_len = AES_GMAC_HASH_LEN,
          .iv_sizes = { AES_GCM_IV_LEN }, .evp_cipher = EVP_aes_192_gcm },
        { .name = "aes-gcm256", .cipher = CRYPTO_AES_NIST_GCM_16,
          .type = T_AEAD, .tag_len = AES_GMAC_HASH_LEN,
          .iv_sizes = { AES_GCM_IV_LEN }, .evp_cipher = EVP_aes_256_gcm },
        { .name = "aes-ccm128", .cipher = CRYPTO_AES_CCM_16, .type = T_AEAD,
          .tag_len = AES_CBC_MAC_HASH_LEN, .iv_sizes = { 12, 7, 8, 9, 10, 11, 13 },
          .evp_cipher = EVP_aes_128_ccm },
        { .name = "aes-ccm192", .cipher = CRYPTO_AES_CCM_16, .type = T_AEAD,
          .tag_len = AES_CBC_MAC_HASH_LEN, .iv_sizes = { 12, 7, 8, 9, 10, 11, 13 },
          .evp_cipher = EVP_aes_192_ccm },
        { .name = "aes-ccm256", .cipher = CRYPTO_AES_CCM_16, .type = T_AEAD,
          .tag_len = AES_CBC_MAC_HASH_LEN, .iv_sizes = { 12, 7, 8, 9, 10, 11, 13 },
          .evp_cipher = EVP_aes_256_ccm },
        { .name = "chacha20-poly1305", .cipher = CRYPTO_CHACHA20_POLY1305,
          .type = T_AEAD, .tag_len = POLY1305_HASH_LEN,
          .iv_sizes = { CHACHA20_POLY1305_IV_LEN },
          .evp_cipher = EVP_chacha20_poly1305 },
};

static bool testall, verbose;
static int requested_crid;
static size_t aad_sizes[48], sizes[EALG_MAX_BLOCK_LEN * 2];
static u_int naad_sizes, nsizes;
static u_int iv_size;

static void
usage(void)
{
        fprintf(stderr,
            "usage: cryptocheck [-vz] [-A aad size] [-a algorithm]\n"
            "                   [-d dev] [-I IV size] [size ...]\n");
        exit(1);
}

static const struct alg *
find_alg(const char *name)
{
        u_int i;

        for (i = 0; i < nitems(algs); i++)
                if (strcasecmp(algs[i].name, name) == 0)
                        return (&algs[i]);
        return (NULL);
}

static struct alg *
build_eta(const struct alg *cipher, const struct alg *mac)
{
        struct alg *eta;
        char *name;

        assert(cipher->type == T_CIPHER);
        assert(mac->type == T_HMAC);
        eta = calloc(1, sizeof(*eta));
        asprintf(&name, "%s+%s", cipher->name, mac->name);
        eta->name = name;
        eta->cipher = cipher->cipher;
        eta->mac = mac->mac;
        eta->type = T_ETA;
        eta->evp_cipher = cipher->evp_cipher;
        eta->evp_md = mac->evp_md;
        return (eta);
}

static void
free_eta(struct alg *eta)
{
        free(__DECONST(char *, eta->name));
        free(eta);
}

static struct alg *
build_eta_name(const char *name)
{
        const struct alg *cipher, *mac;
        const char *mac_name;
        char *cp, *cipher_name;

        cp = strchr(name, '+');
        cipher_name = strndup(name, cp - name);
        mac_name = cp + 1;
        cipher = find_alg(cipher_name);
        free(cipher_name);
        if (cipher == NULL || cipher->type != T_CIPHER)
                errx(1, "Invalid cipher %s", cipher_name);
        mac = find_alg(mac_name);
        if (mac == NULL || mac->type != T_HMAC)
                errx(1, "Invalid hmac %s", mac_name);
        return (build_eta(cipher, mac));
}

static int
devcrypto(void)
{
        static int fd = -1;

        if (fd < 0) {
                fd = open("/dev/crypto", O_RDWR | O_CLOEXEC, 0);
                if (fd < 0)
                        err(1, "/dev/crypto");
        }
        return (fd);
}

/*
 * Called on exit to change kern.cryptodevallowsoft back to 0
 */
#define CRYPT_SOFT_ALLOW        "kern.cryptodevallowsoft"

static void
reset_user_soft(void)
{
        int off = 0;
        sysctlbyname(CRYPT_SOFT_ALLOW, NULL, NULL, &off, sizeof(off));
}

static void
enable_user_soft(void)
{
        int curstate;
        int on = 1;
        size_t cursize = sizeof(curstate);

        if (sysctlbyname(CRYPT_SOFT_ALLOW, &curstate, &cursize,
            &on, sizeof(on)) == 0) {
                if (curstate == 0)
                        atexit(reset_user_soft);
        } else {
                err(1, "sysctl(%s)", CRYPT_SOFT_ALLOW);
        }
}

static int
crlookup(const char *devname)
{
        struct crypt_find_op find;

        if (strncmp(devname, "soft", 4) == 0 ||
            strncmp(devname, "ossl", 4) == 0 ||
            strncmp(devname, "aesni", 5) == 0 ||
            strncmp(devname, "armv8crypto", 11) == 0) {
                enable_user_soft();
                return CRYPTO_FLAG_SOFTWARE;
        }

        find.crid = -1;
        strlcpy(find.name, devname, sizeof(find.name));
        if (ioctl(devcrypto(), CIOCFINDDEV, &find) == -1)
                err(1, "ioctl(CIOCFINDDEV)");
        return (find.crid);
}

static const char *
crfind(int crid)
{
        static struct crypt_find_op find;

        if (crid == CRYPTO_FLAG_SOFTWARE)
                return ("soft");
        else if (crid == CRYPTO_FLAG_HARDWARE)
                return ("unknown");

        bzero(&find, sizeof(find));
        find.crid = crid;
        if (ioctl(devcrypto(), CIOCFINDDEV, &find) == -1)
                err(1, "ioctl(CIOCFINDDEV): crid %d", crid);
        return (find.name);
}

static char
rdigit(void)
{
        const char a[] = {
                0x10,0x54,0x11,0x48,0x45,0x12,0x4f,0x13,0x49,0x53,0x14,0x41,
                0x15,0x16,0x4e,0x55,0x54,0x17,0x18,0x4a,0x4f,0x42,0x19,0x01
        };
        return 0x20+a[random()%nitems(a)];
}

static char *
alloc_buffer(size_t len)
{
        char *buf;
        size_t i;

        buf = malloc(len);
        for (i = 0; i < len; i++)
                buf[i] = rdigit();
        return (buf);
}

static char *
generate_iv(size_t len, const struct alg *alg)
{
        char *iv;

        iv = alloc_buffer(len);
        switch (alg->cipher) {
        case CRYPTO_AES_ICM:
                /* Clear the low 32 bits of the IV to hold the counter. */
                iv[len - 4] = 0;
                iv[len - 3] = 0;
                iv[len - 2] = 0;
                iv[len - 1] = 0;
                break;
        case CRYPTO_AES_XTS:
                /*
                 * Clear the low 64-bits to only store a 64-bit block
                 * number.
                 */
                iv[len - 8] = 0;
                iv[len - 7] = 0;
                iv[len - 6] = 0;
                iv[len - 5] = 0;
                iv[len - 4] = 0;
                iv[len - 3] = 0;
                iv[len - 2] = 0;
                iv[len - 1] = 0;
                break;
        }
        return (iv);
}

static void
ocf_init_sop(struct session2_op *sop)
{
        memset(sop, 0, sizeof(*sop));
        sop->crid = requested_crid;
}

static bool
ocf_init_session(struct session2_op *sop, const char *type, const char *name,
    struct ocf_session *ses)
{
        int fd;

        fd = devcrypto();
        if (ioctl(fd, CIOCGSESSION2, sop) < 0) {
                warn("cryptodev %s %s not supported for device %s",
                    type, name, crfind(sop->crid));
                ses->fd = -1;
                return (false);
        }
        ses->fd = fd;
        ses->ses = sop->ses;
        ses->crid = sop->crid;
        return (true);
}

static void
ocf_destroy_session(struct ocf_session *ses)
{
        if (ses->fd == -1)
                return;

        if (ioctl(ses->fd, CIOCFSESSION, &ses->ses) < 0)
                warn("ioctl(CIOCFSESSION)");
}

static void
ocf_init_cop(const struct ocf_session *ses, struct crypt_op *cop)
{
        memset(cop, 0, sizeof(*cop));
        cop->ses = ses->ses;
}

static void
ocf_init_caead(const struct ocf_session *ses, struct crypt_aead *caead)
{
        memset(caead, 0, sizeof(*caead));
        caead->ses = ses->ses;
}

static bool
ocf_hash(const struct alg *alg, const char *buffer, size_t size, char *digest,
    int *cridp)
{
        struct ocf_session ses;
        struct session2_op sop;
        struct crypt_op cop;

        ocf_init_sop(&sop);
        sop.mac = alg->mac;
        if (!ocf_init_session(&sop, "HASH", alg->name, &ses))
                return (false);

        ocf_init_cop(&ses, &cop);
        cop.op = 0;
        cop.len = size;
        cop.src = buffer;
        cop.mac = digest;

        if (ioctl(ses.fd, CIOCCRYPT, &cop) < 0) {
                warn("cryptodev %s (%zu) HASH failed for device %s", alg->name,
                    size, crfind(ses.crid));
                ocf_destroy_session(&ses);
                return (false);
        }

        *cridp = ses.crid;
        ocf_destroy_session(&ses);
        return (true);
}

static bool
openssl_hash(const struct alg *alg, const EVP_MD *md, const void *buffer,
    size_t size, void *digest_out, unsigned *digest_sz_out)
{
        EVP_MD_CTX *mdctx;
        const char *errs;
        int rc;

        errs = "";

        mdctx = EVP_MD_CTX_create();
        if (mdctx == NULL)
                goto err_out;

        rc = EVP_DigestInit_ex(mdctx, md, NULL);
        if (rc != 1)
                goto err_out;

        rc = EVP_DigestUpdate(mdctx, buffer, size);
        if (rc != 1)
                goto err_out;

        rc = EVP_DigestFinal_ex(mdctx, digest_out, digest_sz_out);
        if (rc != 1)
                goto err_out;

        EVP_MD_CTX_destroy(mdctx);
        return (true);

err_out:
        warnx("OpenSSL %s HASH failed%s: %s", alg->name, errs,
            ERR_error_string(ERR_get_error(), NULL));
        return (false);
}

static void
run_hash_test(const struct alg *alg, size_t size)
{
        const EVP_MD *md;
        char *buffer;
        u_int digest_len;
        int crid;
        char control_digest[EVP_MAX_MD_SIZE], test_digest[EVP_MAX_MD_SIZE];

        memset(control_digest, 0x3c, sizeof(control_digest));
        memset(test_digest, 0x3c, sizeof(test_digest));

        md = alg->evp_md();
        assert((size_t)EVP_MD_size(md) <= sizeof(control_digest));

        buffer = alloc_buffer(size);

        /* OpenSSL HASH. */
        digest_len = sizeof(control_digest);
        if (!openssl_hash(alg, md, buffer, size, control_digest, &digest_len))
                goto out;

        /* cryptodev HASH. */
        if (!ocf_hash(alg, buffer, size, test_digest, &crid))
                goto out;
        if (memcmp(control_digest, test_digest, sizeof(control_digest)) != 0) {
                if (memcmp(control_digest, test_digest, EVP_MD_size(md)) == 0)
                        printf("%s (%zu) mismatch in trailer:\n",
                            alg->name, size);
                else
                        printf("%s (%zu) mismatch:\n", alg->name, size);
                printf("control:\n");
                hexdump(control_digest, sizeof(control_digest), NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(crid));
                hexdump(test_digest, sizeof(test_digest), NULL, 0);
                goto out;
        }

        if (verbose)
                printf("%s (%zu) matched (cryptodev device %s)\n",
                    alg->name, size, crfind(crid));

out:
        free(buffer);
}

static bool
ocf_hmac(const struct alg *alg, const char *buffer, size_t size,
    const char *key, size_t key_len, char *digest, int *cridp)
{
        struct ocf_session ses;
        struct session2_op sop;
        struct crypt_op cop;

        ocf_init_sop(&sop);
        sop.mackeylen = key_len;
        sop.mackey = key;
        sop.mac = alg->mac;
        if (!ocf_init_session(&sop, "HMAC", alg->name, &ses))
                return (false);

        ocf_init_cop(&ses, &cop);
        cop.op = 0;
        cop.len = size;
        cop.src = buffer;
        cop.mac = digest;

        if (ioctl(ses.fd, CIOCCRYPT, &cop) < 0) {
                warn("cryptodev %s (%zu) HMAC failed for device %s", alg->name,
                    size, crfind(ses.crid));
                ocf_destroy_session(&ses);
                return (false);
        }

        *cridp = ses.crid;
        ocf_destroy_session(&ses);
        return (true);
}

static void
run_hmac_test(const struct alg *alg, size_t size)
{
        const EVP_MD *md;
        char *key, *buffer;
        u_int key_len, digest_len;
        int crid;
        char control_digest[EVP_MAX_MD_SIZE], test_digest[EVP_MAX_MD_SIZE];

        memset(control_digest, 0x3c, sizeof(control_digest));
        memset(test_digest, 0x3c, sizeof(test_digest));

        md = alg->evp_md();
        key_len = EVP_MD_size(md);
        assert((size_t)EVP_MD_size(md) <= sizeof(control_digest));

        key = alloc_buffer(key_len);
        buffer = alloc_buffer(size);

        /* OpenSSL HMAC. */
        digest_len = sizeof(control_digest);
        if (HMAC(md, key, key_len, (u_char *)buffer, size,
            (u_char *)control_digest, &digest_len) == NULL) {
                warnx("OpenSSL %s (%zu) HMAC failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto out;
        }

        /* cryptodev HMAC. */
        if (!ocf_hmac(alg, buffer, size, key, key_len, test_digest, &crid))
                goto out;
        if (memcmp(control_digest, test_digest, sizeof(control_digest)) != 0) {
                if (memcmp(control_digest, test_digest, EVP_MD_size(md)) == 0)
                        printf("%s (%zu) mismatch in trailer:\n",
                            alg->name, size);
                else
                        printf("%s (%zu) mismatch:\n", alg->name, size);
                printf("control:\n");
                hexdump(control_digest, sizeof(control_digest), NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(crid));
                hexdump(test_digest, sizeof(test_digest), NULL, 0);
                goto out;
        }

        if (verbose)
                printf("%s (%zu) matched (cryptodev device %s)\n",
                    alg->name, size, crfind(crid));

out:
        free(buffer);
        free(key);
}

static bool
openssl_cipher(const struct alg *alg, const EVP_CIPHER *cipher, const char *key,
    const char *iv, const char *input, char *output, size_t size, int enc)
{
        EVP_CIPHER_CTX *ctx;
        int outl, total;

        ctx = EVP_CIPHER_CTX_new();
        if (ctx == NULL) {
                warnx("OpenSSL %s (%zu) ctx new failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                return (false);
        }
        if (EVP_CipherInit_ex(ctx, cipher, NULL, (const u_char *)key,
            (const u_char *)iv, enc) != 1) {
                warnx("OpenSSL %s (%zu) ctx init failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        EVP_CIPHER_CTX_set_padding(ctx, 0);
        if (EVP_CipherUpdate(ctx, (u_char *)output, &outl,
            (const u_char *)input, size) != 1) {
                warnx("OpenSSL %s (%zu) cipher update failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        total = outl;
        if (EVP_CipherFinal_ex(ctx, (u_char *)output + outl, &outl) != 1) {
                warnx("OpenSSL %s (%zu) cipher final failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        total += outl;
        if ((size_t)total != size) {
                warnx("OpenSSL %s (%zu) cipher size mismatch: %d", alg->name,
                    size, total);
                goto error;
        }
        EVP_CIPHER_CTX_free(ctx);
        return (true);

error:
        EVP_CIPHER_CTX_free(ctx);
        return (false);
}

static bool
ocf_init_cipher_session(const struct alg *alg, const char *key, size_t key_len,
    struct ocf_session *ses)
{
        struct session2_op sop;

        ocf_init_sop(&sop);
        sop.keylen = key_len;
        sop.key = key;
        sop.cipher = alg->cipher;
        return (ocf_init_session(&sop, "cipher", alg->name, ses));
}

static bool
ocf_cipher(const struct ocf_session *ses, const struct alg *alg, const char *iv,
    const char *input, char *output, size_t size, int op)
{
        struct crypt_op cop;

        ocf_init_cop(ses, &cop);
        cop.op = op;
        cop.len = size;
        cop.src = input;
        cop.dst = output;
        cop.iv = iv;

        if (ioctl(ses->fd, CIOCCRYPT, &cop) < 0) {
                warn("cryptodev %s (%zu) cipher failed for device %s",
                    alg->name, size, crfind(ses->crid));
                return (false);
        }

        return (true);
}

static void
run_cipher_test(const struct alg *alg, size_t size)
{
        struct ocf_session ses;
        const EVP_CIPHER *cipher;
        char *buffer, *cleartext, *ciphertext;
        char *iv, *key;
        u_int iv_len, key_len;

        cipher = alg->evp_cipher();
        if (size % EVP_CIPHER_block_size(cipher) != 0) {
                if (verbose)
                        printf(
                            "%s (%zu): invalid buffer size (block size %d)\n",
                            alg->name, size, EVP_CIPHER_block_size(cipher));
                return;
        }

        /*
         * XTS requires at least one full block so that any partial
         * block at the end has cipher text to steal.  Hardcoding the
         * AES block size isn't ideal, but OpenSSL doesn't have a
         * notion of a "native" block size.
         */
        if (EVP_CIPHER_mode(cipher) == EVP_CIPH_XTS_MODE &&
            size < AES_BLOCK_LEN) {
                if (verbose)
                        printf("%s (%zu): invalid buffer size\n", alg->name,
                            size);
                return;
        }

        key_len = EVP_CIPHER_key_length(cipher);
        iv_len = EVP_CIPHER_iv_length(cipher);

        key = alloc_buffer(key_len);
        iv = generate_iv(iv_len, alg);
        cleartext = alloc_buffer(size);
        buffer = malloc(size);
        ciphertext = malloc(size);

        /* OpenSSL cipher. */
        if (!openssl_cipher(alg, cipher, key, iv, cleartext, ciphertext, size,
            1))
                goto out_noocf;
        if (size > 0 && memcmp(cleartext, ciphertext, size) == 0) {
                warnx("OpenSSL %s (%zu): cipher text unchanged", alg->name,
                    size);
                goto out_noocf;
        }
        if (!openssl_cipher(alg, cipher, key, iv, ciphertext, buffer, size, 0))
                goto out_noocf;
        if (memcmp(cleartext, buffer, size) != 0) {
                printf("OpenSSL %s (%zu): cipher mismatch:", alg->name, size);
                printf("original:\n");
                hexdump(cleartext, size, NULL, 0);
                printf("decrypted:\n");
                hexdump(buffer, size, NULL, 0);
                goto out_noocf;
        }

        if (!ocf_init_cipher_session(alg, key, key_len, &ses))
                goto out_noocf;

        /* OCF encrypt. */
        if (!ocf_cipher(&ses, alg, iv, cleartext, buffer, size, COP_ENCRYPT))
                goto out;
        if (memcmp(ciphertext, buffer, size) != 0) {
                printf("%s (%zu) encryption mismatch:\n", alg->name, size);
                printf("control:\n");
                hexdump(ciphertext, size, NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(ses.crid));
                hexdump(buffer, size, NULL, 0);
                goto out;
        }

        /* OCF decrypt. */
        if (!ocf_cipher(&ses, alg, iv, ciphertext, buffer, size, COP_DECRYPT))
                goto out;
        if (memcmp(cleartext, buffer, size) != 0) {
                printf("%s (%zu) decryption mismatch:\n", alg->name, size);
                printf("control:\n");
                hexdump(cleartext, size, NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(ses.crid));
                hexdump(buffer, size, NULL, 0);
                goto out;
        }

        if (verbose)
                printf("%s (%zu) matched (cryptodev device %s)\n",
                    alg->name, size, crfind(ses.crid));

out:
        ocf_destroy_session(&ses);
out_noocf:
        free(ciphertext);
        free(buffer);
        free(cleartext);
        free(iv);
        free(key);
}

static bool
ocf_init_eta_session(const struct alg *alg, const char *cipher_key,
    size_t cipher_key_len, const char *auth_key, size_t auth_key_len,
    struct ocf_session *ses)
{
        struct session2_op sop;

        ocf_init_sop(&sop);
        sop.keylen = cipher_key_len;
        sop.key = cipher_key;
        sop.cipher = alg->cipher;
        sop.mackeylen = auth_key_len;
        sop.mackey = auth_key;
        sop.mac = alg->mac;
        return (ocf_init_session(&sop, "ETA", alg->name, ses));
}

static int
ocf_eta(const struct ocf_session *ses, const char *iv, size_t iv_len,
    const char *aad, size_t aad_len, const char *input, char *output,
    size_t size, char *digest, int op)
{
        int ret;

        if (aad_len != 0) {
                struct crypt_aead caead;

                ocf_init_caead(ses, &caead);
                caead.op = op;
                caead.len = size;
                caead.aadlen = aad_len;
                caead.ivlen = iv_len;
                caead.src = input;
                caead.dst = output;
                caead.aad = aad;
                caead.tag = digest;
                caead.iv = iv;

                ret = ioctl(ses->fd, CIOCCRYPTAEAD, &caead);
        } else {
                struct crypt_op cop;

                ocf_init_cop(ses, &cop);
                cop.op = op;
                cop.len = size;
                cop.src = input;
                cop.dst = output;
                cop.mac = digest;
                cop.iv = iv;

                ret = ioctl(ses->fd, CIOCCRYPT, &cop);
        }

        if (ret < 0)
                return (errno);
        return (0);
}

static void
run_eta_test(const struct alg *alg, size_t aad_len, size_t size)
{
        struct ocf_session ses;
        const EVP_CIPHER *cipher;
        const EVP_MD *md;
        char *buffer, *cleartext, *ciphertext;
        char *iv, *auth_key, *cipher_key;
        u_int iv_len, auth_key_len, cipher_key_len, digest_len;
        int error;
        char control_digest[EVP_MAX_MD_SIZE], test_digest[EVP_MAX_MD_SIZE];

        cipher = alg->evp_cipher();
        if (size % EVP_CIPHER_block_size(cipher) != 0) {
                if (verbose)
                        printf(
                    "%s (%zu, %zu): invalid buffer size (block size %d)\n",
                            alg->name, aad_len, size,
                            EVP_CIPHER_block_size(cipher));
                return;
        }

        /* See comment in run_cipher_test. */
        if (EVP_CIPHER_mode(cipher) == EVP_CIPH_XTS_MODE &&
            size < AES_BLOCK_LEN) {
                if (verbose)
                        printf("%s (%zu): invalid buffer size\n", alg->name,
                            size);
                return;
        }

        memset(control_digest, 0x3c, sizeof(control_digest));
        memset(test_digest, 0x3c, sizeof(test_digest));

        md = alg->evp_md();

        cipher_key_len = EVP_CIPHER_key_length(cipher);
        iv_len = EVP_CIPHER_iv_length(cipher);
        auth_key_len = EVP_MD_size(md);

        cipher_key = alloc_buffer(cipher_key_len);
        iv = generate_iv(iv_len, alg);
        auth_key = alloc_buffer(auth_key_len);
        cleartext = alloc_buffer(aad_len + size);
        buffer = malloc(aad_len + size);
        ciphertext = malloc(aad_len + size);

        /* OpenSSL encrypt + HMAC. */
        if (aad_len != 0)
                memcpy(ciphertext, cleartext, aad_len);
        if (!openssl_cipher(alg, cipher, cipher_key, iv, cleartext + aad_len,
            ciphertext + aad_len, size, 1))
                goto out_noocf;
        if (size > 0 && memcmp(cleartext + aad_len, ciphertext + aad_len,
            size) == 0) {
                warnx("OpenSSL %s (%zu, %zu): cipher text unchanged",
                    alg->name, aad_len, size);
                goto out_noocf;
        }
        digest_len = sizeof(control_digest);
        if (HMAC(md, auth_key, auth_key_len, (u_char *)ciphertext,
            aad_len + size, (u_char *)control_digest, &digest_len) == NULL) {
                warnx("OpenSSL %s (%zu, %zu) HMAC failed: %s", alg->name,
                    aad_len, size, ERR_error_string(ERR_get_error(), NULL));
                goto out_noocf;
        }

        if (!ocf_init_eta_session(alg, cipher_key, cipher_key_len, auth_key,
            auth_key_len, &ses))
                goto out_noocf;

        /* OCF encrypt + HMAC. */
        error = ocf_eta(&ses, iv, iv_len, aad_len != 0 ? cleartext : NULL,
            aad_len, cleartext + aad_len, buffer + aad_len, size, test_digest,
            COP_ENCRYPT);
        if (error != 0) {
                warnc(error, "cryptodev %s (%zu, %zu) ETA failed for device %s",
                    alg->name, aad_len, size, crfind(ses.crid));
                goto out;
        }
        if (memcmp(ciphertext + aad_len, buffer + aad_len, size) != 0) {
                printf("%s (%zu, %zu) encryption mismatch:\n", alg->name,
                    aad_len, size);
                printf("control:\n");
                hexdump(ciphertext + aad_len, size, NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(ses.crid));
                hexdump(buffer + aad_len, size, NULL, 0);
                goto out;
        }
        if (memcmp(control_digest, test_digest, sizeof(control_digest)) != 0) {
                if (memcmp(control_digest, test_digest, EVP_MD_size(md)) == 0)
                        printf("%s (%zu, %zu) enc hash mismatch in trailer:\n",
                            alg->name, aad_len, size);
                else
                        printf("%s (%zu, %zu) enc hash mismatch:\n", alg->name,
                            aad_len, size);
                printf("control:\n");
                hexdump(control_digest, sizeof(control_digest), NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(ses.crid));
                hexdump(test_digest, sizeof(test_digest), NULL, 0);
                goto out;
        }

        /* OCF HMAC + decrypt. */
        error = ocf_eta(&ses, iv, iv_len, aad_len != 0 ? ciphertext : NULL,
            aad_len, ciphertext + aad_len, buffer + aad_len, size, test_digest,
            COP_DECRYPT);
        if (error != 0) {
                warnc(error, "cryptodev %s (%zu, %zu) ETA failed for device %s",
                    alg->name, aad_len, size, crfind(ses.crid));
                goto out;
        }
        if (memcmp(cleartext + aad_len, buffer + aad_len, size) != 0) {
                printf("%s (%zu, %zu) decryption mismatch:\n", alg->name,
                    aad_len, size);
                printf("control:\n");
                hexdump(cleartext, size, NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(ses.crid));
                hexdump(buffer, size, NULL, 0);
                goto out;
        }

        /* Verify OCF HMAC + decrypt fails with busted MAC. */
        test_digest[0] ^= 0x1;
        error = ocf_eta(&ses, iv, iv_len, aad_len != 0 ? ciphertext : NULL,
            aad_len, ciphertext + aad_len, buffer + aad_len, size, test_digest,
            COP_DECRYPT);
        if (error != EBADMSG) {
                if (error != 0)
                        warnc(error,
                    "cryptodev %s (%zu, %zu) corrupt tag failed for device %s",
                            alg->name, aad_len, size, crfind(ses.crid));
                else
                        warnx(
            "cryptodev %s (%zu, %zu) corrupt tag didn't fail for device %s",
                            alg->name, aad_len, size, crfind(ses.crid));
                goto out;
        }

        if (verbose)
                printf("%s (%zu, %zu) matched (cryptodev device %s)\n",
                    alg->name, aad_len, size, crfind(ses.crid));

out:
        ocf_destroy_session(&ses);
out_noocf:
        free(ciphertext);
        free(buffer);
        free(cleartext);
        free(auth_key);
        free(iv);
        free(cipher_key);
}

static bool
openssl_gmac(const struct alg *alg, const EVP_CIPHER *cipher, const char *key,
    const char *iv, const char *input, size_t size, char *tag)
{
        EVP_CIPHER_CTX *ctx;
        int outl;

        ctx = EVP_CIPHER_CTX_new();
        if (ctx == NULL) {
                warnx("OpenSSL %s (%zu) ctx new failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                return (false);
        }
        if (EVP_EncryptInit_ex(ctx, cipher, NULL, (const u_char *)key,
            (const u_char *)iv) != 1) {
                warnx("OpenSSL %s (%zu) ctx init failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        EVP_CIPHER_CTX_set_padding(ctx, 0);
        if (EVP_EncryptUpdate(ctx, NULL, &outl, (const u_char *)input,
            size) != 1) {
                warnx("OpenSSL %s (%zu) update failed: %s",
                    alg->name, size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_EncryptFinal_ex(ctx, NULL, &outl) != 1) {
                warnx("OpenSSL %s (%zu) final failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, alg->tag_len,
            tag) != 1) {
                warnx("OpenSSL %s (%zu) get tag failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        EVP_CIPHER_CTX_free(ctx);
        return (true);

error:
        EVP_CIPHER_CTX_free(ctx);
        return (false);
}

static bool
ocf_mac(const struct alg *alg, const char *input, size_t size, const char *key,
    size_t key_len, const char *iv, char *tag, int *cridp)
{
        struct ocf_session ses;
        struct session2_op sop;
        struct crypt_op cop;

        ocf_init_sop(&sop);
        sop.mackeylen = key_len;
        sop.mackey = key;
        sop.mac = alg->mac;
        if (!ocf_init_session(&sop, "MAC", alg->name, &ses))
                return (false);

        ocf_init_cop(&ses, &cop);
        cop.op = 0;
        cop.len = size;
        cop.src = input;
        cop.mac = tag;
        cop.iv = iv;

        if (ioctl(ses.fd, CIOCCRYPT, &cop) < 0) {
                warn("cryptodev %s (%zu) failed for device %s", alg->name,
                    size, crfind(ses.crid));
                ocf_destroy_session(&ses);
                return (false);
        }

        *cridp = ses.crid;
        ocf_destroy_session(&ses);
        return (true);
}

static void
run_gmac_test(const struct alg *alg, size_t size)
{
        const EVP_CIPHER *cipher;
        char *iv, *key, *buffer;
        u_int iv_len, key_len;
        int crid;
        char control_tag[AES_GMAC_HASH_LEN], test_tag[AES_GMAC_HASH_LEN];

        cipher = alg->evp_cipher();

        memset(control_tag, 0x3c, sizeof(control_tag));
        memset(test_tag, 0x3c, sizeof(test_tag));

        key_len = EVP_CIPHER_key_length(cipher);
        iv_len = EVP_CIPHER_iv_length(cipher);

        key = alloc_buffer(key_len);
        iv = generate_iv(iv_len, alg);
        buffer = alloc_buffer(size);

        /* OpenSSL GMAC. */
        if (!openssl_gmac(alg, cipher, key, iv, buffer, size, control_tag))
                goto out;

        /* OCF GMAC. */
        if (!ocf_mac(alg, buffer, size, key, key_len, iv, test_tag, &crid))
                goto out;
        if (memcmp(control_tag, test_tag, sizeof(control_tag)) != 0) {
                printf("%s (%zu) mismatch:\n", alg->name, size);
                printf("control:\n");
                hexdump(control_tag, sizeof(control_tag), NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(crid));
                hexdump(test_tag, sizeof(test_tag), NULL, 0);
                goto out;
        }

        if (verbose)
                printf("%s (%zu) matched (cryptodev device %s)\n",
                    alg->name, size, crfind(crid));

out:
        free(buffer);
        free(iv);
        free(key);
}

static bool
openssl_digest(const struct alg *alg, const char *key, u_int key_len,
    const char *input, size_t size, char *tag, u_int tag_len)
{
        EVP_MD_CTX *mdctx;
        EVP_PKEY *pkey;
        size_t len;

        pkey = EVP_PKEY_new_raw_private_key(alg->pkey, NULL, key, key_len);
        if (pkey == NULL) {
                warnx("OpenSSL %s (%zu) pkey new failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                return (false);
        }
        mdctx = EVP_MD_CTX_new();
        if (mdctx == NULL) {
                warnx("OpenSSL %s (%zu) ctx new failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                EVP_PKEY_free(pkey);
                return (false);
        }
        if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
                warnx("OpenSSL %s (%zu) digest sign init failed: %s",
                    alg->name, size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_DigestSignUpdate(mdctx, input, size) != 1) {
                warnx("OpenSSL %s (%zu) digest update failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        len = tag_len;
        if (EVP_DigestSignFinal(mdctx, tag, &len) != 1) {
                warnx("OpenSSL %s (%zu) digest final failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        EVP_MD_CTX_free(mdctx);
        EVP_PKEY_free(pkey);
        return (true);

error:
        EVP_MD_CTX_free(mdctx);
        EVP_PKEY_free(pkey);
        return (false);
}

static void
run_digest_test(const struct alg *alg, size_t size)
{
        char *key, *buffer;
        u_int key_len;
        int crid;
        char control_tag[EVP_MAX_MD_SIZE], test_tag[EVP_MAX_MD_SIZE];

        memset(control_tag, 0x3c, sizeof(control_tag));
        memset(test_tag, 0x3c, sizeof(test_tag));

        key_len = alg->key_len;

        key = alloc_buffer(key_len);
        buffer = alloc_buffer(size);

        /* OpenSSL Poly1305. */
        if (!openssl_digest(alg, key, key_len, buffer, size, control_tag,
            sizeof(control_tag)))
                goto out;

        /* OCF Poly1305. */
        if (!ocf_mac(alg, buffer, size, key, key_len, NULL, test_tag, &crid))
                goto out;
        if (memcmp(control_tag, test_tag, sizeof(control_tag)) != 0) {
                printf("%s (%zu) mismatch:\n", alg->name, size);
                printf("control:\n");
                hexdump(control_tag, sizeof(control_tag), NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(crid));
                hexdump(test_tag, sizeof(test_tag), NULL, 0);
                goto out;
        }

        if (verbose)
                printf("%s (%zu) matched (cryptodev device %s)\n",
                    alg->name, size, crfind(crid));

out:
        free(buffer);
        free(key);
}

static bool
openssl_aead_encrypt(const struct alg *alg, const EVP_CIPHER *cipher,
    const char *key, const char *iv, size_t iv_len, const char *aad,
    size_t aad_len, const char *input, char *output, size_t size, char *tag)
{
        EVP_CIPHER_CTX *ctx;
        int outl, total;

        ctx = EVP_CIPHER_CTX_new();
        if (ctx == NULL) {
                warnx("OpenSSL %s (%zu) ctx new failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                return (false);
        }
        if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
                warnx("OpenSSL %s (%zu) ctx init failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, iv_len, NULL) !=
            1) {
                warnx("OpenSSL %s (%zu) setting iv length failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_EncryptInit_ex(ctx, NULL, NULL, (const u_char *)key,
            (const u_char *)iv) != 1) {
                warnx("OpenSSL %s (%zu) ctx init failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        EVP_CIPHER_CTX_set_padding(ctx, 0);
        if (aad != NULL) {
                if (EVP_EncryptUpdate(ctx, NULL, &outl, (const u_char *)aad,
                    aad_len) != 1) {
                        warnx("OpenSSL %s (%zu) aad update failed: %s",
                            alg->name, size,
                            ERR_error_string(ERR_get_error(), NULL));
                        goto error;
                }
        }
        if (EVP_EncryptUpdate(ctx, (u_char *)output, &outl,
            (const u_char *)input, size) != 1) {
                warnx("OpenSSL %s (%zu) encrypt update failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        total = outl;
        if (EVP_EncryptFinal_ex(ctx, (u_char *)output + outl, &outl) != 1) {
                warnx("OpenSSL %s (%zu) encrypt final failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        total += outl;
        if ((size_t)total != size) {
                warnx("OpenSSL %s (%zu) encrypt size mismatch: %d", alg->name,
                    size, total);
                goto error;
        }
        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, alg->tag_len,
            tag) != 1) {
                warnx("OpenSSL %s (%zu) get tag failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        EVP_CIPHER_CTX_free(ctx);
        return (true);

error:
        EVP_CIPHER_CTX_free(ctx);
        return (false);
}

#ifdef notused
static bool
openssl_aead_decrypt(const struct alg *alg, const EVP_CIPHER *cipher,
    const char *key, const char *iv, const char *aad, size_t aad_len,
    const char *input, char *output, size_t size, char *tag)
{
        EVP_CIPHER_CTX *ctx;
        int outl, total;
        bool valid;

        ctx = EVP_CIPHER_CTX_new();
        if (ctx == NULL)
                errx(1, "OpenSSL %s (%zu) ctx new failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
        if (EVP_DecryptInit_ex(ctx, cipher, NULL, (const u_char *)key,
            (const u_char *)iv) != 1)
                errx(1, "OpenSSL %s (%zu) ctx init failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
        EVP_CIPHER_CTX_set_padding(ctx, 0);
        if (aad != NULL) {
                if (EVP_DecryptUpdate(ctx, NULL, &outl, (const u_char *)aad,
                    aad_len) != 1)
                        errx(1, "OpenSSL %s (%zu) aad update failed: %s",
                            alg->name, size,
                            ERR_error_string(ERR_get_error(), NULL));
        }
        if (EVP_DecryptUpdate(ctx, (u_char *)output, &outl,
            (const u_char *)input, size) != 1)
                errx(1, "OpenSSL %s (%zu) decrypt update failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
        total = outl;
        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, alg->tag_len,
            tag) != 1)
                errx(1, "OpenSSL %s (%zu) get tag failed: %s", alg->name,
                    size, ERR_error_string(ERR_get_error(), NULL));
        valid = (EVP_DecryptFinal_ex(ctx, (u_char *)output + outl, &outl) != 1);
        total += outl;
        if (total != size)
                errx(1, "OpenSSL %s (%zu) decrypt size mismatch: %d", alg->name,
                    size, total);
        EVP_CIPHER_CTX_free(ctx);
        return (valid);
}
#endif

static bool
openssl_ccm_encrypt(const struct alg *alg, const EVP_CIPHER *cipher,
    const char *key, const char *iv, size_t iv_len, const char *aad,
    size_t aad_len, const char *input, char *output, size_t size, char *tag)
{
        EVP_CIPHER_CTX *ctx;
        int outl, total;

        ctx = EVP_CIPHER_CTX_new();
        if (ctx == NULL) {
                warnx("OpenSSL %s/%zu (%zu, %zu) ctx new failed: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                return (false);
        }
        if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
                warnx("OpenSSL %s/%zu (%zu, %zu) ctx init failed: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, iv_len, NULL) !=
            1) {
                warnx("OpenSSL %s/%zu (%zu, %zu) setting iv length failed: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, AES_CBC_MAC_HASH_LEN,
            NULL) != 1) {
                warnx("OpenSSL %s/%zu (%zu, %zu) setting tag length failed: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_EncryptInit_ex(ctx, NULL, NULL, (const u_char *)key,
            (const u_char *)iv) != 1) {
                warnx("OpenSSL %s/%zu (%zu, %zu) ctx init failed: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        if (EVP_EncryptUpdate(ctx, NULL, &outl, NULL, size) != 1) {
                warnx("OpenSSL %s/%zu (%zu, %zu) unable to set data length: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }

        if (aad != NULL) {
                if (EVP_EncryptUpdate(ctx, NULL, &outl, (const u_char *)aad,
                    aad_len) != 1) {
                        warnx("OpenSSL %s/%zu (%zu, %zu) aad update failed: %s",
                            alg->name, iv_len, aad_len, size,
                            ERR_error_string(ERR_get_error(), NULL));
                        goto error;
                }
        }
        if (EVP_EncryptUpdate(ctx, (u_char *)output, &outl,
            (const u_char *)input, size) != 1) {
                warnx("OpenSSL %s/%zu (%zu, %zu) encrypt update failed: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        total = outl;
        if (EVP_EncryptFinal_ex(ctx, (u_char *)output + outl, &outl) != 1) {
                warnx("OpenSSL %s/%zu (%zu, %zu) encrypt final failed: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        total += outl;
        if ((size_t)total != size) {
                warnx("OpenSSL %s/%zu (%zu, %zu) encrypt size mismatch: %d",
                    alg->name, iv_len, aad_len, size, total);
                goto error;
        }
        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, AES_CBC_MAC_HASH_LEN,
            tag) != 1) {
                warnx("OpenSSL %s/%zu (%zu, %zu) get tag failed: %s",
                    alg->name, iv_len, aad_len, size,
                    ERR_error_string(ERR_get_error(), NULL));
                goto error;
        }
        EVP_CIPHER_CTX_free(ctx);
        return (true);

error:
        EVP_CIPHER_CTX_free(ctx);
        return (false);
}

static bool
ocf_init_aead_session(const struct alg *alg, const char *key, size_t key_len,
    size_t iv_len, struct ocf_session *ses)
{
        struct session2_op sop;

        ocf_init_sop(&sop);
        sop.keylen = key_len;
        sop.key = key;
        sop.cipher = alg->cipher;
        sop.ivlen = iv_len;
        return (ocf_init_session(&sop, "AEAD", alg->name, ses));
}

static int
ocf_aead(const struct ocf_session *ses, const char *iv, size_t iv_len,
    const char *aad, size_t aad_len, const char *input, char *output,
    size_t size, char *tag, int op)
{
        struct crypt_aead caead;

        ocf_init_caead(ses, &caead);
        caead.op = op;
        caead.len = size;
        caead.aadlen = aad_len;
        caead.ivlen = iv_len;
        caead.src = input;
        caead.dst = output;
        caead.aad = aad;
        caead.tag = tag;
        caead.iv = iv;

        if (ioctl(ses->fd, CIOCCRYPTAEAD, &caead) < 0)
                return (errno);
        return (0);
}

#define AEAD_MAX_TAG_LEN                                \
        MAX(MAX(AES_GMAC_HASH_LEN, AES_CBC_MAC_HASH_LEN), POLY1305_HASH_LEN)

static size_t
max_ccm_buffer_length(size_t iv_len)
{
        const u_int L = 15 - iv_len;

        switch (L) {
        case 2:
                return (0xffff);
        case 3:
                return (0xffffff);
#ifdef __LP64__
        case 4:
                return (0xffffffff);
        case 5:
                return (0xffffffffff);
        case 6:
                return (0xffffffffffff);
        case 7:
                return (0xffffffffffffff);
        default:
                return (0xffffffffffffffff);
#else
        default:
                return (0xffffffff);
#endif
        }
}

static void
run_aead_test(const struct alg *alg, size_t aad_len, size_t size,
    size_t iv_len)
{
        struct ocf_session ses;
        const EVP_CIPHER *cipher;
        char *aad, *buffer, *cleartext, *ciphertext;
        char *iv, *key;
        u_int key_len;
        int error;
        char control_tag[AEAD_MAX_TAG_LEN], test_tag[AEAD_MAX_TAG_LEN];
        bool ok;

        cipher = alg->evp_cipher();
        if (size % EVP_CIPHER_block_size(cipher) != 0) {
                if (verbose)
                        printf(
                    "%s/%zu (%zu, %zu): invalid buffer size (block size %d)\n",
                            alg->name, iv_len, aad_len, size,
                            EVP_CIPHER_block_size(cipher));
                return;
        }

        if (EVP_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE &&
            size > max_ccm_buffer_length(iv_len)) {
                if (verbose)
                        printf("%s/%zu (%zu, %zu): invalid buffer size\n",
                            alg->name, iv_len, aad_len, size);
                return;
        }

        memset(control_tag, 0x3c, sizeof(control_tag));
        memset(test_tag, 0x3c, sizeof(test_tag));

        key_len = EVP_CIPHER_key_length(cipher);

        key = alloc_buffer(key_len);
        iv = generate_iv(iv_len, alg);
        cleartext = alloc_buffer(size);
        buffer = malloc(size);
        ciphertext = malloc(size);
        if (aad_len != 0)
                aad = alloc_buffer(aad_len);
        else
                aad = NULL;

        /* OpenSSL encrypt */
        if (EVP_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE)
                ok = openssl_ccm_encrypt(alg, cipher, key, iv, iv_len, aad,
                    aad_len, cleartext, ciphertext, size, control_tag);
        else
                ok = openssl_aead_encrypt(alg, cipher, key, iv, iv_len, aad,
                    aad_len, cleartext, ciphertext, size, control_tag);
        if (!ok)
                goto out_noocf;

        if (!ocf_init_aead_session(alg, key, key_len, iv_len, &ses))
                goto out_noocf;

        /* OCF encrypt */
        error = ocf_aead(&ses, iv, iv_len, aad, aad_len, cleartext, buffer,
            size, test_tag, COP_ENCRYPT);
        if (error != 0) {
                warnc(error, "cryptodev %s/%zu (%zu, %zu) failed for device %s",
                    alg->name, iv_len, aad_len, size, crfind(ses.crid));
                goto out;
        }
        if (memcmp(ciphertext, buffer, size) != 0) {
                printf("%s/%zu (%zu, %zu) encryption mismatch:\n", alg->name,
                    iv_len, aad_len, size);
                printf("control:\n");
                hexdump(ciphertext, size, NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(ses.crid));
                hexdump(buffer, size, NULL, 0);
                goto out;
        }
        if (memcmp(control_tag, test_tag, sizeof(control_tag)) != 0) {
                printf("%s/%zu (%zu, %zu) enc tag mismatch:\n", alg->name,
                    iv_len, aad_len, size);
                printf("control:\n");
                hexdump(control_tag, sizeof(control_tag), NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(ses.crid));
                hexdump(test_tag, sizeof(test_tag), NULL, 0);
                goto out;
        }

        /* OCF decrypt */
        error = ocf_aead(&ses, iv, iv_len, aad, aad_len, ciphertext,
            buffer, size, control_tag, COP_DECRYPT);
        if (error != 0) {
                warnc(error, "cryptodev %s/%zu (%zu, %zu) failed for device %s",
                    alg->name, iv_len, aad_len, size, crfind(ses.crid));
                goto out;
        }
        if (memcmp(cleartext, buffer, size) != 0) {
                printf("%s/%zu (%zu, %zu) decryption mismatch:\n", alg->name,
                    iv_len, aad_len, size);
                printf("control:\n");
                hexdump(cleartext, size, NULL, 0);
                printf("test (cryptodev device %s):\n", crfind(ses.crid));
                hexdump(buffer, size, NULL, 0);
                goto out;
        }

        /* Verify OCF decrypt fails with busted tag. */
        test_tag[0] ^= 0x1;
        error = ocf_aead(&ses, iv, iv_len, aad, aad_len, ciphertext,
            buffer, size, test_tag, COP_DECRYPT);
        if (error != EBADMSG) {
                if (error != 0)
                        warnc(error,
                    "cryptodev %s/%zu (%zu, %zu) corrupt tag failed for device %s",
                            alg->name, iv_len, aad_len, size, crfind(ses.crid));
                else
                        warnx(
            "cryptodev %s/%zu (%zu, %zu) corrupt tag didn't fail for device %s",
                            alg->name, iv_len, aad_len, size, crfind(ses.crid));
                goto out;
        }

        if (verbose)
                printf("%s/%zu (%zu, %zu) matched (cryptodev device %s)\n",
                    alg->name, iv_len, aad_len, size, crfind(ses.crid));

out:
        ocf_destroy_session(&ses);
out_noocf:
        free(aad);
        free(ciphertext);
        free(buffer);
        free(cleartext);
        free(iv);
        free(key);
}

static void
run_test(const struct alg *alg, size_t aad_len, size_t size, size_t iv_len)
{

        switch (alg->type) {
        case T_HASH:
                run_hash_test(alg, size);
                break;
        case T_HMAC:
                run_hmac_test(alg, size);
                break;
        case T_GMAC:
                run_gmac_test(alg, size);
                break;
        case T_DIGEST:
                run_digest_test(alg, size);
                break;
        case T_CIPHER:
                run_cipher_test(alg, size);
                break;
        case T_ETA:
                run_eta_test(alg, aad_len, size);
                break;
        case T_AEAD:
                run_aead_test(alg, aad_len, size, iv_len);
                break;
        }
}

static void
run_test_sizes(const struct alg *alg)
{
        u_int i, j, k;

        switch (alg->type) {
        default:
                for (i = 0; i < nsizes; i++)
                        run_test(alg, 0, sizes[i], 0);
                break;
        case T_ETA:
                for (i = 0; i < naad_sizes; i++)
                        for (j = 0; j < nsizes; j++)
                                run_test(alg, aad_sizes[i], sizes[j], 0);
                break;
        case T_AEAD:
                for (i = 0; i < naad_sizes; i++) {
                        for (j = 0; j < nsizes; j++) {
                                if (iv_size != 0)
                                        run_test(alg, aad_sizes[i], sizes[j],
                                            iv_size);
                                else if (testall) {
                                        for (k = 0; alg->iv_sizes[k] != 0; k++)
                                                run_test(alg, aad_sizes[i],
                                                    sizes[j], alg->iv_sizes[k]);
                                } else
                                        run_test(alg, aad_sizes[i], sizes[j],
                                            alg->iv_sizes[0]);
                        }
                }
                break;
        }
}

static void
run_hash_tests(void)
{
        u_int i;

        for (i = 0; i < nitems(algs); i++)
                if (algs[i].type == T_HASH)
                        run_test_sizes(&algs[i]);
}

static void
run_mac_tests(void)
{
        u_int i;

        for (i = 0; i < nitems(algs); i++)
                if (algs[i].type == T_HMAC || algs[i].type == T_GMAC ||
                    algs[i].type == T_DIGEST)
                        run_test_sizes(&algs[i]);
}

static void
run_cipher_tests(void)
{
        u_int i;

        for (i = 0; i < nitems(algs); i++)
                if (algs[i].type == T_CIPHER)
                        run_test_sizes(&algs[i]);
}

static void
run_eta_tests(void)
{
        const struct alg *cipher, *mac;
        struct alg *eta;
        u_int i, j;

        for (i = 0; i < nitems(algs); i++) {
                cipher = &algs[i];
                if (cipher->type != T_CIPHER)
                        continue;
                for (j = 0; j < nitems(algs); j++) {
                        mac = &algs[j];
                        if (mac->type != T_HMAC)
                                continue;
                        eta = build_eta(cipher, mac);
                        run_test_sizes(eta);
                        free_eta(eta);
                }
        }
}

static void
run_aead_tests(void)
{
        u_int i;

        for (i = 0; i < nitems(algs); i++)
                if (algs[i].type == T_AEAD)
                        run_test_sizes(&algs[i]);
}

static void
run_prefix_tests(const char *prefix)
{
        size_t prefix_len;
        u_int i;

        prefix_len = strlen(prefix);
        for (i = 0; i < nitems(algs); i++)
                if (strlen(algs[i].name) >= prefix_len &&
                    memcmp(algs[i].name, prefix, prefix_len) == 0)
                        run_test_sizes(&algs[i]);
}

int
main(int ac, char **av)
{
        const char *algname;
        const struct alg *alg;
        struct alg *eta;
        char *cp;
        size_t base_size;
        u_int i;
        int ch;

        algname = NULL;
        requested_crid = CRYPTO_FLAG_HARDWARE;
        testall = false;
        verbose = false;
        iv_size = 0;
        while ((ch = getopt(ac, av, "A:a:d:I:vz")) != -1)
                switch (ch) {
                case 'A':
                        if (naad_sizes >= nitems(aad_sizes)) {
                                warnx("Too many AAD sizes, ignoring extras");
                                break;
                        }
                        aad_sizes[naad_sizes] = strtol(optarg, &cp, 0);
                        if (*cp != '\0')
                                errx(1, "Bad AAD size %s", optarg);
                        naad_sizes++;
                        break;
                case 'a':
                        algname = optarg;
                        break;
                case 'd':
                        requested_crid = crlookup(optarg);
                        break;
                case 'I':
                        iv_size = strtol(optarg, &cp, 0);
                        if (*cp != '\0')
                                errx(1, "Bad IV size %s", optarg);
                        break;
                case 'v':
                        verbose = true;
                        break;
                case 'z':
                        testall = true;
                        break;
                default:
                        usage();
                }
        ac -= optind;
        av += optind;
        nsizes = 0;
        while (ac > 0) {
                if (nsizes >= nitems(sizes)) {
                        warnx("Too many sizes, ignoring extras");
                        break;
                }
                sizes[nsizes] = strtol(av[0], &cp, 0);
                if (*cp != '\0')
                        errx(1, "Bad size %s", av[0]);
                nsizes++;
                ac--;
                av++;
        }

        if (algname == NULL)
                errx(1, "Algorithm required");

        if (naad_sizes == 0) {
                if (testall) {
                        for (i = 0; i <= 32; i++) {
                                aad_sizes[naad_sizes] = i;
                                naad_sizes++;
                        }

                        base_size = 32;
                        while (base_size * 2 < 512) {
                                base_size *= 2;
                                assert(naad_sizes < nitems(aad_sizes));
                                aad_sizes[naad_sizes] = base_size;
                                naad_sizes++;
                        }
                } else {
                        aad_sizes[0] = 0;
                        naad_sizes = 1;
                }
        }

        if (nsizes == 0) {
                if (testall) {
                        for (i = 1; i <= EALG_MAX_BLOCK_LEN; i++) {
                                sizes[nsizes] = i;
                                nsizes++;
                        }

                        for (i = EALG_MAX_BLOCK_LEN + 8;
                             i <= EALG_MAX_BLOCK_LEN * 2; i += 8) {
                                sizes[nsizes] = i;
                                nsizes++;
                        }

                        base_size = EALG_MAX_BLOCK_LEN * 2;
                        while (base_size * 2 < 240 * 1024) {
                                base_size *= 2;
                                assert(nsizes < nitems(sizes));
                                sizes[nsizes] = base_size;
                                nsizes++;
                        }

                        if (sizes[nsizes - 1] < 240 * 1024) {
                                assert(nsizes < nitems(sizes));
                                sizes[nsizes] = 240 * 1024;
                                nsizes++;
                        }
                } else {
                        sizes[0] = 16;
                        nsizes = 1;
                }
        }

        if (strcasecmp(algname, "hash") == 0)
                run_hash_tests();
        else if (strcasecmp(algname, "mac") == 0)
                run_mac_tests();
        else if (strcasecmp(algname, "cipher") == 0)
                run_cipher_tests();
        else if (strcasecmp(algname, "eta") == 0)
                run_eta_tests();
        else if (strcasecmp(algname, "aead") == 0)
                run_aead_tests();
        else if (strcasecmp(algname, "gmac") == 0 ||
            strcasecmp(algname, "aes-cbc") == 0 ||
            strcasecmp(algname, "aes-ctr") == 0 ||
            strcasecmp(algname, "aes-xts") == 0 ||
            strcasecmp(algname, "camellia-cbc") == 0 ||
            strcasecmp(algname, "aes-gcm") == 0 ||
            strcasecmp(algname, "aes-ccm") == 0)
                run_prefix_tests(algname);
        else if (strcasecmp(algname, "all") == 0) {
                run_hash_tests();
                run_mac_tests();
                run_cipher_tests();
                run_eta_tests();
                run_aead_tests();
        } else if (strchr(algname, '+') != NULL) {
                eta = build_eta_name(algname);
                run_test_sizes(eta);
                free_eta(eta);
        } else {
                alg = find_alg(algname);
                if (alg == NULL)
                        errx(1, "Invalid algorithm %s", algname);
                run_test_sizes(alg);
        }

        return (0);
}