/*
 * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com).
 * 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.
 *
 * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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.
 */

#include "rnp_tests.h"
#include "support.h"
#include "rnp.h"
#include "crypto/hash.hpp"
#include "crypto/signatures.h"
#include "pgp-key.h"
#include <time.h>
#include "rnp.h"
#include <librepgp/stream-ctx.h>
#include <librepgp/stream-packet.h>
#include <librepgp/stream-sig.h>
#include <librepgp/stream-key.h>
#include <librepgp/stream-dump.h>
#include <librepgp/stream-armor.h>
#include <librepgp/stream-write.h>
#include <algorithm>
#include "time-utils.h"

static bool
stream_hash_file(rnp::Hash &hash, const char *path)
{
    pgp_source_t src;
    if (init_file_src(&src, path)) {
        return false;
    }

    bool res = false;
    do {
        uint8_t readbuf[1024];
        size_t  read = 0;
        if (!src_read(&src, readbuf, sizeof(readbuf), &read)) {
            goto finish;
        } else if (read == 0) {
            break;
        }
        hash.add(readbuf, read);
    } while (1);

    res = true;
finish:
    src_close(&src);
    return res;
}

TEST_F(rnp_tests, test_stream_memory)
{
    const char *data = "Sample data to test memory streams";
    size_t      datalen;
    pgp_dest_t  memdst;
    void *      mown;
    void *      mcpy;

    datalen = strlen(data) + 1;

    /* populate memory dst and own inner data */
    assert_rnp_success(init_mem_dest(&memdst, NULL, 0));
    assert_rnp_success(memdst.werr);
    dst_write(&memdst, data, datalen);
    assert_rnp_success(memdst.werr);
    assert_int_equal(memdst.writeb, datalen);

    assert_non_null(mcpy = mem_dest_get_memory(&memdst));
    assert_false(memcmp(mcpy, data, datalen));
    assert_non_null(mown = mem_dest_own_memory(&memdst));
    assert_false(memcmp(mown, data, datalen));
    dst_close(&memdst, true);
    /* make sure we own data after close */
    assert_false(memcmp(mown, data, datalen));
    free(mown);
    /* make sure init_mem_src fails with NULL parameter */
    pgp_source_t memsrc;
    assert_rnp_failure(init_mem_src(&memsrc, NULL, 12, false));
    assert_rnp_failure(init_mem_src(&memsrc, NULL, 12, true));
}

TEST_F(rnp_tests, test_stream_memory_discard)
{
    pgp_dest_t   memdst = {};
    char         mem[32];
    const char * hexes = "123456789ABCDEF";
    const size_t hexes_len = 15;

    /* init mem dst and write some data */
    assert_rnp_success(init_mem_dest(&memdst, mem, sizeof(mem)));
    dst_write(&memdst, "Hello", 5);
    assert_int_equal(memdst.writeb, 5);
    dst_write(&memdst, ", ", 2);
    assert_int_equal(memdst.writeb, 7);
    dst_write(&memdst, "world!\n", 7);
    assert_int_equal(memdst.writeb, 14);
    /* Now discard overflowing data and attempt to overflow it */
    mem_dest_discard_overflow(&memdst, true);
    dst_write(&memdst, "Hello, world!\n", 14);
    assert_int_equal(memdst.writeb, 28);
    assert_int_equal(memdst.werr, RNP_SUCCESS);
    dst_write(&memdst, hexes, hexes_len);
    /* While extra data is discarded, writeb is still incremented by hexes_len */
    assert_int_equal(memdst.writeb, 43);
    assert_int_equal(memdst.werr, RNP_SUCCESS);
    assert_int_equal(memcmp(mem, "Hello, world!\nHello, world!\n1234", 32), 0);
    dst_write(&memdst, hexes, hexes_len);
    assert_int_equal(memdst.writeb, 58);
    assert_int_equal(memdst.werr, RNP_SUCCESS);
    assert_int_equal(memcmp(mem, "Hello, world!\nHello, world!\n1234", 32), 0);
    /* Now make sure that error is generated */
    mem_dest_discard_overflow(&memdst, false);
    dst_write(&memdst, hexes, hexes_len);
    assert_int_equal(memdst.writeb, 58);
    assert_int_not_equal(memdst.werr, RNP_SUCCESS);

    dst_close(&memdst, true);
    assert_int_equal(memcmp(mem, "Hello, world!\nHello, world!\n1234", 32), 0);

    /* Now do tests with dynamic memory allocation */
    const size_t bytes = 12345;
    assert_rnp_success(init_mem_dest(&memdst, NULL, bytes));
    for (size_t i = 0; i < bytes / hexes_len; i++) {
        dst_write(&memdst, hexes, hexes_len);
        assert_int_equal(memdst.writeb, (i + 1) * hexes_len);
        assert_int_equal(memdst.werr, RNP_SUCCESS);
    }

    mem_dest_discard_overflow(&memdst, true);
    dst_write(&memdst, hexes, hexes_len);
    assert_int_equal(memdst.writeb, bytes - bytes % hexes_len + hexes_len);
    assert_int_equal(memdst.werr, RNP_SUCCESS);
    mem_dest_discard_overflow(&memdst, false);
    dst_write(&memdst, hexes, hexes_len);
    assert_int_equal(memdst.writeb, bytes - bytes % hexes_len + hexes_len);
    assert_int_not_equal(memdst.werr, RNP_SUCCESS);
    dst_write(&memdst, hexes, hexes_len);
    assert_int_equal(memdst.writeb, bytes - bytes % hexes_len + hexes_len);
    assert_int_not_equal(memdst.werr, RNP_SUCCESS);
    dst_close(&memdst, true);
}

static void
copy_tmp_path(char *buf, size_t buflen, pgp_dest_t *dst)
{
    typedef struct pgp_dest_file_param_t {
        int         fd;
        int         errcode;
        bool        overwrite;
        std::string path;
    } pgp_dest_file_param_t;

    pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
    strncpy(buf, param->path.c_str(), buflen);
}

TEST_F(rnp_tests, test_stream_file)
{
    const char * filename = "dummyfile.dat";
    const char * dirname = "dummydir";
    const char * file2name = "dummydir/dummyfile.dat";
    const char * filedata = "dummy message to be stored in the file";
    const int    iterations = 10000;
    const int    filedatalen = strlen(filedata);
    char         tmpname[128] = {0};
    uint8_t      tmpbuf[1024] = {0};
    pgp_dest_t   dst = {};
    pgp_source_t src = {};

    /* try to read non-existing file */
    assert_rnp_failure(init_file_src(&src, filename));
    assert_rnp_failure(init_file_src(&src, dirname));
    /* create dir */
    assert_int_equal(RNP_MKDIR(dirname, S_IRWXU), 0);
    /* attempt to read or create file in place of directory */
    assert_rnp_failure(init_file_src(&src, dirname));
    assert_rnp_failure(init_file_dest(&dst, dirname, false));
    /* with overwrite flag it must succeed, then delete it */
    assert_rnp_success(init_file_dest(&dst, dirname, true));
    assert_int_equal(file_size(dirname), 0);
    dst_close(&dst, true);
    /* create dir back */
    assert_int_equal(RNP_MKDIR(dirname, S_IRWXU), 0);

    /* write some data to the file and the discard it */
    assert_rnp_success(init_file_dest(&dst, filename, false));
    dst_write(&dst, filedata, filedatalen);
    assert_int_not_equal(file_size(filename), -1);
    dst_close(&dst, true);
    assert_int_equal(file_size(filename), -1);

    /* write some data to the file and make sure it is written */
    assert_rnp_success(init_file_dest(&dst, filename, false));
    dst_write(&dst, filedata, filedatalen);
    assert_int_not_equal(file_size(filename), -1);
    dst_close(&dst, false);
    assert_int_equal(file_size(filename), filedatalen);

    /* attempt to create file over existing without overwrite flag */
    assert_rnp_failure(init_file_dest(&dst, filename, false));
    assert_int_equal(file_size(filename), filedatalen);

    /* overwrite file - it should be truncated, then write bunch of bytes */
    assert_rnp_success(init_file_dest(&dst, filename, true));
    assert_int_equal(file_size(filename), 0);
    for (int i = 0; i < iterations; i++) {
        dst_write(&dst, filedata, filedatalen);
    }
    /* and some smaller writes */
    for (int i = 0; i < 5 * iterations; i++) {
        dst_write(&dst, "zzz", 3);
    }
    dst_close(&dst, false);
    assert_int_equal(file_size(filename), iterations * (filedatalen + 15));

    /* read file back, checking the contents */
    assert_rnp_success(init_file_src(&src, filename));
    for (int i = 0; i < iterations; i++) {
        size_t read = 0;
        assert_true(src_read(&src, tmpbuf, filedatalen, &read));
        assert_int_equal(read, filedatalen);
        assert_int_equal(memcmp(tmpbuf, filedata, filedatalen), 0);
    }
    for (int i = 0; i < 5 * iterations; i++) {
        size_t read = 0;
        assert_true(src_read(&src, tmpbuf, 3, &read));
        assert_int_equal(read, 3);
        assert_int_equal(memcmp(tmpbuf, "zzz", 3), 0);
    }
    src_close(&src);

    /* overwrite and discard - file should be deleted */
    assert_rnp_success(init_file_dest(&dst, filename, true));
    assert_int_equal(file_size(filename), 0);
    for (int i = 0; i < iterations; i++) {
        dst_write(&dst, "hello", 6);
    }
    dst_close(&dst, true);
    assert_int_equal(file_size(filename), -1);

    /* create and populate file in subfolder */
    assert_rnp_success(init_file_dest(&dst, file2name, true));
    assert_int_equal(file_size(file2name), 0);
    for (int i = 0; i < iterations; i++) {
        dst_write(&dst, filedata, filedatalen);
    }
    dst_close(&dst, false);
    assert_int_equal(file_size(file2name), filedatalen * iterations);
    assert_int_equal(rnp_unlink(file2name), 0);

    /* create and populate file stream, using tmp name before closing */
    assert_rnp_success(init_tmpfile_dest(&dst, filename, false));
    copy_tmp_path(tmpname, sizeof(tmpname), &dst);
    assert_int_equal(file_size(tmpname), 0);
    assert_int_equal(file_size(filename), -1);
    for (int i = 0; i < iterations; i++) {
        dst_write(&dst, filedata, filedatalen);
    }
    dst_close(&dst, false);
    assert_int_equal(file_size(tmpname), -1);
    assert_int_equal(file_size(filename), filedatalen * iterations);

    /* create and then discard file stream, using tmp name before closing */
    assert_rnp_success(init_tmpfile_dest(&dst, filename, true));
    copy_tmp_path(tmpname, sizeof(tmpname), &dst);
    assert_int_equal(file_size(tmpname), 0);
    dst_write(&dst, filedata, filedatalen);
    /* make sure file was not overwritten */
    assert_int_equal(file_size(filename), filedatalen * iterations);
    dst_close(&dst, true);
    assert_int_equal(file_size(tmpname), -1);
    assert_int_equal(file_size(filename), filedatalen * iterations);

    /* create and then close file stream, using tmp name before closing. No overwrite. */
    assert_rnp_success(init_tmpfile_dest(&dst, filename, false));
    copy_tmp_path(tmpname, sizeof(tmpname), &dst);
    assert_int_equal(file_size(tmpname), 0);
    dst_write(&dst, filedata, filedatalen);
    /* make sure file was not overwritten */
    assert_int_equal(file_size(filename), filedatalen * iterations);
    assert_rnp_failure(dst_finish(&dst));
    dst_close(&dst, false);
    assert_int_equal(file_size(tmpname), filedatalen);
    assert_int_equal(file_size(filename), filedatalen * iterations);
    assert_int_equal(rnp_unlink(tmpname), 0);

    /* create and then close file stream, using tmp name before closing. Overwrite existing. */
    assert_rnp_success(init_tmpfile_dest(&dst, filename, true));
    copy_tmp_path(tmpname, sizeof(tmpname), &dst);
    assert_int_equal(file_size(tmpname), 0);
    dst_write(&dst, filedata, filedatalen);
    /* make sure file was not overwritten yet */
    assert_int_equal(file_size(filename), filedatalen * iterations);
    assert_rnp_success(dst_finish(&dst));
    dst_close(&dst, false);
    assert_int_equal(file_size(tmpname), -1);
    assert_int_equal(file_size(filename), filedatalen);

    /* make sure we can overwrite directory */
    assert_rnp_success(init_tmpfile_dest(&dst, dirname, true));
    copy_tmp_path(tmpname, sizeof(tmpname), &dst);
    assert_int_equal(file_size(tmpname), 0);
    dst_write(&dst, filedata, filedatalen);
    /* make sure file was not overwritten yet */
    assert_int_equal(file_size(dirname), -1);
    assert_rnp_success(dst_finish(&dst));
    dst_close(&dst, false);
    assert_int_equal(file_size(tmpname), -1);
    assert_int_equal(file_size(dirname), filedatalen);

    /* cleanup */
    assert_int_equal(rnp_unlink(dirname), 0);
}

TEST_F(rnp_tests, test_stream_signatures)
{
    rnp_key_store_t *pubring;
    rnp_key_store_t *secring;
    pgp_signature_t  sig;
    pgp_source_t     sigsrc;
    pgp_key_t *      key = NULL;

    /* load keys */
    pubring = new rnp_key_store_t(
      PGP_KEY_STORE_GPG, "data/test_stream_signatures/pub.asc", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    /* load signature */
    assert_rnp_success(init_file_src(&sigsrc, "data/test_stream_signatures/source.txt.sig"));
    assert_rnp_success(sig.parse(sigsrc));
    src_close(&sigsrc);
    /* hash signed file */
    pgp_hash_alg_t halg = sig.halg;
    auto           hash_orig = rnp::Hash::create(halg);
    assert_true(stream_hash_file(*hash_orig, "data/test_stream_signatures/source.txt"));
    /* hash forged file */
    auto hash_forged = rnp::Hash::create(halg);
    assert_true(
      stream_hash_file(*hash_forged, "data/test_stream_signatures/source_forged.txt"));
    /* find signing key */
    assert_non_null(key = rnp_key_store_get_signer_key(pubring, &sig));
    /* validate signature and fields */
    auto hash = hash_orig->clone();
    assert_int_equal(sig.creation(), 1522241943);
    assert_rnp_success(signature_validate(sig, key->material(), *hash, global_ctx));
    /* check forged file */
    hash = hash_forged->clone();
    assert_rnp_failure(signature_validate(sig, key->material(), *hash, global_ctx));
    /* now let's create signature and sign file */

    /* load secret key */
    secring = new rnp_key_store_t(
      PGP_KEY_STORE_GPG, "data/test_stream_signatures/sec.asc", global_ctx);
    assert_true(rnp_key_store_load_from_path(secring, NULL));
    assert_non_null(key = rnp_key_store_get_signer_key(secring, &sig));
    assert_true(key->is_secret());
    /* fill signature */
    uint32_t create = time(NULL);
    uint32_t expire = 123456;
    sig = {};
    sig.version = PGP_V4;
    sig.halg = halg;
    sig.palg = key->alg();
    sig.set_type(PGP_SIG_BINARY);
    sig.set_keyfp(key->fp());
    sig.set_keyid(key->keyid());
    sig.set_creation(create);
    sig.set_expiration(expire);
    /* make use of add_notation() to cover it */
    try {
        std::vector<uint8_t> value;
        value.resize(66000);
        sig.add_notation("dummy@example.com", value, false, true);
        assert_true(false);
    } catch (const rnp::rnp_exception &e) {
        assert_int_equal(e.code(), RNP_ERROR_BAD_PARAMETERS);
    }
    sig.add_notation("dummy@example.com", "make codecov happy!", false);
    sig.fill_hashed_data();
    /* try to sign without decrypting of the secret key */
    hash = hash_orig->clone();
    assert_throw(signature_calculate(sig, key->material(), *hash, global_ctx));
    /* now unlock the key and sign */
    pgp_password_provider_t pswd_prov(rnp_password_provider_string, (void *) "password");
    assert_true(key->unlock(pswd_prov));
    hash = hash_orig->clone();
    signature_calculate(sig, key->material(), *hash, global_ctx);
    /* now verify signature */
    hash = hash_orig->clone();
    /* validate signature and fields */
    assert_int_equal(sig.creation(), create);
    assert_int_equal(sig.expiration(), expire);
    assert_true(sig.has_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR));
    assert_true(sig.keyfp() == key->fp());
    assert_rnp_success(signature_validate(sig, key->material(), *hash, global_ctx));
    /* cleanup */
    delete pubring;
    delete secring;
}

TEST_F(rnp_tests, test_stream_signatures_revoked_key)
{
    pgp_signature_t sig = {};
    pgp_source_t    sigsrc = {0};

    /* load signature */
    assert_rnp_success(
      init_file_src(&sigsrc, "data/test_stream_signatures/revoked-key-sig.gpg"));
    assert_rnp_success(sig.parse(sigsrc));
    src_close(&sigsrc);
    /* check revocation */
    assert_int_equal(sig.revocation_code(), PGP_REVOCATION_RETIRED);
    assert_string_equal(sig.revocation_reason().c_str(), "For testing!");
}

TEST_F(rnp_tests, test_stream_key_load)
{
    pgp_source_t               keysrc = {0};
    pgp_dest_t                 keydst = {0};
    pgp_key_sequence_t         keyseq;
    pgp_key_id_t               keyid = {};
    pgp_fingerprint_t          keyfp;
    pgp_transferable_key_t *   key = NULL;
    pgp_transferable_subkey_t *skey = NULL;

    /* public keyring, read-save-read-save armored-read */
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/pubring.gpg"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_true(keyseq.keys.size() > 1);
    src_close(&keysrc);

    assert_rnp_success(init_file_dest(&keydst, "keyout.gpg", true));
    assert_true(write_transferable_keys(keyseq, &keydst, false));
    dst_close(&keydst, false);

    assert_rnp_success(init_file_src(&keysrc, "keyout.gpg"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    src_close(&keysrc);

    assert_rnp_success(init_file_dest(&keydst, "keyout.asc", true));
    assert_true(write_transferable_keys(keyseq, &keydst, true));
    dst_close(&keydst, false);

    assert_rnp_success(init_file_src(&keysrc, "keyout.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    src_close(&keysrc);

    /* secret keyring */
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/secring.gpg"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_true(keyseq.keys.size() > 1);
    src_close(&keysrc);

    assert_rnp_success(init_file_dest(&keydst, "keyout-sec.gpg", true));
    assert_true(write_transferable_keys(keyseq, &keydst, false));
    dst_close(&keydst, false);

    assert_rnp_success(init_file_src(&keysrc, "keyout-sec.gpg"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    src_close(&keysrc);

    assert_rnp_success(init_file_dest(&keydst, "keyout-sec.asc", true));
    assert_true(write_transferable_keys(keyseq, &keydst, true));
    dst_close(&keydst, false);

    assert_rnp_success(init_file_src(&keysrc, "keyout-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    src_close(&keysrc);

    /* armored v3 public key */
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/4/rsav3-p.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_keyid(keyid, key->key));
    assert_true(cmp_keyid(keyid, "7D0BC10E933404C9"));
    assert_false(cmp_keyid(keyid, "1D0BC10E933404C9"));
    src_close(&keysrc);

    /* armored v3 secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/4/rsav3-s.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_keyid(keyid, key->key));
    assert_true(cmp_keyid(keyid, "7D0BC10E933404C9"));
    src_close(&keysrc);

    /* rsa/rsa public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/rsa-rsa-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "6BC04A5A3DDB35766B9A40D82FB9179118898E8B"));
    assert_rnp_success(pgp_keyid(keyid, key->key));
    assert_true(cmp_keyid(keyid, "2FB9179118898E8B"));
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* rsa/rsa secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/rsa-rsa-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "6BC04A5A3DDB35766B9A40D82FB9179118898E8B"));
    assert_rnp_success(pgp_keyid(keyid, key->key));
    assert_true(cmp_keyid(keyid, "2FB9179118898E8B"));
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* dsa/el-gamal public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/dsa-eg-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "091C44CE9CFBC3FF7EC7A64DC8A10A7D78273E10"));
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "02A5715C3537717E"));
    src_close(&keysrc);

    /* dsa/el-gamal secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/dsa-eg-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* curve 25519 ecc public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-25519-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "21FC68274AAE3B5DE39A4277CC786278981B0728"));
    src_close(&keysrc);

    /* curve 25519 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-25519-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_true(key->subkeys.empty());
    src_close(&keysrc);

    /* eddsa/x25519 ecc public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-x25519-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "4C9738A6F2BE4E1A796C9B7B941822A0FC1B30A5"));
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "C711187E594376AF"));
    src_close(&keysrc);

    /* eddsa/x25519 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-x25519-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* p-256 ecc public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "B54FDEBBB673423A5D0AA54423674F21B2441527"));
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "37E285E9E9851491"));
    src_close(&keysrc);

    /* p-256 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* p-384 ecc public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p384-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "AB25CBA042DD924C3ACC3ED3242A3AA5EA85F44A"));
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "E210E3D554A4FAD9"));
    src_close(&keysrc);

    /* p-384 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p384-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* p-521 ecc public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p521-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "4FB39FF6FA4857A4BD7EF5B42092CA8324263B6A"));
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "9853DF2F6D297442"));
    src_close(&keysrc);

    /* p-521 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p521-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* Brainpool P256 ecc public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp256-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "0633C5F72A198F51E650E4ABD0C8A3DAF9E0634A"));
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "2EDABB94D3055F76"));
    src_close(&keysrc);

    /* Brainpool P256 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp256-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* Brainpool P384 ecc public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp384-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "5B8A254C823CED98DECD10ED6CF2DCE85599ADA2"));
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "CFF1BB6F16D28191"));
    src_close(&keysrc);

    /* Brainpool P384 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp384-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* Brainpool P512 ecc public key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp512-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "4C59AB9272AA6A1F60B85BD0AA5C58D14F7B8F48"));
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "20CDAA1482BA79CE"));
    src_close(&keysrc);

    /* Brainpool P512 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp512-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);

    /* secp256k1 ecc public key, not supported now */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256k1-pub.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(pgp_fingerprint(keyfp, key->key));
    assert_true(cmp_keyfp(keyfp, "81F772B57D4EBFE7000A66233EA5BB6F9692C1A0"));
    assert_non_null(skey = &key->subkeys.front());
    assert_rnp_success(pgp_keyid(keyid, skey->subkey));
    assert_true(cmp_keyid(keyid, "7635401F90D3E533"));
    src_close(&keysrc);

    /* secp256k1 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256k1-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_int_equal(keyseq.keys.size(), 1);
    assert_non_null(key = &keyseq.keys.front());
    assert_int_equal(key->subkeys.size(), 1);
    assert_non_null(key->subkeys[0].subkey.hashed_data);
    src_close(&keysrc);
}

static void
buggy_key_load_single(const void *keydata, size_t keylen)
{
    pgp_source_t       memsrc = {0};
    pgp_key_sequence_t keyseq;
    size_t             partlen;
    uint8_t *          dataptr;

    /* try truncated load */
    for (partlen = 1; partlen < keylen; partlen += 15) {
        assert_rnp_success(init_mem_src(&memsrc, keydata, partlen, false));
        if (!process_pgp_keys(memsrc, keyseq, false)) {
            /* it may succeed if we accidentally hit some packet boundary */
            assert_false(keyseq.keys.empty());
        } else {
            assert_true(keyseq.keys.empty());
        }
        src_close(&memsrc);
    }

    /* try modified load */
    dataptr = (uint8_t *) keydata;
    for (partlen = 1; partlen < keylen; partlen++) {
        dataptr[partlen] ^= 0xff;
        assert_rnp_success(init_mem_src(&memsrc, keydata, keylen, false));
        if (!process_pgp_keys(memsrc, keyseq, false)) {
            /* it may succeed if we accidentally hit some packet boundary */
            assert_false(keyseq.keys.empty());
        } else {
            assert_true(keyseq.keys.empty());
        }
        src_close(&memsrc);
        dataptr[partlen] ^= 0xff;
    }
}

/* check for memory leaks during buggy key loads */
TEST_F(rnp_tests, test_stream_key_load_errors)
{
    pgp_source_t fsrc = {0};
    pgp_source_t armorsrc = {0};
    pgp_source_t memsrc = {0};

    const char *key_files[] = {"data/keyrings/4/rsav3-p.asc",
                               "data/keyrings/4/rsav3-s.asc",
                               "data/keyrings/1/pubring.gpg",
                               "data/keyrings/1/secring.gpg",
                               "data/test_stream_key_load/dsa-eg-pub.asc",
                               "data/test_stream_key_load/dsa-eg-sec.asc",
                               "data/test_stream_key_load/ecc-25519-pub.asc",
                               "data/test_stream_key_load/ecc-25519-sec.asc",
                               "data/test_stream_key_load/ecc-x25519-pub.asc",
                               "data/test_stream_key_load/ecc-x25519-sec.asc",
                               "data/test_stream_key_load/ecc-p256-pub.asc",
                               "data/test_stream_key_load/ecc-p256-sec.asc",
                               "data/test_stream_key_load/ecc-p384-pub.asc",
                               "data/test_stream_key_load/ecc-p384-sec.asc",
                               "data/test_stream_key_load/ecc-p521-pub.asc",
                               "data/test_stream_key_load/ecc-p521-sec.asc",
                               "data/test_stream_key_load/ecc-bp256-pub.asc",
                               "data/test_stream_key_load/ecc-bp256-sec.asc",
                               "data/test_stream_key_load/ecc-bp384-pub.asc",
                               "data/test_stream_key_load/ecc-bp384-sec.asc",
                               "data/test_stream_key_load/ecc-bp512-pub.asc",
                               "data/test_stream_key_load/ecc-bp512-sec.asc",
                               "data/test_stream_key_load/ecc-p256k1-pub.asc",
                               "data/test_stream_key_load/ecc-p256k1-sec.asc"};

    for (size_t i = 0; i < sizeof(key_files) / sizeof(char *); i++) {
        assert_rnp_success(init_file_src(&fsrc, key_files[i]));
        if (is_armored_source(&fsrc)) {
            assert_rnp_success(init_armored_src(&armorsrc, &fsrc));
            assert_rnp_success(read_mem_src(&memsrc, &armorsrc));
            src_close(&armorsrc);
        } else {
            assert_rnp_success(read_mem_src(&memsrc, &fsrc));
        }
        src_close(&fsrc);
        buggy_key_load_single(mem_src_get_memory(&memsrc), memsrc.size);
        src_close(&memsrc);
    }
}

TEST_F(rnp_tests, test_stream_key_decrypt)
{
    pgp_source_t               keysrc = {0};
    pgp_key_sequence_t         keyseq;
    pgp_transferable_key_t *   key = NULL;
    pgp_transferable_subkey_t *subkey = NULL;

    /* load and decrypt secret keyring */
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/secring.gpg"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    for (auto &key : keyseq.keys) {
        assert_rnp_failure(decrypt_secret_key(&key.key, "passw0rd"));
        assert_rnp_success(decrypt_secret_key(&key.key, "password"));

        for (auto &subkey : key.subkeys) {
            assert_rnp_failure(decrypt_secret_key(&subkey.subkey, "passw0rd"));
            assert_rnp_success(decrypt_secret_key(&subkey.subkey, "password"));
        }
    }
    src_close(&keysrc);

    /* armored v3 secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/4/rsav3-s.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_failure(decrypt_secret_key(&key->key, "passw0rd"));
#if defined(ENABLE_IDEA)
    assert_rnp_success(decrypt_secret_key(&key->key, "password"));
#else
    assert_rnp_failure(decrypt_secret_key(&key->key, "password"));
#endif
    src_close(&keysrc);

    /* rsa/rsa secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/rsa-rsa-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(decrypt_secret_key(&key->key, "password"));
    assert_non_null(subkey = &key->subkeys.front());
    assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
    src_close(&keysrc);

    /* dsa/el-gamal secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/dsa-eg-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(decrypt_secret_key(&key->key, "password"));
    assert_non_null(subkey = &key->subkeys.front());
    assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
    src_close(&keysrc);

    /* curve 25519 eddsa ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-25519-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(decrypt_secret_key(&key->key, "password"));
    src_close(&keysrc);

    /* x25519 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-x25519-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(decrypt_secret_key(&key->key, "password"));
    assert_non_null(subkey = &key->subkeys.front());
    assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
    src_close(&keysrc);

    /* p-256 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(decrypt_secret_key(&key->key, "password"));
    assert_non_null(subkey = &key->subkeys.front());
    assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
    src_close(&keysrc);

    /* p-384 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p384-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(decrypt_secret_key(&key->key, "password"));
    assert_non_null(subkey = &key->subkeys.front());
    assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
    src_close(&keysrc);

    /* p-521 ecc secret key */
    assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p521-sec.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    assert_non_null(key = &keyseq.keys.front());
    assert_rnp_success(decrypt_secret_key(&key->key, "password"));
    assert_non_null(subkey = &key->subkeys.front());
    assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
    src_close(&keysrc);
}

TEST_F(rnp_tests, test_stream_key_encrypt)
{
    pgp_source_t       keysrc = {0};
    pgp_dest_t         keydst = {0};
    uint8_t            keybuf[16384];
    size_t             keylen;
    pgp_key_sequence_t keyseq;
    pgp_key_sequence_t keyseq2;

    /* load and decrypt secret keyring, then re-encrypt and reload keys */
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/secring.gpg"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    src_close(&keysrc);
    for (auto &key : keyseq.keys) {
        assert_rnp_success(decrypt_secret_key(&key.key, "password"));

        for (auto &subkey : key.subkeys) {
            assert_rnp_success(decrypt_secret_key(&subkey.subkey, "password"));
        }

        /* change password and encryption algorithm */
        key.key.sec_protection.symm_alg = PGP_SA_CAMELLIA_192;
        assert_rnp_success(encrypt_secret_key(&key.key, "passw0rd", global_ctx.rng));
        for (auto &subkey : key.subkeys) {
            subkey.subkey.sec_protection.symm_alg = PGP_SA_CAMELLIA_256;
            assert_rnp_success(encrypt_secret_key(&subkey.subkey, "passw0rd", global_ctx.rng));
        }
        /* write changed key */
        assert_rnp_success(init_mem_dest(&keydst, keybuf, sizeof(keybuf)));
        assert_true(write_transferable_key(key, keydst));
        keylen = keydst.writeb;
        dst_close(&keydst, false);
        /* load and decrypt changed key */
        assert_rnp_success(init_mem_src(&keysrc, keybuf, keylen, false));
        assert_rnp_success(process_pgp_keys(keysrc, keyseq2, false));
        src_close(&keysrc);
        assert_false(keyseq2.keys.empty());
        auto &key2 = keyseq2.keys.front();
        assert_int_equal(key2.key.sec_protection.symm_alg, PGP_SA_CAMELLIA_192);
        assert_rnp_success(decrypt_secret_key(&key2.key, "passw0rd"));

        for (auto &subkey : key2.subkeys) {
            assert_int_equal(subkey.subkey.sec_protection.symm_alg, PGP_SA_CAMELLIA_256);
            assert_rnp_success(decrypt_secret_key(&subkey.subkey, "passw0rd"));
        }
        /* write key without the password */
        key2.key.sec_protection.s2k.usage = PGP_S2KU_NONE;
        assert_rnp_success(encrypt_secret_key(&key2.key, NULL, global_ctx.rng));
        for (auto &subkey : key2.subkeys) {
            subkey.subkey.sec_protection.s2k.usage = PGP_S2KU_NONE;
            assert_rnp_success(encrypt_secret_key(&subkey.subkey, NULL, global_ctx.rng));
        }
        /* write changed key */
        assert_rnp_success(init_mem_dest(&keydst, keybuf, sizeof(keybuf)));
        assert_true(write_transferable_key(key2, keydst));
        keylen = keydst.writeb;
        dst_close(&keydst, false);
        /* load non-encrypted key */
        assert_rnp_success(init_mem_src(&keysrc, keybuf, keylen, false));
        assert_rnp_success(process_pgp_keys(keysrc, keyseq2, false));
        src_close(&keysrc);
        assert_false(keyseq2.keys.empty());
        auto &key3 = keyseq2.keys.front();
        assert_int_equal(key3.key.sec_protection.s2k.usage, PGP_S2KU_NONE);
        assert_rnp_success(decrypt_secret_key(&key3.key, NULL));

        for (auto &subkey : key3.subkeys) {
            assert_int_equal(subkey.subkey.sec_protection.s2k.usage, PGP_S2KU_NONE);
            assert_rnp_success(decrypt_secret_key(&subkey.subkey, NULL));
        }
    }
}

TEST_F(rnp_tests, test_stream_key_signatures)
{
    pgp_source_t         keysrc = {0};
    pgp_key_sequence_t   keyseq;
    pgp_key_t *          pkey = NULL;
    pgp_signature_info_t sinfo = {};

    /* v3 public key */
    auto pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/4/rsav3-p.asc", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/4/rsav3-p.asc"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    src_close(&keysrc);
    assert_int_equal(keyseq.keys.size(), 1);
    auto &key = keyseq.keys.front();
    auto &uid = key.userids.front();
    auto &sig = uid.signatures.front();
    assert_non_null(pkey = rnp_key_store_get_signer_key(pubring, &sig));
    /* check certification signature */
    auto hash = signature_hash_certification(sig, key.key, uid.uid);
    /* this signature uses MD5 hash after the allowed date */
    assert_int_equal(signature_validate(sig, pkey->material(), *hash, global_ctx),
                     RNP_ERROR_SIGNATURE_INVALID);
    /* add rule which allows MD5 */
    rnp::SecurityRule allow_md5(
      rnp::FeatureType::Hash, PGP_HASH_MD5, rnp::SecurityLevel::Default);
    allow_md5.override = true;
    global_ctx.profile.add_rule(allow_md5);
    assert_rnp_success(signature_validate(sig, pkey->material(), *hash, global_ctx));
    /* modify userid and check signature */
    uid.uid.uid[2] = '?';
    hash = signature_hash_certification(sig, key.key, uid.uid);
    assert_rnp_failure(signature_validate(sig, pkey->material(), *hash, global_ctx));
    /* remove MD5 rule */
    assert_true(global_ctx.profile.del_rule(allow_md5));
    delete pubring;

    /* keyring */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/pubring.gpg"));
    assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
    src_close(&keysrc);

    /* check key signatures */
    for (auto &keyref : keyseq.keys) {
        for (auto &uid : keyref.userids) {
            /* userid certifications */
            for (auto &sig : uid.signatures) {
                assert_non_null(pkey = rnp_key_store_get_signer_key(pubring, &sig));
                /* high level interface */
                sinfo.sig = &sig;
                pkey->validate_cert(sinfo, keyref.key, uid.uid, global_ctx);
                assert_true(sinfo.valid);
                /* low level check */
                auto hash = signature_hash_certification(sig, keyref.key, uid.uid);
                assert_rnp_success(
                  signature_validate(sig, pkey->material(), *hash, global_ctx));
                /* modify userid and check signature */
                uid.uid.uid[2] = '?';
                pkey->validate_cert(sinfo, keyref.key, uid.uid, global_ctx);
                assert_false(sinfo.valid);
                hash = signature_hash_certification(sig, keyref.key, uid.uid);
                assert_rnp_failure(
                  signature_validate(sig, pkey->material(), *hash, global_ctx));
            }
        }

        /* subkey binding signatures */
        for (auto &subkey : keyref.subkeys) {
            auto &sig = subkey.signatures.front();
            assert_non_null(pkey = rnp_key_store_get_signer_key(pubring, &sig));
            /* high level interface */
            sinfo.sig = &sig;
            pgp_key_id_t subid;
            assert_rnp_success(pgp_keyid(subid, subkey.subkey));
            char ssubid[PGP_KEY_ID_SIZE * 2 + 1];
            assert_true(rnp::hex_encode(subid.data(), subid.size(), ssubid, sizeof(ssubid)));
            pgp_key_t *psub = rnp_tests_get_key_by_id(pubring, ssubid);
            assert_non_null(psub);
            pkey->validate_binding(sinfo, *psub, global_ctx);
            assert_true(sinfo.valid);
            /* low level check */
            hash = signature_hash_binding(sig, keyref.key, subkey.subkey);
            pkey->validate_sig(sinfo, *hash, global_ctx);
            assert_true(sinfo.valid);
        }
    }

    delete pubring;
}

static bool
validate_key_sigs(const char *path, rnp::SecurityContext &global_ctx)
{
    rnp_key_store_t *pubring = new rnp_key_store_t(PGP_KEY_STORE_GPG, path, global_ctx);
    bool             valid = rnp_key_store_load_from_path(pubring, NULL);
    for (auto &key : pubring->keys) {
        key.validate(*pubring);
        valid = valid && key.valid();
    }
    delete pubring;
    return valid;
}

TEST_F(rnp_tests, test_stream_key_signature_validate)
{
    /* v3 public key */
    rnp_key_store_t *pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/4/rsav3-p.asc", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_int_equal(rnp_key_store_get_key_count(pubring), 1);
    pgp_key_t &pkey = pubring->keys.front();
    pkey.validate(*pubring);
    /* MD5 signature is marked as invalid by default */
    assert_false(pkey.valid());
    rnp::SecurityRule allow_md5(
      rnp::FeatureType::Hash, PGP_HASH_MD5, rnp::SecurityLevel::Default);
    allow_md5.override = true;
    /* Allow MD5 */
    global_ctx.profile.add_rule(allow_md5);
    /* we need to manually reset signature validity */
    pkey.get_sig(0).validity.reset();
    pkey.revalidate(*pubring);
    assert_true(pkey.valid());
    /* Remove MD5 and revalidate */
    assert_true(global_ctx.profile.del_rule(allow_md5));
    pkey.get_sig(0).validity.reset();
    pkey.revalidate(*pubring);
    assert_false(pkey.valid());
    delete pubring;

    /* keyring */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_true(rnp_key_store_get_key_count(pubring) > 0);
    int i = 0;
    for (auto &key : pubring->keys) {
        key.validate(*pubring);
        // subkey #2 is expired
        if (i == 2) {
            assert_false(key.valid());
        } else {
            assert_true(key.valid());
        }
        i++;
    }
    delete pubring;

    /* misc key files */
    auto validate = [this](const std::string &file) {
        auto path = "data/test_stream_key_load/" + file;
        return validate_key_sigs(path.c_str(), this->global_ctx);
    };
    assert_true(validate("dsa-eg-pub.asc"));
    assert_true(validate("dsa-eg-sec.asc"));
    assert_true(validate("ecc-25519-pub.asc"));
    assert_true(validate("ecc-25519-sec.asc"));
    assert_true(validate("ecc-x25519-pub.asc"));
    assert_true(validate("ecc-x25519-sec.asc"));
    assert_true(validate("ecc-p256-pub.asc"));
    assert_true(validate("ecc-p256-sec.asc"));
    assert_true(validate("ecc-p384-pub.asc"));
    assert_true(validate("ecc-p384-sec.asc"));
    assert_true(validate("ecc-p521-pub.asc"));
    assert_true(validate("ecc-p521-sec.asc"));
    assert_true(validate("ecc-bp256-pub.asc") == brainpool_enabled());
    assert_true(validate("ecc-bp256-sec.asc") == brainpool_enabled());
    assert_true(validate("ecc-bp384-pub.asc") == brainpool_enabled());
    assert_true(validate("ecc-bp384-sec.asc") == brainpool_enabled());
    assert_true(validate("ecc-bp512-pub.asc") == brainpool_enabled());
    assert_true(validate("ecc-bp512-sec.asc") == brainpool_enabled());
    assert_true(validate("ecc-p256k1-pub.asc"));
    assert_true(validate("ecc-p256k1-sec.asc"));
}

TEST_F(rnp_tests, test_stream_verify_no_key)
{
    cli_rnp_t rnp;
    rnp_cfg   cfg;

    /* setup rnp structure and params */
    cfg.set_str(CFG_KR_PUB_PATH, "");
    cfg.set_str(CFG_KR_SEC_PATH, "");
    cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_GPG);
    cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_GPG);
    assert_true(rnp.init(cfg));

    rnp_cfg &rnpcfg = rnp.cfg();
    /* setup cfg for verification */
    rnpcfg.set_str(CFG_INFILE, "data/test_stream_verification/verify_encrypted_no_key.pgp");
    rnpcfg.set_str(CFG_OUTFILE, "output.dat");
    rnpcfg.set_bool(CFG_OVERWRITE, true);
    /* setup operation context */
    assert_rnp_success(
      rnp_ffi_set_pass_provider(rnp.ffi, ffi_string_password_provider, (void *) "pass1"));
    rnpcfg.set_bool(CFG_NO_OUTPUT, false);
    if (sm2_enabled()) {
        /* operation should success if output is not discarded, i.e. operation = decrypt */
        assert_true(cli_rnp_process_file(&rnp));
        assert_int_equal(file_size("output.dat"), 4);
    } else {
        /* operation should fail */
        assert_false(cli_rnp_process_file(&rnp));
        assert_int_equal(file_size("output.dat"), -1);
    }
    /* try second password */
    assert_rnp_success(
      rnp_ffi_set_pass_provider(rnp.ffi, ffi_string_password_provider, (void *) "pass2"));
    assert_true(cli_rnp_process_file(&rnp));
    assert_int_equal(file_size("output.dat"), 4);
    /* decryption/verification fails without password */
    assert_rnp_success(
      rnp_ffi_set_pass_provider(rnp.ffi, ffi_failing_password_provider, NULL));
    assert_false(cli_rnp_process_file(&rnp));
    assert_int_equal(file_size("output.dat"), -1);
    /* decryption/verification fails with wrong password */
    assert_rnp_success(
      rnp_ffi_set_pass_provider(rnp.ffi, ffi_string_password_provider, (void *) "pass_wrong"));
    assert_false(cli_rnp_process_file(&rnp));
    assert_int_equal(file_size("output.dat"), -1);
    /* verification fails if output is discarded, i.e. operation = verify */
    rnpcfg.set_bool(CFG_NO_OUTPUT, true);
    assert_false(cli_rnp_process_file(&rnp));
    assert_int_equal(file_size("output.dat"), -1);

    /* cleanup */
    rnp.end();
}

static bool
check_dump_file_dst(const char *file, bool mpi, bool grip)
{
    pgp_source_t   src;
    pgp_dest_t     dst;
    rnp_dump_ctx_t ctx = {0};

    ctx.dump_mpi = mpi;
    ctx.dump_grips = grip;

    if (init_file_src(&src, file)) {
        return false;
    }
    if (init_mem_dest(&dst, NULL, 0)) {
        return false;
    }
    if (stream_dump_packets(&ctx, &src, &dst)) {
        return false;
    }
    src_close(&src);
    dst_close(&dst, false);
    return true;
}

static bool
check_dump_file_json(const char *file, bool mpi, bool grip)
{
    pgp_source_t   src;
    rnp_dump_ctx_t ctx = {0};
    json_object *  jso = NULL;

    ctx.dump_mpi = mpi;
    ctx.dump_grips = grip;

    if (init_file_src(&src, file)) {
        return false;
    }
    if (stream_dump_packets_json(&ctx, &src, &jso)) {
        return false;
    }
    if (!json_object_is_type(jso, json_type_array)) {
        return false;
    }
    src_close(&src);
    json_object_put(jso);
    return true;
}

static bool
check_dump_file(const char *file, bool mpi, bool grip)
{
    return check_dump_file_dst(file, mpi, grip) && check_dump_file_json(file, mpi, grip);
}

TEST_F(rnp_tests, test_y2k38)
{
    cli_rnp_t rnp;
    rnp_cfg   cfg;

    /* setup rnp structure and params */
    cfg.set_str(CFG_KR_PUB_PATH, "data/keyrings/6");
    cfg.set_str(CFG_KR_SEC_PATH, "data/keyrings/6");
    cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_GPG);
    cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_GPG);
    cfg.set_str(CFG_IO_RESS, "stderr.dat");
    assert_true(rnp.init(cfg));

    rnp_cfg &rnpcfg = rnp.cfg();
    /* verify */
    rnpcfg.set_str(CFG_INFILE, "data/test_messages/future.pgp");
    rnpcfg.set_bool(CFG_OVERWRITE, true);
    assert_false(cli_rnp_process_file(&rnp));

    /* clean up and flush the file */
    rnp.end();

    /* check the file for presence of correct dates */
    auto        output = file_to_str("stderr.dat");
    time_t      crtime = 0xC0000000;
    std::string correctMade = "signature made ";
    if (rnp_y2k38_warning(crtime)) {
        correctMade += ">=";
    }
    correctMade += rnp_ctime(crtime);
    assert_true(
      std::search(output.begin(), output.end(), correctMade.begin(), correctMade.end()) !=
      output.end());
    time_t      validtime = rnp_timeadd(crtime, 0xD0000000);
    std::string correctValid = "Valid until ";
    if (rnp_y2k38_warning(validtime)) {
        correctValid += ">=";
    }
    correctValid += rnp_ctime(validtime);
    assert_true(
      std::search(output.begin(), output.end(), correctValid.begin(), correctValid.end()) !=
      output.end());
    unlink("stderr.dat");
}

TEST_F(rnp_tests, test_stream_dumper_y2k38)
{
    pgp_source_t   src;
    pgp_dest_t     dst;
    rnp_dump_ctx_t ctx = {0};

    assert_rnp_success(init_file_src(&src, "data/keyrings/6/pubring.gpg"));
    assert_rnp_success(init_mem_dest(&dst, NULL, 0));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    auto   written = (const uint8_t *) mem_dest_get_memory(&dst);
    auto   last = written + dst.writeb;
    time_t timestamp = 2958774690;
    // regenerate time for the current timezone
    std::string correct = "creation time: 2958774690 (";
    if (rnp_y2k38_warning(timestamp)) {
        correct += ">=";
    }
    correct += rnp_ctime(timestamp).substr(0, 24);
    correct += ')';
    assert_true(std::search(written, last, correct.begin(), correct.end()) != last);
    dst_close(&dst, false);
}

TEST_F(rnp_tests, test_stream_dumper)
{
    pgp_source_t   src;
    pgp_dest_t     dst;
    rnp_dump_ctx_t ctx = {0};

    assert_true(check_dump_file("data/keyrings/1/pubring.gpg", false, false));
    assert_true(check_dump_file("data/keyrings/1/secring.gpg", false, false));
    assert_true(check_dump_file("data/keyrings/4/rsav3-p.asc", false, false));
    assert_true(check_dump_file("data/keyrings/4/rsav3-p.asc", true, true));
    assert_true(check_dump_file("data/keyrings/4/rsav3-s.asc", true, false));
    assert_true(check_dump_file("data/test_repgp/encrypted_text.gpg", true, false));
    assert_true(check_dump_file("data/test_repgp/signed.gpg", true, false));
    assert_true(check_dump_file("data/test_repgp/encrypted_key.gpg", true, false));
    assert_true(check_dump_file("data/test_stream_key_load/dsa-eg-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/dsa-eg-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-25519-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-25519-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-x25519-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-x25519-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-p256-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-p256-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-p384-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-p384-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-p521-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-p521-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-bp256-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-bp256-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-bp384-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-bp384-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-bp512-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-bp512-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-p256k1-pub.asc", true, true));
    assert_true(check_dump_file("data/test_stream_key_load/ecc-p256k1-sec.asc", true, true));
    assert_true(check_dump_file("data/test_stream_signatures/source.txt.asc", true, true));
    assert_true(check_dump_file("data/test_stream_signatures/source.txt.asc.asc", true, true));
    assert_true(check_dump_file(
      "data/test_stream_verification/verify_encrypted_no_key.pgp", true, true));

    assert_rnp_success(init_file_src(&src, "data/test_stream_signatures/source.txt"));
    assert_rnp_success(init_mem_dest(&dst, NULL, 0));
    assert_rnp_failure(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, false);

    assert_rnp_success(init_file_src(&src, "data/test_messages/message.txt.enc-no-mdc"));
    assert_rnp_success(init_mem_dest(&dst, NULL, 0));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, false);

    assert_rnp_success(init_file_src(&src, "data/test_messages/message.txt.enc-mdc"));
    assert_rnp_success(init_mem_dest(&dst, NULL, 0));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, false);

    assert_rnp_success(init_file_src(&src, "data/test_messages/message-32k-crlf.txt.gpg"));
    assert_rnp_success(init_mem_dest(&dst, NULL, 0));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, false);
}

TEST_F(rnp_tests, test_stream_z)
{
    pgp_source_t   src;
    pgp_dest_t     dst;
    rnp_dump_ctx_t ctx = {0};

    /* packet dumper will decompress source stream, making less code lines here */
    ctx.dump_mpi = true;
    ctx.dump_packets = true;

    assert_rnp_success(init_file_src(&src, "data/test_stream_z/4gb.bzip2"));
    assert_rnp_success(init_null_dest(&dst));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, true);

    assert_rnp_success(init_file_src(&src, "data/test_stream_z/4gb.bzip2.cut"));
    assert_rnp_success(init_null_dest(&dst));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, true);

    assert_rnp_success(init_file_src(&src, "data/test_stream_z/128mb.zlib"));
    assert_rnp_success(init_null_dest(&dst));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, true);

    assert_rnp_success(init_file_src(&src, "data/test_stream_z/128mb.zlib.cut"));
    assert_rnp_success(init_null_dest(&dst));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, true);

    assert_rnp_success(init_file_src(&src, "data/test_stream_z/128mb.zip"));
    assert_rnp_success(init_null_dest(&dst));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, true);

    assert_rnp_success(init_file_src(&src, "data/test_stream_z/128mb.zip.cut"));
    assert_rnp_success(init_null_dest(&dst));
    assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
    src_close(&src);
    dst_close(&dst, true);
}

/* This test checks for GitHub issue #814.
 */
TEST_F(rnp_tests, test_stream_814_dearmor_double_free)
{
    pgp_source_t src;
    pgp_dest_t   dst;
    const char * buf = "-----BEGIN PGP BAD HEADER-----";

    assert_rnp_success(init_mem_src(&src, buf, strlen(buf), false));
    assert_rnp_success(init_null_dest(&dst));
    assert_rnp_failure(rnp_dearmor_source(&src, &dst));
    src_close(&src);
    dst_close(&dst, true);
}

TEST_F(rnp_tests, test_stream_825_dearmor_blank_line)
{
    rnp_key_store_t *keystore = NULL;
    pgp_source_t     src = {};

    keystore = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);
    assert_rnp_success(
      init_file_src(&src, "data/test_stream_armor/extra_line_before_trailer.asc"));
    assert_true(rnp_key_store_load_from_src(keystore, &src, NULL));
    assert_int_equal(rnp_key_store_get_key_count(keystore), 2);
    src_close(&src);
    delete keystore;
}

static bool
try_dearmor(const char *str, int len)
{
    pgp_source_t src = {};
    pgp_dest_t   dst = {};
    bool         res = false;

    if (len < 0) {
        return false;
    }
    if (init_mem_src(&src, str, len, false) != RNP_SUCCESS) {
        goto done;
    }
    if (init_null_dest(&dst) != RNP_SUCCESS) {
        goto done;
    }
    res = rnp_dearmor_source(&src, &dst) == RNP_SUCCESS;
done:
    src_close(&src);
    dst_close(&dst, true);
    return res;
}

TEST_F(rnp_tests, test_stream_dearmor_edge_cases)
{
    const char *HDR = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
    const char *B1 = "mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo";
    const char *B2 = "ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID";
    const char *B3 = "AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco";
    const char *B4 = "lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU";
    const char *B5 = "9RrUJSYZnMla/pQdgOxd7/PjRCpbCg==";
    const char *CRC = "=miZp";
    const char *FTR = "-----END PGP PUBLIC KEY BLOCK-----";
    const char *FTR2 = "-----END PGP WEIRD KEY BLOCK-----";
    char        b64[1024];
    char        msg[1024];
    int         b64len = 0;
    int         len = 0;

    /* fill the body with normal \n line endings */
    b64len = snprintf(b64, sizeof(b64), "%s\n%s\n%s\n%s\n%s", B1, B2, B3, B4, B5);
    assert_true((b64len > 0) && (b64len < (int) sizeof(b64)));

    /* try normal message */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));

    /* no empty line after the headers, now accepted, see #1289 */
    len = snprintf(msg, sizeof(msg), "%s\n%s\n%s\n%s\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));

    /* \r\n line ending */
    len = snprintf(msg, sizeof(msg), "%s\r\n\r\n%s\r\n%s\r\n%s\r\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));

    /* mixed line ending */
    len = snprintf(msg, sizeof(msg), "%s\r\n\n%s\r\n%s\n%s\r\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));

    /* extra line before the footer */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n\n%s\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));

    /* extra spaces after the header: allowed by RFC */
    len = snprintf(msg, sizeof(msg), "%s  \t  \n\n%s\n%s\n%s\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));

    /* extra spaces after the footer: allowed by RFC as well */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s \n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\t\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\t\t     \t\t \n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));

    /* invalid footer */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\n", HDR, b64, CRC, FTR2);
    assert_false(try_dearmor(msg, len));

    /* extra spaces or tabs before the footer - allow it, see issue #2199 */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n  %s\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n\t\t %s\n", HDR, b64, CRC, FTR);
    assert_true(try_dearmor(msg, len));
    /* no empty line between crc and footer - FAIL */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s%s\n", HDR, b64, CRC, FTR);
    assert_false(try_dearmor(msg, len));
    /* extra chars before the footer - FAIL */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n11111%s\n", HDR, b64, CRC, FTR);
    assert_false(try_dearmor(msg, len));

    /* cut out or extended b64 padding */
    len = snprintf(msg, sizeof(msg), "%s\n\n%.*s\n%s\n%s\n", HDR, b64len - 1, b64, CRC, FTR);
    assert_false(try_dearmor(msg, len));
    len = snprintf(msg, sizeof(msg), "%s\n\n%.*s\n%s\n%s\n", HDR, b64len - 2, b64, CRC, FTR);
    assert_false(try_dearmor(msg, len));
    len = snprintf(msg, sizeof(msg), "%s\n\n%s==\n%s\n%s\n", HDR, b64, CRC, FTR);
    assert_false(try_dearmor(msg, len));

    /* invalid chars in b64 data */
    char old = b64[30];
    b64[30] = '?';
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\n", HDR, b64, CRC, FTR);
    assert_false(try_dearmor(msg, len));
    b64[30] = old;

    /* modified/malformed crc (should be accepted now, see #1401) */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n=miZq\n%s\n", HDR, b64, FTR);
    assert_true(try_dearmor(msg, len));
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\nmiZp\n%s\n", HDR, b64, FTR);
    assert_false(try_dearmor(msg, len));
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n==miZp\n%s\n", HDR, b64, FTR);
    assert_false(try_dearmor(msg, len));
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n=miZpp\n%s\n", HDR, b64, FTR);
    assert_false(try_dearmor(msg, len));
    /* missing crc */
    len = snprintf(msg, sizeof(msg), "%s\n\n%s\n\n%s\n", HDR, b64, FTR);
    assert_true(try_dearmor(msg, len));
}

static void
add_openpgp_layers(
  const char *msg, pgp_dest_t &pgpdst, int compr, int encr, rnp::SecurityContext &global_ctx)
{
    pgp_source_t src = {};
    pgp_dest_t   dst = {};

    assert_rnp_success(init_mem_src(&src, msg, strlen(msg), false));
    assert_rnp_success(init_mem_dest(&dst, NULL, 0));
    assert_rnp_success(rnp_wrap_src(src, dst, "message.txt", time(NULL)));
    src_close(&src);
    assert_rnp_success(init_mem_src(&src, mem_dest_own_memory(&dst), dst.writeb, true));
    dst_close(&dst, false);

    /* add compression layers */
    for (int i = 0; i < compr; i++) {
        pgp_compression_type_t alg = (pgp_compression_type_t)((i % 3) + 1);
        assert_rnp_success(init_mem_dest(&dst, NULL, 0));
        assert_rnp_success(rnp_compress_src(src, dst, alg, 9));
        src_close(&src);
        assert_rnp_success(init_mem_src(&src, mem_dest_own_memory(&dst), dst.writeb, true));
        dst_close(&dst, false);
    }

    /* add encryption layers */
    for (int i = 0; i < encr; i++) {
        assert_rnp_success(init_mem_dest(&dst, NULL, 0));
        assert_rnp_success(rnp_raw_encrypt_src(src, dst, "password", global_ctx));
        src_close(&src);
        assert_rnp_success(init_mem_src(&src, mem_dest_own_memory(&dst), dst.writeb, true));
        dst_close(&dst, false);
    }

    assert_rnp_success(init_mem_dest(&pgpdst, NULL, 0));
    assert_rnp_success(dst_write_src(&src, &pgpdst));
    src_close(&src);
}

TEST_F(rnp_tests, test_stream_deep_packet_nesting)
{
    const char *message = "message";
    pgp_dest_t  dst = {};

    /* add 30 compression layers and 2 encryption - must fail */
    add_openpgp_layers(message, dst, 30, 2, global_ctx);
#ifdef DUMP_TEST_CASE
    /* remove ifdef if you want to write it to stdout */
    pgp_source_t src = {};
    assert_rnp_success(init_mem_src(&src, mem_dest_get_memory(&dst), dst.writeb, false));
    pgp_dest_t outdst = {};
    assert_rnp_success(init_stdout_dest(&outdst));
    assert_rnp_success(rnp_armor_source(&src, &outdst, PGP_ARMORED_MESSAGE));
    dst_close(&outdst, false);
    src_close(&src);
#endif
    /* decrypt it via FFI for less code */
    rnp_ffi_t ffi = NULL;
    rnp_ffi_create(&ffi, "GPG", "GPG");
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));

    rnp_input_t input = NULL;
    assert_rnp_success(rnp_input_from_memory(
      &input, (const uint8_t *) mem_dest_get_memory(&dst), dst.writeb, false));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    dst_close(&dst, false);

    /* add  27 compression & 4 encryption layers - must succeed */
    add_openpgp_layers("message", dst, 27, 4, global_ctx);
#ifdef DUMP_TEST_CASE
    /* remove ifdef if you want to write it to stdout */
    assert_rnp_success(init_mem_src(&src, mem_dest_get_memory(&dst), dst.writeb, false));
    assert_rnp_success(init_stdout_dest(&outdst));
    assert_rnp_success(rnp_armor_source(&src, &outdst, PGP_ARMORED_MESSAGE));
    dst_close(&outdst, false);
    src_close(&src);
#endif
    /* decrypt it via FFI for less code */
    assert_rnp_success(rnp_input_from_memory(
      &input, (const uint8_t *) mem_dest_get_memory(&dst), dst.writeb, false));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    rnp_input_destroy(input);
    /* check output */
    uint8_t *buf = NULL;
    size_t   len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(strlen(message), len);
    assert_int_equal(memcmp(buf, message, len), 0);

    rnp_output_destroy(output);
    dst_close(&dst, false);

    rnp_ffi_destroy(ffi);
}

static bool
src_reader_generator(pgp_source_t *, void *buf, size_t len, size_t *read)
{
    *read = len;
    for (; len; buf = ((uint8_t *) buf) + 1, len--) {
        *(uint8_t *) buf = len & 0x7F;
    }
    return true;
}

TEST_F(rnp_tests, test_stream_cache)
{
    pgp_source_t src = {0};
    uint8_t      sample[sizeof(src.cache->buf)];
    size_t       samplesize = sizeof(sample);
    assert_true(src_reader_generator(NULL, sample, samplesize, &samplesize));
    assert_int_equal(sizeof(sample), samplesize);

    init_src_common(&src, 0);
    int8_t *buf = (int8_t *) src.cache->buf;
    src.read = src_reader_generator;
    size_t len = sizeof(src.cache->buf);

    // empty cache, pos=0
    memset(src.cache->buf, 0xFF, len);
    src.cache->pos = 0;
    src.cache->len = 0;
    assert_true(src_peek_eq(&src, NULL, len));
    assert_false(memcmp(buf, sample, samplesize));

    // empty cache, pos is somewhere in the middle
    memset(src.cache->buf, 0xFF, len);
    src.cache->pos = 100;
    src.cache->len = 100;
    assert_true(src_peek_eq(&src, NULL, len));
    assert_false(memcmp(buf, sample, samplesize));

    // empty cache, pos=max
    memset(src.cache->buf, 0xFF, len);
    src.cache->pos = len;
    src.cache->len = len;
    assert_true(src_peek_eq(&src, NULL, len));
    assert_false(memcmp(buf, sample, samplesize));

    // cache has some data in the middle
    src.cache->pos = 128; // sample boundary
    src.cache->len = 300;
    memset(src.cache->buf, 0xFF, src.cache->pos);
    memset(src.cache->buf + src.cache->len, 0xFF, len - src.cache->len);
    assert_true(src_peek_eq(&src, NULL, len));
    assert_false(memcmp(buf, sample, samplesize));

    // cache has some data starting from pos until the end
    src.cache->pos = 128; // sample boundary
    src.cache->len = len;
    memset(src.cache->buf, 0xFF, src.cache->pos);
    assert_true(src_peek_eq(&src, NULL, len));
    assert_false(memcmp(buf, sample, samplesize));

    // cache is almost full
    src.cache->pos = 0;
    src.cache->len = len - 1;
    src.cache->buf[len - 1] = 0xFF;
    assert_true(src_peek_eq(&src, NULL, len));
    assert_false(memcmp(buf, sample, samplesize));

    // cache is full
    src.cache->pos = 0;
    src.cache->len = len;
    memset(src.cache->buf, 0xFF, src.cache->pos);
    memset(src.cache->buf + src.cache->len, 0xFF, len - src.cache->len);
    assert_true(src_peek_eq(&src, NULL, len));
    assert_false(memcmp(buf, sample, samplesize));

    src_close(&src);
}
