/*
 * Copyright (c) 2017-2023 [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 <fstream>
#include <vector>
#include <string>
#include <set>
#include <utility>
#include <cstdint>

#include <rnp/rnp.h>
#include "rnp_tests.h"
#include "support.h"
#include "librepgp/stream-common.h"
#include "librepgp/stream-packet.h"
#include "librepgp/stream-sig.h"
#include <json.h>
#include "file-utils.h"
#include "str-utils.h"
#include <librepgp/stream-ctx.h>
#include "pgp-key.h"
#include "ffi-priv-types.h"

TEST_F(rnp_tests, test_ffi_homedir)
{
    rnp_ffi_t ffi = NULL;
    char *    pub_format = NULL;
    char *    pub_path = NULL;
    char *    sec_format = NULL;
    char *    sec_path = NULL;

    // get the default homedir (not a very thorough test)
    {
        assert_rnp_failure(rnp_get_default_homedir(NULL));
        char *homedir = NULL;
        assert_rnp_success(rnp_get_default_homedir(&homedir));
        assert_non_null(homedir);
        rnp_buffer_destroy(homedir);
    }

    // check NULL params
    assert_rnp_failure(
      rnp_detect_homedir_info(NULL, &pub_format, &pub_path, &sec_format, &sec_path));
    assert_rnp_failure(
      rnp_detect_homedir_info("data/keyrings/1", NULL, &pub_path, &sec_format, &sec_path));
    assert_rnp_failure(
      rnp_detect_homedir_info("data/keyrings/1", &pub_format, NULL, &sec_format, &sec_path));
    assert_rnp_failure(
      rnp_detect_homedir_info("data/keyrings/1", &pub_format, &pub_path, NULL, &sec_path));
    assert_rnp_failure(
      rnp_detect_homedir_info("data/keyrings/1", &pub_format, &pub_path, &sec_format, NULL));
    // detect the formats+paths
    assert_rnp_success(rnp_detect_homedir_info(
      "data/keyrings/1", &pub_format, &pub_path, &sec_format, &sec_path));
    // check formats
    assert_string_equal(pub_format, "GPG");
    assert_string_equal(sec_format, "GPG");
    // check paths
    assert_string_equal(pub_path, "data/keyrings/1/pubring.gpg");
    assert_string_equal(sec_path, "data/keyrings/1/secring.gpg");
    rnp_buffer_destroy(pub_format);
    rnp_buffer_destroy(pub_path);
    rnp_buffer_destroy(sec_format);
    rnp_buffer_destroy(sec_path);
// detect windows-style slashes
#ifdef _WIN32
    assert_rnp_success(rnp_detect_homedir_info(
      "data\\keyrings\\1", &pub_format, &pub_path, &sec_format, &sec_path));
    // check formats
    assert_string_equal(pub_format, "GPG");
    assert_string_equal(sec_format, "GPG");
    // check paths
    assert_string_equal(pub_path, "data\\keyrings\\1\\pubring.gpg");
    assert_string_equal(sec_path, "data\\keyrings\\1\\secring.gpg");
    rnp_buffer_destroy(pub_format);
    rnp_buffer_destroy(pub_path);
    rnp_buffer_destroy(sec_format);
    rnp_buffer_destroy(sec_path);
#endif
    // detect with the trailing slash
    assert_rnp_success(rnp_detect_homedir_info(
      "data/keyrings/1/", &pub_format, &pub_path, &sec_format, &sec_path));
    // check formats
    assert_string_equal(pub_format, "GPG");
    assert_string_equal(sec_format, "GPG");
    // check paths
    assert_string_equal(pub_path, "data/keyrings/1/pubring.gpg");
    assert_string_equal(sec_path, "data/keyrings/1/secring.gpg");
    // setup FFI with wrong parameters
    assert_rnp_failure(rnp_ffi_create(NULL, "GPG", "GPG"));
    assert_rnp_failure(rnp_ffi_create(&ffi, "GPG", NULL));
    assert_rnp_failure(rnp_ffi_create(&ffi, NULL, "GPG"));
    assert_rnp_failure(rnp_ffi_create(&ffi, "ZZG", "GPG"));
    assert_rnp_failure(rnp_ffi_create(&ffi, "GPG", "ZZG"));
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, pub_format, sec_format));
    // load our keyrings
    assert_true(load_keys_gpg(ffi, pub_path, sec_path));
    // free formats+paths
    rnp_buffer_destroy(pub_format);
    pub_format = NULL;
    rnp_buffer_destroy(pub_path);
    pub_path = NULL;
    rnp_buffer_destroy(sec_format);
    sec_format = NULL;
    rnp_buffer_destroy(sec_path);
    sec_path = NULL;
    // check key counts
    size_t count = 0;
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(7, count);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(7, count);
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;

    // detect the formats+paths
    assert_rnp_success(rnp_detect_homedir_info(
      "data/keyrings/3", &pub_format, &pub_path, &sec_format, &sec_path));
    // check formats
    assert_string_equal(pub_format, "KBX");
    assert_string_equal(sec_format, "G10");
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, pub_format, sec_format));
    // load our keyrings
    assert_true(load_keys_kbx_g10(ffi, pub_path, sec_path));
    // free formats+paths
    rnp_buffer_destroy(pub_format);
    pub_format = NULL;
    rnp_buffer_destroy(pub_path);
    pub_path = NULL;
    rnp_buffer_destroy(sec_format);
    sec_format = NULL;
    rnp_buffer_destroy(sec_path);
    sec_path = NULL;
    // check key counts
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(2, count);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(2, count);
    // check grip (1)
    rnp_key_handle_t key = NULL;
    assert_int_equal(
      RNP_SUCCESS,
      rnp_locate_key(ffi, "grip", "63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59", &key));
    assert_non_null(key);
    char *grip = NULL;
    assert_rnp_success(rnp_key_get_grip(key, &grip));
    assert_non_null(grip);
    assert_string_equal(grip, "63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59");
    rnp_buffer_destroy(grip);
    grip = NULL;
    rnp_key_handle_destroy(key);
    key = NULL;
    // check grip (2)
    assert_int_equal(
      RNP_SUCCESS,
      rnp_locate_key(ffi, "grip", "7EAB41A2F46257C36F2892696F5A2F0432499AD3", &key));
    assert_non_null(key);
    grip = NULL;
    assert_rnp_success(rnp_key_get_grip(key, &grip));
    assert_non_null(grip);
    assert_string_equal(grip, "7EAB41A2F46257C36F2892696F5A2F0432499AD3");
    rnp_buffer_destroy(grip);
    grip = NULL;
    assert_rnp_success(rnp_key_handle_destroy(key));
    key = NULL;
    // cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_detect_key_format)
{
    char *format = NULL;

    // Wrong parameters
    auto data = file_to_vec("data/keyrings/1/pubring.gpg");
    assert_rnp_failure(rnp_detect_key_format(NULL, data.size(), &format));
    assert_rnp_failure(rnp_detect_key_format(data.data(), 0, &format));
    assert_rnp_failure(rnp_detect_key_format(data.data(), data.size(), NULL));
    free(format);

    // GPG
    format = NULL;
    data = file_to_vec("data/keyrings/1/pubring.gpg");
    assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
    assert_string_equal(format, "GPG");
    free(format);
    format = NULL;

    // GPG
    data = file_to_vec("data/keyrings/1/secring.gpg");
    assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
    assert_string_equal(format, "GPG");
    free(format);
    format = NULL;

    // GPG (armored)
    data = file_to_vec("data/keyrings/4/rsav3-p.asc");
    assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
    assert_string_equal(format, "GPG");
    free(format);
    format = NULL;

    // KBX
    data = file_to_vec("data/keyrings/3/pubring.kbx");
    assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
    assert_string_equal(format, "KBX");
    free(format);
    format = NULL;

    // G10
    data = file_to_vec(
      "data/keyrings/3/private-keys-v1.d/63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59.key");
    assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
    assert_string_equal(format, "G10");
    free(format);
    format = NULL;

    // invalid
    assert_rnp_success(rnp_detect_key_format((uint8_t *) "ABC", 3, &format));
    assert_null(format);
}

TEST_F(rnp_tests, test_ffi_load_keys)
{
    rnp_ffi_t   ffi = NULL;
    rnp_input_t input = NULL;
    size_t      count;

    /* load public keys from pubring */
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load pubring
    assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
    // again
    assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
    // check counts
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(7, count);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(0, count);
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;

    /* load public keys from secring */
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load
    assert_true(load_keys_gpg(ffi, "data/keyrings/1/secring.gpg"));
    // check counts
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(7, count);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(0, count);
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;

    /* load secret keys from secring */
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load secring
    assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
    // again
    assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
    // check counts
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(7, count);
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(0, count);
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;

    /* load secret keys from pubring */
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load pubring
    assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/pubring.gpg"));
    // check counts
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(0, count);
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(0, count);
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_ffi_destroy(ffi);
    ffi = NULL;

    /* concatenate the pubring and secrings into a single buffer */
    auto pub_buf = file_to_vec("data/keyrings/1/pubring.gpg");
    auto sec_buf = file_to_vec("data/keyrings/1/secring.gpg");
    auto buf = pub_buf;
    buf.reserve(buf.size() + sec_buf.size());
    buf.insert(buf.end(), sec_buf.begin(), sec_buf.end());

    /* load secret keys from pubring */
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load
    assert_rnp_success(rnp_input_from_memory(&input, buf.data(), buf.size(), true));
    assert_non_null(input);
    // try wrong parameters
    assert_rnp_failure(rnp_load_keys(NULL, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_failure(rnp_load_keys(ffi, NULL, input, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_failure(rnp_load_keys(ffi, "GPG", NULL, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_failure(rnp_load_keys(ffi, "WRONG", input, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_failure(rnp_load_keys(ffi, "WRONG", input, 0));
    assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
    rnp_input_destroy(input);
    input = NULL;
    // again
    assert_rnp_success(rnp_input_from_memory(&input, buf.data(), buf.size(), true));
    assert_non_null(input);
    assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
    rnp_input_destroy(input);
    input = NULL;
    // check counts
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(7, count);
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_ffi_destroy(ffi);
    ffi = NULL;
}

TEST_F(rnp_tests, test_ffi_clear_keys)
{
    rnp_ffi_t ffi = NULL;
    size_t    pub_count;
    size_t    sec_count;

    // setup FFI
    test_ffi_init(&ffi);
    // check counts
    assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
    assert_int_equal(7, pub_count);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
    assert_int_equal(7, sec_count);
    // clear public keys
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
    assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
    assert_int_equal(pub_count, 0);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
    assert_int_equal(sec_count, 7);
    // clear secret keys
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET));
    assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
    assert_int_equal(pub_count, 0);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
    assert_int_equal(sec_count, 0);
    // load public and clear secret keys
    assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET));
    assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
    assert_int_equal(pub_count, 7);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
    assert_int_equal(sec_count, 0);
    // load secret keys and clear all
    assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
    assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
    assert_int_equal(7, pub_count);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
    assert_int_equal(7, sec_count);
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
    assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
    assert_int_equal(pub_count, 0);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
    assert_int_equal(sec_count, 0);
    // attempt to clear NULL ffi
    assert_rnp_failure(rnp_unload_keys(NULL, RNP_KEY_UNLOAD_SECRET));
    // attempt to pass wrong flags
    assert_rnp_failure(rnp_unload_keys(ffi, 255));
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;
}

TEST_F(rnp_tests, test_ffi_save_keys)
{
    rnp_ffi_t ffi = NULL;
    // setup FFI
    test_ffi_init(&ffi);
    char *temp_dir = make_temp_dir();
    // save pubring
    auto pub_path = rnp::path::append(temp_dir, "pubring.gpg");
    assert_false(rnp::path::exists(pub_path));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_path(&output, pub_path.c_str()));
    assert_rnp_failure(rnp_save_keys(NULL, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
    assert_rnp_failure(rnp_save_keys(ffi, NULL, output, RNP_LOAD_SAVE_PUBLIC_KEYS));
    assert_rnp_failure(rnp_save_keys(ffi, "GPG", NULL, RNP_LOAD_SAVE_PUBLIC_KEYS));
    assert_rnp_failure(rnp_save_keys(ffi, "WRONG", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
    assert_rnp_failure(rnp_save_keys(ffi, "GPG", output, 0));
    assert_rnp_success(rnp_save_keys(ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_true(rnp::path::exists(pub_path));
    // save secring
    auto sec_path = rnp::path::append(temp_dir, "secring.gpg");
    assert_false(rnp::path::exists(sec_path));
    assert_rnp_success(rnp_output_to_path(&output, sec_path.c_str()));
    assert_rnp_success(rnp_save_keys(ffi, "GPG", output, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_true(rnp::path::exists(sec_path));
    // save pubring && secring
    auto both_path = rnp::path::append(temp_dir, "bothring.gpg");
    assert_false(rnp::path::exists(both_path));
    assert_rnp_success(rnp_output_to_path(&output, both_path.c_str()));
    assert_int_equal(
      RNP_SUCCESS,
      rnp_save_keys(
        ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_true(rnp::path::exists(both_path));
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;
    // start over (read from the saved locations)
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load pubring & secring
    assert_true(load_keys_gpg(ffi, pub_path, sec_path));
    // check the counts
    size_t count = 0;
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(7, count);
    count = 0;
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(7, count);
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;
    // load both keyrings from the single file
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load pubring
    rnp_input_t input = NULL;
    assert_rnp_success(rnp_input_from_path(&input, both_path.c_str()));
    assert_non_null(input);
    assert_int_equal(
      RNP_SUCCESS,
      rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS));
    rnp_input_destroy(input);
    input = NULL;
    // check the counts. We should get both secret and public keys, since public keys are
    // extracted from the secret ones.
    count = 0;
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(7, count);
    count = 0;
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(7, count);
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
    // load pubring & secring
    assert_true(load_keys_kbx_g10(
      ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d"));
    // save pubring
    pub_path = rnp::path::append(temp_dir, "pubring.kbx");
    assert_rnp_success(rnp_output_to_path(&output, pub_path.c_str()));
    assert_rnp_success(rnp_save_keys(ffi, "KBX", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_true(rnp::path::exists(pub_path));
    // save secring to file - will fail for G10
    sec_path = rnp::path::append(temp_dir, "secring.file");
    assert_rnp_success(rnp_output_to_path(&output, sec_path.c_str()));
    assert_rnp_failure(rnp_save_keys(ffi, "G10", output, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    // save secring
    sec_path = rnp::path::append(temp_dir, "private-keys-v1.d");
    assert_false(rnp::path::exists(sec_path, true));
    assert_int_equal(0, RNP_MKDIR(sec_path.c_str(), S_IRWXU));
    assert_rnp_success(rnp_output_to_path(&output, sec_path.c_str()));
    assert_rnp_success(rnp_save_keys(ffi, "G10", output, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_true(rnp::path::exists(sec_path, true));
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;
    // start over (read from the saved locations)
    assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
    // load pubring & secring
    assert_true(load_keys_kbx_g10(ffi, pub_path, sec_path));
    // check the counts
    count = 0;
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(2, count);
    count = 0;
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(2, count);
    // cleanup
    rnp_ffi_destroy(ffi);

    // final cleanup
    clean_temp_dir(temp_dir);
    free(temp_dir);
}

TEST_F(rnp_tests, test_ffi_load_save_keys_to_utf8_path)
{
    const char kbx_pubring_utf8_filename[] = "pubring_\xC2\xA2.kbx";
    const char g10_secring_utf8_dirname[] = "private-keys-\xC2\xA2.d";
    const char utf8_filename[] = "bothring_\xC2\xA2.gpg";

    // setup FFI
    rnp_ffi_t ffi = NULL;
    test_ffi_init(&ffi);
    auto temp_dir = make_temp_dir();
    // save pubring && secring
    auto both_path = rnp::path::append(temp_dir, utf8_filename);
    assert_false(rnp::path::exists(both_path));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_path(&output, both_path.c_str()));
    assert_rnp_success(rnp_save_keys(
      ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_true(rnp::path::exists(both_path));
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;
    // start over (read from the saved locations)
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load both keyrings from the single file
    rnp_input_t input = NULL;
    assert_rnp_success(rnp_input_from_path(&input, both_path.c_str()));
    assert_non_null(input);
    assert_rnp_success(
      rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS));
    rnp_input_destroy(input);
    input = NULL;
    // check the counts. We should get both secret and public keys, since public keys are
    // extracted from the secret ones.
    size_t count = 0;
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(7, count);
    count = 0;
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(7, count);
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
    // load pubring
    assert_true(load_keys_kbx_g10(
      ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d"));
    // save pubring
    auto pub_path = rnp::path::append(temp_dir, kbx_pubring_utf8_filename);
    assert_rnp_success(rnp_output_to_path(&output, pub_path.c_str()));
    assert_rnp_success(rnp_save_keys(ffi, "KBX", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_true(rnp::path::exists(pub_path));
    // save secring
    auto sec_path = rnp::path::append(temp_dir, g10_secring_utf8_dirname);
    assert_false(rnp::path::exists(sec_path, true));
    assert_int_equal(0, RNP_MKDIR(sec_path.c_str(), S_IRWXU));
    assert_rnp_success(rnp_output_to_path(&output, sec_path.c_str()));
    assert_rnp_success(rnp_save_keys(ffi, "G10", output, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_true(rnp::path::exists(sec_path, true));
    // cleanup
    rnp_ffi_destroy(ffi);
    ffi = NULL;
    // start over (read from the saved locations)
    assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
    // load pubring & secring
    assert_true(load_keys_kbx_g10(ffi, pub_path, sec_path));
    // check the counts
    count = 0;
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(2, count);
    count = 0;
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(2, count);
    // cleanup
    rnp_ffi_destroy(ffi);

    // final cleanup
    clean_temp_dir(temp_dir);
    free(temp_dir);
}

static size_t
get_longest_line_length(const std::string &str, const std::set<std::string> lines_to_skip)
{
    // eol could be \n or \r\n
    size_t index = 0;
    size_t max_len = 0;
    for (;;) {
        auto new_index = str.find('\n', index);
        if (new_index == std::string::npos) {
            break;
        }
        size_t line_length = new_index - index;
        if (str[new_index - 1] == '\r') {
            line_length--;
        }
        if (line_length > max_len &&
            lines_to_skip.find(str.substr(index, line_length)) == lines_to_skip.end()) {
            max_len = line_length;
        }
        index = new_index + 1;
    }
    return max_len;
}

TEST_F(rnp_tests, test_ffi_add_userid)
{
    rnp_ffi_t              ffi = NULL;
    char *                 results = NULL;
    size_t                 count = 0;
    rnp_uid_handle_t       uid;
    rnp_signature_handle_t sig;
    char *                 hash_alg_name = NULL;

    const char *new_userid = "my new userid <user@example.com>";
    const char *default_hash_userid = "default hash <user@example.com";
    const char *ripemd_hash_userid = "ripemd160 <user@example.com";

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL));

    // load our JSON
    auto json = file_to_str("data/test_ffi_json/generate-primary.json");

    // generate the keys
    assert_rnp_success(rnp_generate_key_json(ffi, json.c_str(), &results));
    assert_non_null(results);
    rnp_buffer_destroy(results);
    results = NULL;

    // check the key counts
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(1, count);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(1, count);

    rnp_key_handle_t key_handle = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "test0", &key_handle));
    assert_non_null(key_handle);

    assert_rnp_success(rnp_key_get_uid_count(key_handle, &count));
    assert_int_equal(1, count);

    // protect+lock the key
    if (!sm2_enabled()) {
        assert_rnp_failure(rnp_key_protect(key_handle, "pass", "SM4", "CFB", "SM3", 999999));
        assert_rnp_success(
          rnp_key_protect(key_handle, "pass", "AES128", "CFB", "SHA256", 999999));
    } else {
        assert_rnp_success(rnp_key_protect(key_handle, "pass", "SM4", "CFB", "SM3", 999999));
    }
    assert_rnp_success(rnp_key_lock(key_handle));

    // add with NULL parameters
    assert_rnp_failure(rnp_key_add_uid(NULL, new_userid, NULL, 2147317200, 0x00, false));
    assert_rnp_failure(rnp_key_add_uid(key_handle, NULL, NULL, 2147317200, 0x00, false));

    // add the userid (no pass provider, should fail)
    assert_int_equal(
      RNP_ERROR_BAD_PASSWORD,
      rnp_key_add_uid(key_handle, new_userid, "SHA256", 2147317200, 0x00, false));

    // actually add the userid
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass"));
    // attempt to add empty uid
    assert_rnp_failure(rnp_key_add_uid(key_handle, "", NULL, 2147317200, 0, false));
    // add with default hash algorithm
    assert_rnp_success(
      rnp_key_add_uid(key_handle, default_hash_userid, NULL, 2147317200, 0, false));
    // check whether key was locked back
    bool locked = false;
    assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
    assert_true(locked);
    // check if default hash was used
    assert_rnp_success(rnp_key_get_uid_handle_at(key_handle, 1, &uid));
    assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
    assert_rnp_success(rnp_signature_get_hash_alg(sig, &hash_alg_name));
    assert_int_equal(strcasecmp(hash_alg_name, DEFAULT_HASH_ALG), 0);
    rnp_buffer_destroy(hash_alg_name);
    hash_alg_name = NULL;
    assert_rnp_success(rnp_signature_handle_destroy(sig));
    assert_rnp_success(rnp_uid_handle_destroy(uid));

    assert_int_equal(
      RNP_SUCCESS, rnp_key_add_uid(key_handle, new_userid, "SHA256", 2147317200, 0x00, false));

    int uid_count_expected = 3;
    int res =
      rnp_key_add_uid(key_handle, ripemd_hash_userid, "RIPEMD160", 2147317200, 0, false);
    if (ripemd160_enabled()) {
        assert_rnp_success(res);
        uid_count_expected++;
    } else {
        assert_rnp_failure(res);
    }

    assert_rnp_success(rnp_key_get_uid_count(key_handle, &count));
    assert_int_equal(uid_count_expected, count);

    rnp_key_handle_t key_handle2 = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", new_userid, &key_handle2));
    assert_non_null(key_handle2);

    rnp_key_handle_destroy(key_handle);
    rnp_key_handle_destroy(key_handle2);
    rnp_ffi_destroy(ffi);
}

static bool
file_equals(const char *filename, const void *data, size_t len)
{
    pgp_source_t msrc = {};
    bool         res = false;

    if (file_to_mem_src(&msrc, filename)) {
        return false;
    }

    res = (msrc.size == len) && !memcmp(mem_src_get_memory(&msrc), data, len);
    src_close(&msrc);
    return res;
}

static void
test_ffi_init_sign_file_input(rnp_input_t *input, rnp_output_t *output)
{
    const char *plaintext = "this is some data that will be signed";

    // write out some data
    str_to_file("plaintext", plaintext);
    // create input+output
    assert_rnp_success(rnp_input_from_path(input, "plaintext"));
    assert_non_null(*input);
    assert_rnp_success(rnp_output_to_path(output, "signed"));
    assert_non_null(*output);
}

static void
test_ffi_init_sign_memory_input(rnp_input_t *input, rnp_output_t *output)
{
    const char *plaintext = "this is some data that will be signed";

    assert_rnp_success(
      rnp_input_from_memory(input, (uint8_t *) plaintext, strlen(plaintext), true));
    assert_non_null(*input);
    if (output) {
        assert_rnp_success(rnp_output_to_memory(output, 0));
        assert_non_null(*output);
    }
}

static void
test_ffi_init_verify_file_input(rnp_input_t *input, rnp_output_t *output)
{
    // create input+output
    assert_rnp_success(rnp_input_from_path(input, "signed"));
    assert_non_null(*input);
    assert_rnp_success(rnp_output_to_path(output, "recovered"));
    assert_non_null(*output);
}

static void
test_ffi_init_verify_detached_file_input(rnp_input_t *input, rnp_input_t *signature)
{
    assert_rnp_success(rnp_input_from_path(input, "plaintext"));
    assert_non_null(*input);
    assert_rnp_success(rnp_input_from_path(signature, "signed"));
    assert_non_null(*signature);
}

static void
test_ffi_init_verify_memory_input(rnp_input_t * input,
                                  rnp_output_t *output,
                                  uint8_t *     signed_buf,
                                  size_t        signed_len)
{
    // create input+output
    assert_rnp_success(rnp_input_from_memory(input, signed_buf, signed_len, false));
    assert_non_null(*input);
    assert_rnp_success(rnp_output_to_memory(output, 0));
    assert_non_null(*output);
}

static void
test_ffi_setup_signatures(rnp_ffi_t *ffi, rnp_op_sign_t *op)
{
    rnp_key_handle_t        key = NULL;
    rnp_op_sign_signature_t sig = NULL;
    // set signature times
    const uint32_t issued = 1516211899;   // Unix epoch, nowish
    const uint32_t expires = 1000000000;  // expires later
    const uint32_t issued2 = 1516211900;  // Unix epoch, nowish
    const uint32_t expires2 = 2000000000; // expires later

    assert_rnp_success(rnp_op_sign_set_armor(*op, true));
    assert_rnp_success(rnp_op_sign_set_hash(*op, "SHA256"));
    assert_rnp_success(rnp_op_sign_set_creation_time(*op, issued));
    assert_rnp_success(rnp_op_sign_set_expiration_time(*op, expires));

    // set pass provider
    assert_rnp_success(
      rnp_ffi_set_pass_provider(*ffi, ffi_string_password_provider, (void *) "password"));

    // set first signature key
    assert_rnp_success(rnp_locate_key(*ffi, "userid", "key0-uid2", &key));
    assert_rnp_success(rnp_op_sign_add_signature(*op, key, NULL));
    assert_rnp_success(rnp_key_handle_destroy(key));
    key = NULL;
    // set second signature key
    assert_rnp_success(rnp_locate_key(*ffi, "userid", "key0-uid1", &key));
    assert_rnp_success(rnp_op_sign_add_signature(*op, key, &sig));
    assert_rnp_success(rnp_op_sign_signature_set_creation_time(sig, issued2));
    assert_rnp_success(rnp_op_sign_signature_set_expiration_time(sig, expires2));
    assert_rnp_success(rnp_op_sign_signature_set_hash(sig, "SHA512"));
    assert_rnp_success(rnp_key_handle_destroy(key));
}

static void
test_ffi_check_signatures(rnp_op_verify_t *verify)
{
    rnp_op_verify_signature_t sig;
    size_t                    sig_count;
    uint32_t                  sig_create;
    uint32_t                  sig_expires;
    char *                    hname = NULL;
    const uint32_t            issued = 1516211899;   // Unix epoch, nowish
    const uint32_t            expires = 1000000000;  // expires later
    const uint32_t            issued2 = 1516211900;  // Unix epoch, nowish
    const uint32_t            expires2 = 2000000000; // expires later

    assert_rnp_success(rnp_op_verify_get_signature_count(*verify, &sig_count));
    assert_int_equal(sig_count, 2);
    // first signature
    assert_rnp_success(rnp_op_verify_get_signature_at(*verify, 0, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    assert_rnp_success(rnp_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
    assert_int_equal(sig_create, issued);
    assert_int_equal(sig_expires, expires);
    assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
    assert_string_equal(hname, "SHA256");
    rnp_buffer_destroy(hname);
    // second signature
    assert_rnp_success(rnp_op_verify_get_signature_at(*verify, 1, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    assert_rnp_success(rnp_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
    assert_int_equal(sig_create, issued2);
    assert_int_equal(sig_expires, expires2);
    assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
    assert_string_equal(hname, "SHA512");
    rnp_buffer_destroy(hname);
}

static bool
test_ffi_check_recovered()
{
    pgp_source_t msrc1 = {};
    pgp_source_t msrc2 = {};
    bool         res = false;

    if (file_to_mem_src(&msrc1, "recovered")) {
        return false;
    }

    if (file_to_mem_src(&msrc2, "plaintext")) {
        goto finish;
    }

    res = (msrc1.size == msrc2.size) &&
          !memcmp(mem_src_get_memory(&msrc1), mem_src_get_memory(&msrc2), msrc1.size);
finish:
    src_close(&msrc1);
    src_close(&msrc2);
    return res;
}

TEST_F(rnp_tests, test_ffi_signatures_memory)
{
    rnp_ffi_t       ffi = NULL;
    rnp_input_t     input = NULL;
    rnp_output_t    output = NULL;
    rnp_op_sign_t   op = NULL;
    rnp_op_verify_t verify;
    uint8_t *       signed_buf;
    size_t          signed_len;
    uint8_t *       verified_buf;
    size_t          verified_len;

    // init ffi
    test_ffi_init(&ffi);
    // init input
    test_ffi_init_sign_memory_input(&input, &output);
    // create signature operation
    assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
    // setup signature(s)
    test_ffi_setup_signatures(&ffi, &op);
    // execute the operation
    assert_rnp_success(rnp_op_sign_execute(op));
    // make sure the output file was created
    assert_rnp_failure(rnp_output_memory_get_buf(NULL, &signed_buf, &signed_len, true));
    assert_rnp_failure(rnp_output_memory_get_buf(output, NULL, &signed_len, true));
    assert_rnp_failure(rnp_output_memory_get_buf(output, &signed_buf, NULL, true));
    assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
    assert_non_null(signed_buf);
    assert_true(signed_len > 0);

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_sign_destroy(op));
    op = NULL;

    /* now verify */
    // make sure it is correctly armored
    assert_int_equal(memcmp(signed_buf, "-----BEGIN PGP MESSAGE-----", 27), 0);
    // create input and output
    test_ffi_init_verify_memory_input(&input, &output, signed_buf, signed_len);
    // call verify
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    // check signatures
    test_ffi_check_signatures(&verify);
    // get output
    assert_rnp_success(rnp_output_memory_get_buf(output, &verified_buf, &verified_len, true));
    assert_non_null(verified_buf);
    assert_true(verified_len > 0);
    // cleanup
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_ffi_destroy(ffi));
    rnp_buffer_destroy(signed_buf);
    rnp_buffer_destroy(verified_buf);
}

TEST_F(rnp_tests, test_ffi_signatures)
{
    rnp_ffi_t       ffi = NULL;
    rnp_input_t     input = NULL;
    rnp_output_t    output = NULL;
    rnp_op_sign_t   op = NULL;
    rnp_op_verify_t verify;

    // init ffi
    test_ffi_init(&ffi);
    // init file input
    test_ffi_init_sign_file_input(&input, &output);
    // create signature operation
    assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
    // setup signature(s)
    test_ffi_setup_signatures(&ffi, &op);
    // execute the operation
    assert_rnp_success(rnp_op_sign_execute(op));
    // make sure the output file was created
    assert_true(rnp_file_exists("signed"));

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_sign_destroy(op));
    op = NULL;

    /* now verify */

    // create input and output
    test_ffi_init_verify_file_input(&input, &output);
    // call verify
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    // check signatures
    test_ffi_check_signatures(&verify);
    // cleanup
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_ffi_destroy(ffi));
    // check output
    assert_true(test_ffi_check_recovered());
}

TEST_F(rnp_tests, test_ffi_signatures_detached_memory)
{
    rnp_ffi_t       ffi = NULL;
    rnp_input_t     input = NULL;
    rnp_input_t     signature = NULL;
    rnp_output_t    output = NULL;
    rnp_op_sign_t   op = NULL;
    rnp_op_verify_t verify;
    uint8_t *       signed_buf;
    size_t          signed_len;

    // init ffi
    test_ffi_init(&ffi);
    // init input
    test_ffi_init_sign_memory_input(&input, &output);
    // create signature operation
    assert_rnp_success(rnp_op_sign_detached_create(&op, ffi, input, output));
    // setup signature(s)
    test_ffi_setup_signatures(&ffi, &op);
    // execute the operation
    assert_rnp_success(rnp_op_sign_execute(op));
    assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
    assert_non_null(signed_buf);
    assert_true(signed_len > 0);

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_sign_destroy(op));
    op = NULL;

    /* now verify */
    // make sure it is correctly armored
    assert_int_equal(memcmp(signed_buf, "-----BEGIN PGP SIGNATURE-----", 29), 0);
    // create input and output
    test_ffi_init_sign_memory_input(&input, NULL);
    assert_rnp_success(rnp_input_from_memory(&signature, signed_buf, signed_len, true));
    assert_non_null(signature);
    // call verify
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
    assert_rnp_success(rnp_op_verify_execute(verify));
    // check signatures
    test_ffi_check_signatures(&verify);
    // cleanup
    rnp_buffer_destroy(signed_buf);
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_input_destroy(signature));
    signature = NULL;
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_ffi_signatures_detached)
{
    rnp_ffi_t       ffi = NULL;
    rnp_input_t     input = NULL;
    rnp_input_t     signature = NULL;
    rnp_output_t    output = NULL;
    rnp_op_sign_t   op = NULL;
    rnp_op_verify_t verify;

    // init ffi
    test_ffi_init(&ffi);
    // init file input
    test_ffi_init_sign_file_input(&input, &output);
    // create signature operation
    assert_rnp_success(rnp_op_sign_detached_create(&op, ffi, input, output));
    // setup signature(s)
    test_ffi_setup_signatures(&ffi, &op);
    // execute the operation
    assert_rnp_success(rnp_op_sign_execute(op));
    // make sure the output file was created
    assert_true(rnp_file_exists("signed"));

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_sign_destroy(op));
    op = NULL;

    /* now verify */

    // create input and output
    test_ffi_init_verify_detached_file_input(&input, &signature);
    // call verify
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
    assert_rnp_success(rnp_op_verify_execute(verify));
    // check signatures
    test_ffi_check_signatures(&verify);
    // cleanup
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_input_destroy(signature));
    signature = NULL;
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_ffi_signatures_dump)
{
    rnp_ffi_t       ffi = NULL;
    rnp_input_t     input = NULL;
    rnp_input_t     signature = NULL;
    rnp_op_verify_t verify;

    /* init ffi and inputs */
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    load_keys_gpg(ffi, "data/test_stream_signatures/pub.asc");
    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_signatures/source.txt"));
    assert_rnp_success(
      rnp_input_from_path(&signature, "data/test_stream_signatures/source.txt.sig"));
    /* call verify detached to obtain signatures */
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
    assert_rnp_success(rnp_op_verify_execute(verify));
    /* get signature and check it */
    rnp_op_verify_signature_t sig;
    size_t                    sig_count;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
    assert_int_equal(sig_count, 1);
    /* get signature handle  */
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    rnp_signature_handle_t sighandle = NULL;
    assert_rnp_success(rnp_op_verify_signature_get_handle(sig, &sighandle));
    assert_non_null(sighandle);
    /* check signature type */
    char *sigtype = NULL;
    assert_rnp_success(rnp_signature_get_type(sighandle, &sigtype));
    assert_string_equal(sigtype, "binary");
    rnp_buffer_destroy(sigtype);
    /* attempt to validate it via wrong function */
    assert_int_equal(rnp_signature_is_valid(sighandle, 0), RNP_ERROR_BAD_PARAMETERS);
    /* cleanup, making sure that sighandle doesn't depend on verify */
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_input_destroy(signature));
    /* check whether getters work on sighandle: algorithm */
    char *alg = NULL;
    assert_rnp_success(rnp_signature_get_alg(sighandle, &alg));
    assert_non_null(alg);
    assert_string_equal(alg, "RSA");
    rnp_buffer_destroy(alg);
    /* keyid */
    char *keyid = NULL;
    assert_rnp_success(rnp_signature_get_keyid(sighandle, &keyid));
    assert_non_null(keyid);
    assert_string_equal(keyid, "5873BD738E575398");
    rnp_buffer_destroy(keyid);
    /* creation time */
    uint32_t create = 0;
    assert_rnp_success(rnp_signature_get_creation(sighandle, &create));
    assert_int_equal(create, 1522241943);
    /* hash algorithm */
    assert_rnp_success(rnp_signature_get_hash_alg(sighandle, &alg));
    assert_non_null(alg);
    assert_string_equal(alg, "SHA256");
    rnp_buffer_destroy(alg);
    /* now dump signature packet to json */
    char *json = NULL;
    assert_rnp_success(rnp_signature_packet_to_json(sighandle, 0, &json));
    json_object *jso = json_tokener_parse(json);
    rnp_buffer_destroy(json);
    assert_non_null(jso);
    assert_true(json_object_is_type(jso, json_type_array));
    assert_int_equal(json_object_array_length(jso), 1);
    /* check the signature packet dump */
    json_object *pkt = json_object_array_get_idx(jso, 0);
    /* check helper functions */
    assert_false(check_json_field_int(pkt, "unknown", 4));
    assert_false(check_json_field_int(pkt, "version", 5));
    assert_true(check_json_field_int(pkt, "version", 4));
    assert_true(check_json_field_int(pkt, "type", 0));
    assert_true(check_json_field_str(pkt, "type.str", "Signature of a binary document"));
    assert_true(check_json_field_int(pkt, "algorithm", 1));
    assert_true(check_json_field_str(pkt, "algorithm.str", "RSA (Encrypt or Sign)"));
    assert_true(check_json_field_int(pkt, "hash algorithm", 8));
    assert_true(check_json_field_str(pkt, "hash algorithm.str", "SHA256"));
    assert_true(check_json_field_str(pkt, "lbits", "816e"));
    json_object *subpkts = NULL;
    assert_true(json_object_object_get_ex(pkt, "subpackets", &subpkts));
    assert_non_null(subpkts);
    assert_true(json_object_is_type(subpkts, json_type_array));
    assert_int_equal(json_object_array_length(subpkts), 3);
    /* subpacket 0 */
    json_object *subpkt = json_object_array_get_idx(subpkts, 0);
    assert_true(check_json_field_int(subpkt, "type", 33));
    assert_true(check_json_field_str(subpkt, "type.str", "issuer fingerprint"));
    assert_true(check_json_field_int(subpkt, "length", 21));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(
      check_json_field_str(subpkt, "fingerprint", "7a60e671179f9b920f6478a25873bd738e575398"));
    /* subpacket 1 */
    subpkt = json_object_array_get_idx(subpkts, 1);
    assert_true(check_json_field_int(subpkt, "type", 2));
    assert_true(check_json_field_str(subpkt, "type.str", "signature creation time"));
    assert_true(check_json_field_int(subpkt, "length", 4));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(check_json_field_int(subpkt, "creation time", 1522241943));
    /* subpacket 2 */
    subpkt = json_object_array_get_idx(subpkts, 2);
    assert_true(check_json_field_int(subpkt, "type", 16));
    assert_true(check_json_field_str(subpkt, "type.str", "issuer key ID"));
    assert_true(check_json_field_int(subpkt, "length", 8));
    assert_true(check_json_field_bool(subpkt, "hashed", false));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(check_json_field_str(subpkt, "issuer keyid", "5873bd738e575398"));
    json_object_put(jso);
    rnp_signature_handle_destroy(sighandle);
    /* check text-mode detached signature */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_signatures/source.txt"));
    assert_rnp_success(
      rnp_input_from_path(&signature, "data/test_stream_signatures/source.txt.text.sig"));
    /* call verify detached to obtain signatures */
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
    assert_rnp_success(rnp_op_verify_execute(verify));
    /* get signature and check it */
    sig_count = 0;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
    assert_int_equal(sig_count, 1);
    /* get signature handle  */
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    assert_rnp_success(rnp_op_verify_signature_get_handle(sig, &sighandle));
    assert_non_null(sighandle);
    /* check signature type */
    assert_rnp_success(rnp_signature_get_type(sighandle, &sigtype));
    assert_string_equal(sigtype, "text");
    rnp_buffer_destroy(sigtype);
    /* attempt to validate it via wrong function */
    assert_int_equal(rnp_signature_is_valid(sighandle, 0), RNP_ERROR_BAD_PARAMETERS);
    /* cleanup, making sure that sighandle doesn't depend on verify */
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_input_destroy(signature));
    /* check whether getters work on sighandle: algorithm */
    assert_rnp_success(rnp_signature_get_alg(sighandle, &alg));
    assert_non_null(alg);
    assert_string_equal(alg, "RSA");
    rnp_buffer_destroy(alg);
    /* keyid */
    assert_rnp_success(rnp_signature_get_keyid(sighandle, &keyid));
    assert_non_null(keyid);
    assert_string_equal(keyid, "5873BD738E575398");
    rnp_buffer_destroy(keyid);
    /* creation time */
    assert_rnp_success(rnp_signature_get_creation(sighandle, &create));
    assert_int_equal(create, 1608118321);
    /* hash algorithm */
    assert_rnp_success(rnp_signature_get_hash_alg(sighandle, &alg));
    assert_non_null(alg);
    assert_string_equal(alg, "SHA256");
    rnp_buffer_destroy(alg);
    /* now dump signature packet to json */
    assert_rnp_success(rnp_signature_packet_to_json(sighandle, 0, &json));
    jso = json_tokener_parse(json);
    rnp_buffer_destroy(json);
    assert_non_null(jso);
    assert_true(json_object_is_type(jso, json_type_array));
    assert_int_equal(json_object_array_length(jso), 1);
    /* check the signature packet dump */
    pkt = json_object_array_get_idx(jso, 0);
    /* check helper functions */
    assert_false(check_json_field_int(pkt, "unknown", 4));
    assert_false(check_json_field_int(pkt, "version", 5));
    assert_true(check_json_field_int(pkt, "version", 4));
    assert_true(check_json_field_int(pkt, "type", 1));
    assert_true(
      check_json_field_str(pkt, "type.str", "Signature of a canonical text document"));
    assert_true(check_json_field_int(pkt, "algorithm", 1));
    assert_true(check_json_field_str(pkt, "algorithm.str", "RSA (Encrypt or Sign)"));
    assert_true(check_json_field_int(pkt, "hash algorithm", 8));
    assert_true(check_json_field_str(pkt, "hash algorithm.str", "SHA256"));
    assert_true(check_json_field_str(pkt, "lbits", "1037"));
    subpkts = NULL;
    assert_true(json_object_object_get_ex(pkt, "subpackets", &subpkts));
    assert_non_null(subpkts);
    assert_true(json_object_is_type(subpkts, json_type_array));
    assert_int_equal(json_object_array_length(subpkts), 3);
    /* subpacket 0 */
    subpkt = json_object_array_get_idx(subpkts, 0);
    assert_true(check_json_field_int(subpkt, "type", 33));
    assert_true(check_json_field_str(subpkt, "type.str", "issuer fingerprint"));
    assert_true(check_json_field_int(subpkt, "length", 21));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(
      check_json_field_str(subpkt, "fingerprint", "7a60e671179f9b920f6478a25873bd738e575398"));
    /* subpacket 1 */
    subpkt = json_object_array_get_idx(subpkts, 1);
    assert_true(check_json_field_int(subpkt, "type", 2));
    assert_true(check_json_field_str(subpkt, "type.str", "signature creation time"));
    assert_true(check_json_field_int(subpkt, "length", 4));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(check_json_field_int(subpkt, "creation time", 1608118321));
    /* subpacket 2 */
    subpkt = json_object_array_get_idx(subpkts, 2);
    assert_true(check_json_field_int(subpkt, "type", 16));
    assert_true(check_json_field_str(subpkt, "type.str", "issuer key ID"));
    assert_true(check_json_field_int(subpkt, "length", 8));
    assert_true(check_json_field_bool(subpkt, "hashed", false));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(check_json_field_str(subpkt, "issuer keyid", "5873bd738e575398"));
    json_object_put(jso);
    rnp_signature_handle_destroy(sighandle);

    /* attempt to validate a timestamp signature instead of detached */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_signatures/source.txt"));
    assert_rnp_success(
      rnp_input_from_path(&signature, "data/test_stream_signatures/signature-timestamp.asc"));
    /* call verify detached to obtain signatures */
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
    assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
    /* get signature and check it */
    sig_count = 0;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
    assert_int_equal(sig_count, 1);
    /* get signature handle  */
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
    assert_int_equal(rnp_op_verify_signature_get_status(sig), RNP_ERROR_SIGNATURE_INVALID);
    assert_rnp_success(rnp_op_verify_signature_get_handle(sig, &sighandle));
    assert_non_null(sighandle);
    /* check signature type */
    assert_rnp_success(rnp_signature_get_type(sighandle, &sigtype));
    assert_string_equal(sigtype, "timestamp");
    rnp_buffer_destroy(sigtype);
    /* attempt to validate it via wrong function */
    assert_int_equal(rnp_signature_is_valid(sighandle, 0), RNP_ERROR_BAD_PARAMETERS);
    /* cleanup, making sure that sighandle doesn't depend on verify */
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_input_destroy(signature));
    /* check whether getters work on sighandle: algorithm */
    assert_rnp_success(rnp_signature_get_alg(sighandle, &alg));
    assert_non_null(alg);
    assert_string_equal(alg, "DSA");
    rnp_buffer_destroy(alg);
    /* keyid */
    assert_rnp_success(rnp_signature_get_keyid(sighandle, &keyid));
    assert_non_null(keyid);
    assert_string_equal(keyid, "2D727CC768697734");
    rnp_buffer_destroy(keyid);
    /* creation time */
    assert_rnp_success(rnp_signature_get_creation(sighandle, &create));
    assert_int_equal(create, 1535389094);
    /* hash algorithm */
    assert_rnp_success(rnp_signature_get_hash_alg(sighandle, &alg));
    assert_non_null(alg);
    assert_string_equal(alg, "SHA512");
    rnp_buffer_destroy(alg);
    /* now dump signature packet to json */
    assert_rnp_success(rnp_signature_packet_to_json(sighandle, 0, &json));
    jso = json_tokener_parse(json);
    rnp_buffer_destroy(json);
    assert_non_null(jso);
    assert_true(json_object_is_type(jso, json_type_array));
    assert_int_equal(json_object_array_length(jso), 1);
    /* check the signature packet dump */
    pkt = json_object_array_get_idx(jso, 0);
    /* check helper functions */
    assert_false(check_json_field_int(pkt, "unknown", 4));
    assert_false(check_json_field_int(pkt, "version", 5));
    assert_true(check_json_field_int(pkt, "version", 4));
    assert_true(check_json_field_int(pkt, "type", 0x40));
    assert_true(check_json_field_str(pkt, "type.str", "Timestamp signature"));
    assert_true(check_json_field_int(pkt, "algorithm", 17));
    assert_true(check_json_field_str(pkt, "algorithm.str", "DSA"));
    assert_true(check_json_field_int(pkt, "hash algorithm", 10));
    assert_true(check_json_field_str(pkt, "hash algorithm.str", "SHA512"));
    assert_true(check_json_field_str(pkt, "lbits", "2727"));
    subpkts = NULL;
    assert_true(json_object_object_get_ex(pkt, "subpackets", &subpkts));
    assert_non_null(subpkts);
    assert_true(json_object_is_type(subpkts, json_type_array));
    assert_int_equal(json_object_array_length(subpkts), 7);
    /* subpacket 0 */
    subpkt = json_object_array_get_idx(subpkts, 0);
    assert_true(check_json_field_int(subpkt, "type", 2));
    assert_true(check_json_field_str(subpkt, "type.str", "signature creation time"));
    assert_true(check_json_field_int(subpkt, "length", 4));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", true));
    assert_true(check_json_field_int(subpkt, "creation time", 1535389094));
    /* subpacket 1 */
    subpkt = json_object_array_get_idx(subpkts, 1);
    assert_true(check_json_field_int(subpkt, "type", 7));
    assert_true(check_json_field_str(subpkt, "type.str", "revocable"));
    assert_true(check_json_field_int(subpkt, "length", 1));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", true));
    assert_true(check_json_field_bool(subpkt, "revocable", false));
    /* subpacket 2 */
    subpkt = json_object_array_get_idx(subpkts, 2);
    assert_true(check_json_field_int(subpkt, "type", 16));
    assert_true(check_json_field_str(subpkt, "type.str", "issuer key ID"));
    assert_true(check_json_field_int(subpkt, "length", 8));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", true));
    assert_true(check_json_field_str(subpkt, "issuer keyid", "2d727cc768697734"));
    /* subpacket 3 */
    subpkt = json_object_array_get_idx(subpkts, 3);
    assert_true(check_json_field_int(subpkt, "type", 20));
    assert_true(check_json_field_str(subpkt, "type.str", "notation data"));
    assert_true(check_json_field_int(subpkt, "length", 51));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(check_json_field_bool(subpkt, "human", true));
    assert_true(check_json_field_str(subpkt, "name", "serialnumber@dots.testdomain.test"));
    assert_true(check_json_field_str(subpkt, "value", "TEST000001"));
    /* subpacket 4 */
    subpkt = json_object_array_get_idx(subpkts, 4);
    assert_true(check_json_field_int(subpkt, "type", 26));
    assert_true(check_json_field_str(subpkt, "type.str", "policy URI"));
    assert_true(check_json_field_int(subpkt, "length", 44));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(
      check_json_field_str(subpkt, "uri", "https://policy.testdomain.test/timestamping/"));
    /* subpacket 5 */
    subpkt = json_object_array_get_idx(subpkts, 5);
    assert_true(check_json_field_int(subpkt, "type", 32));
    assert_true(check_json_field_str(subpkt, "type.str", "embedded signature"));
    assert_true(check_json_field_int(subpkt, "length", 105));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", true));
    json_object *embsig = NULL;
    assert_true(json_object_object_get_ex(subpkt, "signature", &embsig));
    assert_true(check_json_field_int(embsig, "version", 4));
    assert_true(check_json_field_int(embsig, "type", 0));
    assert_true(check_json_field_str(embsig, "type.str", "Signature of a binary document"));
    assert_true(check_json_field_int(embsig, "algorithm", 17));
    assert_true(check_json_field_str(embsig, "algorithm.str", "DSA"));
    assert_true(check_json_field_int(embsig, "hash algorithm", 10));
    assert_true(check_json_field_str(embsig, "hash algorithm.str", "SHA512"));
    assert_true(check_json_field_str(embsig, "lbits", "a386"));
    /* subpacket 6 */
    subpkt = json_object_array_get_idx(subpkts, 6);
    assert_true(check_json_field_int(subpkt, "type", 33));
    assert_true(check_json_field_str(subpkt, "type.str", "issuer fingerprint"));
    assert_true(check_json_field_int(subpkt, "length", 21));
    assert_true(check_json_field_bool(subpkt, "hashed", true));
    assert_true(check_json_field_bool(subpkt, "critical", false));
    assert_true(
      check_json_field_str(subpkt, "fingerprint", "a0ff4590bb6122edef6e3c542d727cc768697734"));
    json_object_put(jso);
    rnp_signature_handle_destroy(sighandle);

    /* cleanup ffi */
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_ffi_locate_key)
{
    rnp_ffi_t ffi = NULL;

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load our keyrings
    assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));

    // keyid
    {
        static const char *ids[] = {"7BC6709B15C23A4A",
                                    "1ED63EE56FADC34D",
                                    "1D7E8A5393C997A8",
                                    "8A05B89FAD5ADED1",
                                    "2FCADF05FFA501BB",
                                    "54505A936A4A970E",
                                    "326EF111425D14A5"};
        for (size_t i = 0; i < ARRAY_SIZE(ids); i++) {
            const char *     id = ids[i];
            rnp_key_handle_t key = NULL;
            assert_rnp_success(rnp_locate_key(ffi, "keyid", id, &key));
            assert_non_null(key);
            rnp_key_handle_destroy(key);
        }
        // invalid - value did not change
        {
            rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
            assert_rnp_failure(rnp_locate_key(ffi, "keyid", "invalid-keyid", &key));
            assert_true(key == (rnp_key_handle_t) 0x111);
        }
        // valid but non-existent - null returned
        {
            rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
            assert_rnp_success(rnp_locate_key(ffi, "keyid", "AAAAAAAAAAAAAAAA", &key));
            assert_null(key);
        }
    }

    // userid
    {
        static const char *ids[] = {
          "key0-uid0", "key0-uid1", "key0-uid2", "key1-uid0", "key1-uid2", "key1-uid1"};
        for (size_t i = 0; i < ARRAY_SIZE(ids); i++) {
            const char *     id = ids[i];
            rnp_key_handle_t key = NULL;
            assert_rnp_success(rnp_locate_key(ffi, "userid", id, &key));
            assert_non_null(key);
            rnp_key_handle_destroy(key);
        }
        // valid but non-existent
        {
            rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
            assert_rnp_success(rnp_locate_key(ffi, "userid", "bad-userid", &key));
            assert_null(key);
        }
    }

    // fingerprint
    {
        static const char *ids[] = {"E95A3CBF583AA80A2CCC53AA7BC6709B15C23A4A",
                                    "E332B27CAF4742A11BAA677F1ED63EE56FADC34D",
                                    "C5B15209940A7816A7AF3FB51D7E8A5393C997A8",
                                    "5CD46D2A0BD0B8CFE0B130AE8A05B89FAD5ADED1",
                                    "BE1C4AB951F4C2F6B604C7F82FCADF05FFA501BB",
                                    "A3E94DE61A8CB229413D348E54505A936A4A970E",
                                    "57F8ED6E5C197DB63C60FFAF326EF111425D14A5"};
        for (size_t i = 0; i < ARRAY_SIZE(ids); i++) {
            const char *     id = ids[i];
            rnp_key_handle_t key = NULL;
            assert_rnp_success(rnp_locate_key(ffi, "fingerprint", id, &key));
            assert_non_null(key);
            rnp_key_handle_destroy(key);
        }
        // invalid
        {
            rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
            assert_rnp_failure(rnp_locate_key(ffi, "fingerprint", "invalid-fpr", &key));
            assert_true(key == (rnp_key_handle_t) 0x111);
        }
        // valid but non-existent
        {
            rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
            assert_rnp_success(rnp_locate_key(
              ffi, "fingerprint", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", &key));
            assert_null(key);
        }
    }

    // grip
    {
        static const char *ids[] = {"66D6A0800A3FACDE0C0EB60B16B3669ED380FDFA",
                                    "D9839D61EDAF0B3974E0A4A341D6E95F3479B9B7",
                                    "B1CC352FEF9A6BD4E885B5351840EF9306D635F0",
                                    "E7C8860B70DC727BED6DB64C633683B41221BB40",
                                    "B2A7F6C34AA2C15484783E9380671869A977A187",
                                    "43C01D6D96BE98C3C87FE0F175870ED92DE7BE45",
                                    "8082FE753013923972632550838A5F13D81F43B9"};
        for (size_t i = 0; i < ARRAY_SIZE(ids); i++) {
            const char *     id = ids[i];
            rnp_key_handle_t key = NULL;
            assert_rnp_success(rnp_locate_key(ffi, "grip", id, &key));
            assert_non_null(key);
            rnp_key_handle_destroy(key);
        }
        // invalid
        {
            rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
            assert_rnp_failure(rnp_locate_key(ffi, "grip", "invalid-fpr", &key));
            assert_true(key == (rnp_key_handle_t) 0x111);
        }
        // valid but non-existent
        {
            rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
            assert_rnp_success(
              rnp_locate_key(ffi, "grip", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", &key));
            assert_null(key);
        }
    }

    // cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_signatures_detached_memory_g10)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_input_t      input_sig = NULL;
    rnp_output_t     output = NULL;
    rnp_key_handle_t key = NULL;
    rnp_op_sign_t    opsign = NULL;
    rnp_op_verify_t  opverify = NULL;
    const char *     data = "my data";
    uint8_t *        sig = NULL;
    size_t           sig_len = 0;

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));

    // load our keyrings
    assert_true(load_keys_kbx_g10(
      ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d"));

    // find our signing key
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "4BE147BB22DF1E60", &key));
    assert_non_null(key);

    // create our input
    assert_rnp_success(rnp_input_from_memory(&input, (uint8_t *) data, strlen(data), false));
    assert_non_null(input);
    // create our output
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_non_null(output);
    // create the signing operation
    assert_rnp_success(rnp_op_sign_detached_create(&opsign, ffi, input, output));
    assert_non_null(opsign);

    // add the signer
    assert_rnp_success(rnp_op_sign_add_signature(opsign, key, NULL));
    // execute the signing operation
    assert_rnp_success(rnp_op_sign_execute(opsign));
    // get the resulting signature
    assert_rnp_success(rnp_output_memory_get_buf(output, &sig, &sig_len, true));
    assert_non_null(sig);
    assert_int_not_equal(0, sig_len);
    // cleanup
    rnp_op_sign_destroy(opsign);
    opsign = NULL;
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;

    // verify
    // create our data input
    assert_rnp_success(rnp_input_from_memory(&input, (uint8_t *) data, strlen(data), false));
    assert_non_null(input);
    // create our signature input
    assert_rnp_success(rnp_input_from_memory(&input_sig, sig, sig_len, true));
    assert_non_null(input_sig);
    // create our operation
    assert_rnp_success(rnp_op_verify_detached_create(&opverify, ffi, input, input_sig));
    assert_non_null(opverify);
    // execute the verification
    assert_rnp_success(rnp_op_verify_execute(opverify));
    // cleanup
    rnp_op_verify_destroy(opverify);
    opverify = NULL;
    rnp_input_destroy(input);
    input = NULL;
    rnp_input_destroy(input_sig);
    input_sig = NULL;

    // verify (tamper with signature)
    // create our data input
    assert_rnp_success(rnp_input_from_memory(&input, (uint8_t *) data, strlen(data), false));
    assert_non_null(input);
    // create our signature input
    sig[sig_len - 5] ^= 0xff;
    assert_rnp_success(rnp_input_from_memory(&input_sig, sig, sig_len, true));
    assert_non_null(input_sig);
    // create our operation
    assert_rnp_success(rnp_op_verify_detached_create(&opverify, ffi, input, input_sig));
    assert_non_null(opverify);
    // execute the verification
    assert_rnp_failure(rnp_op_verify_execute(opverify));
    // cleanup
    rnp_op_verify_destroy(opverify);
    opverify = NULL;
    rnp_input_destroy(input);
    input = NULL;
    rnp_input_destroy(input_sig);
    input_sig = NULL;

    // cleanup
    rnp_buffer_destroy(sig);
    rnp_key_handle_destroy(key);
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_enarmor_dearmor)
{
    std::string data;

    // enarmor plain message
    const std::string msg("this is a test");
    data.clear();
    {
        uint8_t *    buf = NULL;
        size_t       buf_size = 0;
        rnp_input_t  input = NULL;
        rnp_output_t output = NULL;

        assert_rnp_success(
          rnp_input_from_memory(&input, (const uint8_t *) msg.data(), msg.size(), true));
        assert_rnp_success(rnp_output_to_memory(&output, 0));

        assert_rnp_success(rnp_enarmor(input, output, "message"));

        rnp_output_memory_get_buf(output, &buf, &buf_size, false);
        data = std::string(buf, buf + buf_size);
        assert_true(starts_with(data, "-----BEGIN PGP MESSAGE-----\r\n"));
        assert_true(ends_with(data, "-----END PGP MESSAGE-----\r\n"));

        rnp_input_destroy(input);
        rnp_output_destroy(output);
    }
    {
        uint8_t *    buf = NULL;
        size_t       buf_size = 0;
        rnp_input_t  input = NULL;
        rnp_output_t output = NULL;

        assert_rnp_success(
          rnp_input_from_memory(&input, (const uint8_t *) data.data(), data.size(), true));
        assert_rnp_success(rnp_output_to_memory(&output, 0));

        assert_rnp_success(rnp_dearmor(input, output));

        assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
        std::string dearmored(buf, buf + buf_size);
        assert_true(msg == dearmored);

        rnp_input_destroy(input);
        rnp_output_destroy(output);
    }

    // enarmor public key
    data.clear();
    {
        uint8_t *    buf = NULL;
        size_t       buf_size = 0;
        rnp_input_t  input = NULL;
        rnp_output_t output = NULL;

        // enarmor
        assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
        assert_rnp_success(rnp_output_to_memory(&output, 0));

        assert_rnp_success(rnp_enarmor(input, output, NULL));

        rnp_output_memory_get_buf(output, &buf, &buf_size, false);
        data = std::string(buf, buf + buf_size);
        assert_true(starts_with(data, "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n"));
        assert_true(ends_with(data, "-----END PGP PUBLIC KEY BLOCK-----\r\n"));

        rnp_input_destroy(input);
        rnp_output_destroy(output);
    }
    // dearmor public key
    {
        uint8_t *    buf = NULL;
        size_t       buf_size = 0;
        rnp_input_t  input = NULL;
        rnp_output_t output = NULL;

        assert_rnp_success(
          rnp_input_from_memory(&input, (const uint8_t *) data.data(), data.size(), true));
        assert_rnp_success(rnp_output_to_memory(&output, 0));

        assert_rnp_success(rnp_dearmor(input, output));

        assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
        std::string   dearmored(buf, buf + buf_size);
        std::ifstream inf("data/keyrings/1/pubring.gpg", std::ios::binary | std::ios::ate);
        std::string   from_disk(inf.tellg(), ' ');
        inf.seekg(0);
        inf.read(&from_disk[0], from_disk.size());
        inf.close();
        assert_true(dearmored == from_disk);

        rnp_input_destroy(input);
        rnp_output_destroy(output);
    }
    // test truncated armored data
    {
        std::ifstream keyf("data/test_stream_key_load/rsa-rsa-pub.asc",
                           std::ios::binary | std::ios::ate);
        std::string   keystr(keyf.tellg(), ' ');
        keyf.seekg(0);
        keyf.read(&keystr[0], keystr.size());
        keyf.close();
        for (size_t sz = keystr.size() - 2; sz > 0; sz--) {
            rnp_input_t  input = NULL;
            rnp_output_t output = NULL;

            assert_rnp_success(
              rnp_input_from_memory(&input, (const uint8_t *) keystr.data(), sz, true));
            assert_rnp_success(rnp_output_to_memory(&output, 0));
            assert_rnp_failure(rnp_dearmor(input, output));

            rnp_input_destroy(input);
            rnp_output_destroy(output);
        }
    }
}

TEST_F(rnp_tests, test_ffi_dearmor_edge_cases)
{
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/long_header_line.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    uint8_t *buf = NULL;
    size_t   len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/empty_header_line.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(rnp_input_from_path(
      &input, "data/test_stream_armor/64k_whitespace_before_armored_message.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_dearmor(input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* Armor header starts and fits in the first 1024 bytes of the input. Prepended by
     * whitespaces. */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/1024_peek_buf.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/blank_line_with_whitespace.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/duplicate_header_line.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/long_header_line_1024.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/long_header_line_64k.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/long_header_nameline_64k.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* Armored message encoded in a single >64k text line */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/message_64k_oneline.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 68647);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/wrong_header_line.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* invalid, > 127 (negative char), preceding the armor header - just warning */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_header.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dearmor(input, output));
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 2226);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* invalid, > 127, base64 chars at positions 1..4 */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_base64_1.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_dearmor(input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_base64_2.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_dearmor(input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_base64_3.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_dearmor(input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_base64_4.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_dearmor(input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* invalid, > 127 base64 char in the crc */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_crc.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_dearmor(input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* too short armor header */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/too_short_header.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_dearmor(input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* wrong base64 padding */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_armor/wrong_b64_trailer.asc"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_failure(rnp_dearmor(input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);
}

TEST_F(rnp_tests, test_ffi_customized_enarmor)
{
    rnp_input_t           input = NULL;
    rnp_output_t          output = NULL;
    rnp_output_t          armor_layer = NULL;
    const std::string     msg("this is a test long enough to have more than 76 characters in "
                          "enarmored representation");
    std::set<std::string> lines_to_skip{"-----BEGIN PGP MESSAGE-----",
                                        "-----END PGP MESSAGE-----"};

    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "message"));
    // should fail when trying to set line length on non-armor output
    assert_rnp_failure(rnp_output_armor_set_line_length(output, 64));
    // should fail when trying to set zero line length
    assert_rnp_failure(rnp_output_armor_set_line_length(armor_layer, 0));
    // should fail when trying to set line length less than the minimum allowed 16
    assert_rnp_failure(rnp_output_armor_set_line_length(armor_layer, 15));
    assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, 16));
    assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, 76));
    // should fail when trying to set line length greater than the maximum allowed 76
    assert_rnp_failure(rnp_output_armor_set_line_length(armor_layer, 77));
    assert_rnp_success(rnp_output_destroy(armor_layer));
    assert_rnp_success(rnp_output_destroy(output));

    for (size_t llen = 16; llen <= 76; llen++) {
        std::string data;
        uint8_t *   buf = NULL;
        size_t      buf_size = 0;

        input = NULL;
        output = NULL;
        armor_layer = NULL;
        assert_rnp_success(
          rnp_input_from_memory(&input, (const uint8_t *) msg.data(), msg.size(), true));
        assert_rnp_success(rnp_output_to_memory(&output, 0));
        assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "message"));
        assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));
        assert_rnp_success(rnp_output_pipe(input, armor_layer));
        assert_rnp_success(rnp_output_finish(armor_layer));
        assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
        data = std::string(buf, buf + buf_size);
        auto effective_llen = get_longest_line_length(data, lines_to_skip);
        assert_int_equal(llen / 4, effective_llen / 4);
        assert_true(llen >= effective_llen);
        assert_rnp_success(rnp_input_destroy(input));
        assert_rnp_success(rnp_output_destroy(armor_layer));
        assert_rnp_success(rnp_output_destroy(output));

        // test that the dearmored message is correct
        assert_rnp_success(
          rnp_input_from_memory(&input, (const uint8_t *) data.data(), data.size(), true));
        assert_rnp_success(rnp_output_to_memory(&output, 0));

        assert_rnp_success(rnp_dearmor(input, output));

        assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
        std::string dearmored(buf, buf + buf_size);
        assert_true(msg == dearmored);

        assert_rnp_success(rnp_input_destroy(input));
        assert_rnp_success(rnp_output_destroy(output));
    }
}

TEST_F(rnp_tests, test_ffi_version)
{
    const uint32_t version = rnp_version();
    const uint32_t major = rnp_version_major(version);
    const uint32_t minor = rnp_version_minor(version);
    const uint32_t patch = rnp_version_patch(version);

    // reconstruct the version string
    assert_string_equal(fmt("%d.%d.%d", major, minor, patch).c_str(), rnp_version_string());

    // full version string should probably be at least as long as regular version string
    assert_true(strlen(rnp_version_string_full()) >= strlen(rnp_version_string()));

    // reconstruct the version value
    assert_int_equal(version, rnp_version_for(major, minor, patch));

    // check out-of-range handling
    assert_int_equal(0, rnp_version_for(1024, 0, 0));
    assert_int_equal(0, rnp_version_for(0, 1024, 0));
    assert_int_equal(0, rnp_version_for(0, 0, 1024));

    // check component extraction again
    assert_int_equal(rnp_version_major(rnp_version_for(5, 4, 3)), 5);
    assert_int_equal(rnp_version_minor(rnp_version_for(5, 4, 3)), 4);
    assert_int_equal(rnp_version_patch(rnp_version_for(5, 4, 3)), 3);

    // simple comparisons
    assert_true(rnp_version_for(1, 0, 1) > rnp_version_for(1, 0, 0));
    assert_true(rnp_version_for(1, 1, 0) > rnp_version_for(1, 0, 1023));
    assert_true(rnp_version_for(2, 0, 0) > rnp_version_for(1, 1023, 1023));

    // commit timestamp
    const uint64_t timestamp = rnp_version_commit_timestamp();
    assert_true(!timestamp || (timestamp >= 1639439116));
}

TEST_F(rnp_tests, test_ffi_backend_version)
{
    assert_non_null(rnp_backend_string());
    assert_non_null(rnp_backend_version());

    assert_true(strlen(rnp_backend_string()) > 0 && strlen(rnp_backend_string()) < 255);
    assert_true(strlen(rnp_backend_version()) > 0 && strlen(rnp_backend_version()) < 255);
}

void check_loaded_keys(const char *                    format,
                       bool                            armored,
                       uint8_t *                       buf,
                       size_t                          buf_len,
                       const char *                    id_type,
                       const std::vector<std::string> &expected_ids,
                       bool                            secret);

TEST_F(rnp_tests, test_ffi_key_export_customized_enarmor)
{
    rnp_ffi_t             ffi = NULL;
    rnp_output_t          output = NULL;
    rnp_output_t          armor_layer = NULL;
    rnp_key_handle_t      key = NULL;
    uint8_t *             buf = NULL;
    size_t                buf_len = 0;
    std::set<std::string> lines_to_skip{"-----BEGIN PGP PUBLIC KEY BLOCK-----",
                                        "-----END PGP PUBLIC KEY BLOCK-----",
                                        "-----BEGIN PGP PRIVATE KEY BLOCK-----",
                                        "-----END PGP PRIVATE KEY BLOCK-----"};
    // setup FFI
    test_ffi_init(&ffi);

    for (size_t llen = 16; llen <= 76; llen++) {
        // primary pub only
        {
            // locate key
            key = NULL;
            assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
            assert_non_null(key);

            // create output
            output = NULL;
            assert_rnp_success(rnp_output_to_memory(&output, 0));
            assert_non_null(output);
            assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "public key"));
            assert_non_null(armor_layer);
            assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));

            // export
            assert_rnp_success(rnp_key_export(key, armor_layer, RNP_KEY_EXPORT_PUBLIC));
            assert_rnp_success(rnp_output_finish(armor_layer));
            // get output
            buf = NULL;
            assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
            assert_non_null(buf);
            std::string data = std::string(buf, buf + buf_len);
            auto        effective_llen = get_longest_line_length(data, lines_to_skip);
            assert_int_equal(llen / 4, effective_llen / 4);
            assert_true(llen >= effective_llen);

            // check results
            check_loaded_keys("GPG", true, buf, buf_len, "keyid", {"2FCADF05FFA501BB"}, false);

            // cleanup
            rnp_output_destroy(armor_layer);
            rnp_output_destroy(output);
            rnp_key_handle_destroy(key);
        }

        // primary sec only
        {
            // locate key
            key = NULL;
            assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
            assert_non_null(key);

            // create output
            output = NULL;
            assert_rnp_success(rnp_output_to_memory(&output, 0));
            assert_non_null(output);
            assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "secret key"));
            assert_non_null(armor_layer);
            assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));

            // export
            assert_rnp_success(rnp_key_export(key, armor_layer, RNP_KEY_EXPORT_SECRET));
            assert_rnp_success(rnp_output_finish(armor_layer));

            // get output
            buf = NULL;
            assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
            assert_non_null(buf);
            std::string data = std::string(buf, buf + buf_len);
            auto        effective_llen = get_longest_line_length(data, lines_to_skip);
            assert_int_equal(llen / 4, effective_llen / 4);
            assert_true(llen >= effective_llen);

            // check results
            check_loaded_keys("GPG", true, buf, buf_len, "keyid", {"2FCADF05FFA501BB"}, true);

            // cleanup
            rnp_output_destroy(armor_layer);
            rnp_output_destroy(output);
            rnp_key_handle_destroy(key);
        }

        // sub pub
        {
            // locate key
            key = NULL;
            assert_rnp_success(rnp_locate_key(ffi, "keyid", "54505A936A4A970E", &key));
            assert_non_null(key);

            // create output
            output = NULL;
            assert_rnp_success(rnp_output_to_memory(&output, 0));
            assert_non_null(output);
            assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "public key"));
            assert_non_null(armor_layer);
            assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));

            // export
            assert_rnp_success(rnp_key_export(key, armor_layer, RNP_KEY_EXPORT_PUBLIC));
            assert_rnp_success(rnp_output_finish(armor_layer));

            // get output
            buf = NULL;
            assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
            assert_non_null(buf);
            std::string data = std::string(buf, buf + buf_len);
            auto        effective_llen = get_longest_line_length(data, lines_to_skip);
            assert_int_equal(llen / 4, effective_llen / 4);
            assert_true(llen >= effective_llen);

            // check results
            check_loaded_keys("GPG",
                              true,
                              buf,
                              buf_len,
                              "keyid",
                              {"2FCADF05FFA501BB", "54505A936A4A970E"},
                              false);

            // cleanup
            rnp_output_destroy(armor_layer);
            rnp_output_destroy(output);
            rnp_key_handle_destroy(key);
        }

        // sub sec
        {
            // locate key
            key = NULL;
            assert_rnp_success(rnp_locate_key(ffi, "keyid", "54505A936A4A970E", &key));
            assert_non_null(key);

            // create output
            output = NULL;
            assert_rnp_success(rnp_output_to_memory(&output, 0));
            assert_non_null(output);
            assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "secret key"));
            assert_non_null(armor_layer);
            assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));

            // export
            assert_rnp_success(rnp_key_export(key, armor_layer, RNP_KEY_EXPORT_SECRET));
            assert_rnp_success(rnp_output_finish(armor_layer));

            // get output
            buf = NULL;
            assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
            assert_non_null(buf);
            std::string data = std::string(buf, buf + buf_len);
            auto        effective_llen = get_longest_line_length(data, lines_to_skip);
            assert_int_equal(llen / 4, effective_llen / 4);
            assert_true(llen >= effective_llen);

            // check results
            check_loaded_keys("GPG",
                              true,
                              buf,
                              buf_len,
                              "keyid",
                              {"2FCADF05FFA501BB", "54505A936A4A970E"},
                              true);

            // cleanup
            rnp_output_destroy(armor_layer);
            rnp_output_destroy(output);
            rnp_key_handle_destroy(key);
        }
    }
    // cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_key_dump)
{
    rnp_ffi_t        ffi = NULL;
    rnp_key_handle_t key = NULL;
    char *           json = NULL;
    json_object *    jso = NULL;

    // setup FFI
    test_ffi_init(&ffi);

    // locate key
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
    assert_non_null(key);

    // dump public key and check results
    assert_rnp_success(rnp_key_packets_to_json(
      key, false, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
    assert_non_null(json);
    jso = json_tokener_parse(json);
    assert_non_null(jso);
    assert_true(json_object_is_type(jso, json_type_array));
    json_object_put(jso);
    rnp_buffer_destroy(json);

    // dump secret key and check results
    assert_rnp_success(rnp_key_packets_to_json(
      key, true, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
    assert_non_null(json);
    jso = json_tokener_parse(json);
    assert_non_null(jso);
    assert_true(json_object_is_type(jso, json_type_array));
    json_object_put(jso);
    rnp_buffer_destroy(json);

    // cleanup
    rnp_key_handle_destroy(key);
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_key_dump_edge_cases)
{
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    /* secret key, stored on gpg card, with too large card serial len */
    rnp_input_t input = NULL;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-2-card-len.pgp"));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
    rnp_input_destroy(input);
    uint8_t *buf = NULL;
    size_t   len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    std::string dstr(buf, buf + len);
    assert_true(
      dstr.find("card serial number: 0x000102030405060708090a0b0c0d0e0f (16 bytes)") !=
      std::string::npos);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-2-card-len.pgp"));
    char *json = NULL;
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    rnp_input_destroy(input);
    dstr = json;
    assert_true(dstr.find("\"card serial number\":\"000102030405060708090a0b0c0d0e0f\"") !=
                std::string::npos);
    rnp_buffer_destroy(json);

    /* secret key, stored with unknown gpg s2k */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-3.pgp"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
    rnp_input_destroy(input);
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    dstr = std::string(buf, buf + len);
    assert_true(dstr.find("Unknown experimental s2k: 0x474e5503 (4 bytes)") !=
                std::string::npos);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-3.pgp"));
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    rnp_input_destroy(input);
    dstr = json;
    assert_true(dstr.find("\"unknown experimental\":\"474e5503\"") != std::string::npos);
    rnp_buffer_destroy(json);

    /* secret key, stored with unknown s2k */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-unknown.pgp"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
    rnp_input_destroy(input);
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    dstr = std::string(buf, buf + len);
    assert_true(dstr.find("Unknown experimental s2k: 0x554e4b4e (4 bytes)") !=
                std::string::npos);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-unknown.pgp"));
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    rnp_input_destroy(input);
    dstr = json;
    assert_true(dstr.find("\"unknown experimental\":\"554e4b4e\"") != std::string::npos);
    rnp_buffer_destroy(json);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_key_userid_dump_has_no_special_chars)
{
    rnp_ffi_t    ffi = NULL;
    char *       json = NULL;
    json_object *jso = NULL;
    const char * trackers[] = {
      "userid\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f@rnp",
      "userid\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f@rnp"};
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    for (int i = 0; i < 2; i++) {
        // generate RSA key
        rnp_op_generate_t keygen = NULL;
        assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "RSA"));
        assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
        // user id
        assert_rnp_success(rnp_op_generate_set_userid(keygen, trackers[0]));
        // now execute keygen operation
        assert_rnp_success(rnp_op_generate_execute(keygen));
        rnp_key_handle_t key = NULL;
        assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
        assert_non_null(key);
        assert_rnp_success(rnp_op_generate_destroy(keygen));
        keygen = NULL;

        // dump public key and check results
        assert_rnp_success(rnp_key_packets_to_json(
          key, false, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
        assert_non_null(json);
        for (char c = 1; c < 0x20; c++) {
            if (c != '\n') {
                assert_null(strchr(json, c));
            }
        }
        jso = json_tokener_parse(json);
        assert_non_null(jso);
        assert_true(json_object_is_type(jso, json_type_array));
        json_object_put(jso);
        rnp_buffer_destroy(json);

        // cleanup
        rnp_key_handle_destroy(key);
    }
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_pkt_dump)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    char *       json = NULL;
    json_object *jso = NULL;

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    // setup input
    assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));

    // try with wrong parameters
    assert_rnp_failure(rnp_dump_packets_to_json(input, 0, NULL));
    assert_rnp_failure(rnp_dump_packets_to_json(NULL, 0, &json));
    assert_rnp_failure(rnp_dump_packets_to_json(input, 117, &json));
    // dump
    assert_rnp_success(rnp_dump_packets_to_json(
      input, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
    rnp_input_destroy(input);
    input = NULL;
    assert_non_null(json);

    // check results
    jso = json_tokener_parse(json);
    assert_non_null(jso);
    assert_true(json_object_is_type(jso, json_type_array));
    /* make sure that correct number of packets dumped */
    assert_int_equal(json_object_array_length(jso), 35);
    json_object_put(jso);
    rnp_buffer_destroy(json);

    // setup input and output
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));

    // try with wrong parameters
    assert_rnp_failure(rnp_dump_packets_to_output(input, NULL, 0));
    assert_rnp_failure(rnp_dump_packets_to_output(NULL, output, 0));
    assert_rnp_failure(rnp_dump_packets_to_output(input, output, 117));
    // dump
    assert_rnp_success(
      rnp_dump_packets_to_output(input, output, RNP_DUMP_MPI | RNP_DUMP_RAW | RNP_DUMP_GRIP));

    uint8_t *buf = NULL;
    size_t   len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    /* make sure output is not cut */
    assert_true(len > 45000);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    // dump data with marker packet
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.marker"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(
      rnp_dump_packets_to_output(input, output, RNP_DUMP_MPI | RNP_DUMP_RAW | RNP_DUMP_GRIP));
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    buf[len - 1] = '\0';
    assert_non_null(strstr((char *) buf, "contents: PGP"));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    // dump data with marker packet to json
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.marker"));
    assert_rnp_success(rnp_dump_packets_to_json(
      input, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
    assert_non_null(strstr(json, "\"contents\":\"PGP\""));
    rnp_buffer_destroy(json);
    rnp_input_destroy(input);

    // dump data with malformed marker packet
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.marker.malf"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(
      rnp_dump_packets_to_output(input, output, RNP_DUMP_MPI | RNP_DUMP_RAW | RNP_DUMP_GRIP));
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    buf[len - 1] = '\0';
    assert_non_null(strstr((char *) buf, "contents: invalid"));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    // dump data with malformed marker packet to json
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.marker.malf"));
    assert_rnp_success(rnp_dump_packets_to_json(
      input, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
    assert_non_null(strstr(json, "\"contents\":\"invalid\""));
    rnp_buffer_destroy(json);
    rnp_input_destroy(input);

    // cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_rsa_v3_dump)
{
    rnp_input_t input = NULL;
    char *      json = NULL;

    /* dump rsav3 key to json via FFI */
    assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/4/rsav3-p.asc"));
    assert_rnp_success(rnp_dump_packets_to_json(input, RNP_JSON_DUMP_GRIP, &json));
    rnp_input_destroy(input);
    /* parse dump */
    json_object *jso = json_tokener_parse(json);
    rnp_buffer_destroy(json);
    assert_non_null(jso);
    assert_true(json_object_is_type(jso, json_type_array));
    json_object *rsapkt = json_object_array_get_idx(jso, 0);
    assert_non_null(rsapkt);
    assert_true(json_object_is_type(rsapkt, json_type_object));
    /* check algorithm string */
    json_object *fld = NULL;
    assert_true(json_object_object_get_ex(rsapkt, "algorithm.str", &fld));
    assert_non_null(fld);
    const char *str = json_object_get_string(fld);
    assert_non_null(str);
    assert_string_equal(str, "RSA (Encrypt or Sign)");
    /* check fingerprint */
    fld = NULL;
    assert_true(json_object_object_get_ex(rsapkt, "fingerprint", &fld));
    assert_non_null(fld);
    str = json_object_get_string(fld);
    assert_non_null(str);
    assert_string_equal(str, "06a044022bb5aa7991077466aeba2ce7");
    json_object_put(jso);
}

TEST_F(rnp_tests, test_ffi_load_userattr)
{
    rnp_ffi_t ffi = NULL;

    // init ffi and load key
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-25519-photo-pub.asc"));
    // check userid 0 : ecc-25519
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "cc786278981b0728", &key));
    assert_non_null(key);
    size_t uid_count = 0;
    assert_rnp_success(rnp_key_get_uid_count(key, &uid_count));
    assert_int_equal(uid_count, 2);
    char *uid = NULL;
    assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid));
    assert_string_equal(uid, "ecc-25519");
    rnp_buffer_destroy(uid);
    // check userattr 1, must be text instead of binary JPEG data
    assert_rnp_success(rnp_key_get_uid_at(key, 1, &uid));
    assert_string_equal(uid, "(photo)");
    rnp_buffer_destroy(uid);
    assert_rnp_success(rnp_key_handle_destroy(key));
    // cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_revocations)
{
    rnp_ffi_t ffi = NULL;

    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load key with revoked userid
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-revoked-uid.asc"));
    // check userid 0 : ecc-p256
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
    assert_non_null(key);
    size_t uid_count = 0;
    assert_rnp_success(rnp_key_get_uid_count(key, &uid_count));
    assert_int_equal(uid_count, 2);
    char *uid = NULL;
    assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid));
    assert_string_equal(uid, "ecc-p256");
    rnp_buffer_destroy(uid);
    rnp_uid_handle_t uid_handle = NULL;
    assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid_handle));
    assert_non_null(uid_handle);
    bool revoked = true;
    assert_rnp_failure(rnp_uid_is_revoked(NULL, &revoked));
    assert_rnp_failure(rnp_uid_is_revoked(uid_handle, NULL));
    assert_rnp_success(rnp_uid_is_revoked(uid_handle, &revoked));
    assert_false(revoked);
    const uintptr_t        p_sig = 0xdeadbeef;
    rnp_signature_handle_t sig = reinterpret_cast<rnp_signature_handle_t>(p_sig);
    assert_rnp_failure(rnp_uid_get_revocation_signature(NULL, &sig));
    assert_rnp_failure(rnp_uid_get_revocation_signature(uid_handle, NULL));
    assert_rnp_success(rnp_uid_get_revocation_signature(uid_handle, &sig));
    assert_null(sig);
    assert_rnp_success(rnp_uid_handle_destroy(uid_handle));
    // check userid 1: ecc-p256-revoked
    assert_rnp_success(rnp_key_get_uid_at(key, 1, &uid));
    assert_string_equal(uid, "ecc-p256-revoked");
    rnp_buffer_destroy(uid);
    assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid_handle));
    assert_non_null(uid_handle);
    assert_rnp_success(rnp_uid_is_revoked(uid_handle, &revoked));
    assert_true(revoked);
    assert_rnp_success(rnp_uid_get_revocation_signature(uid_handle, &sig));
    assert_non_null(sig);
    uint32_t creation = 0;
    assert_rnp_success(rnp_signature_get_creation(sig, &creation));
    assert_int_equal(creation, 1556630215);
    assert_rnp_success(rnp_signature_handle_destroy(sig));
    assert_rnp_success(rnp_uid_handle_destroy(uid_handle));
    assert_rnp_success(rnp_key_handle_destroy(key));

    // load key with revoked subkey
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-revoked-sub.asc"));
    // key is not revoked
    assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
    assert_rnp_success(rnp_key_is_revoked(key, &revoked));
    assert_false(revoked);
    assert_rnp_failure(rnp_key_get_revocation_signature(NULL, &sig));
    assert_rnp_failure(rnp_key_get_revocation_signature(key, NULL));
    assert_rnp_success(rnp_key_get_revocation_signature(key, &sig));
    assert_null(sig);
    bool valid = false;
    assert_rnp_success(rnp_key_is_valid(key, &valid));
    assert_true(valid);
    uint32_t till = 0;
    assert_rnp_success(rnp_key_valid_till(key, &till));
    assert_int_equal(till, 0xFFFFFFFF);
    assert_rnp_success(rnp_key_handle_destroy(key));
    // subkey is revoked
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "37E285E9E9851491", &key));
    assert_rnp_success(rnp_key_is_revoked(key, &revoked));
    assert_true(revoked);
    char *reason = NULL;
    assert_rnp_success(rnp_key_get_revocation_reason(key, &reason));
    assert_string_equal(reason, "Subkey revocation test.");
    rnp_buffer_destroy(reason);
    assert_rnp_success(rnp_key_is_superseded(key, &revoked));
    assert_false(revoked);
    assert_rnp_success(rnp_key_is_compromised(key, &revoked));
    assert_true(revoked);
    assert_rnp_success(rnp_key_is_retired(key, &revoked));
    assert_false(revoked);
    assert_rnp_success(rnp_key_get_revocation_signature(key, &sig));
    assert_non_null(sig);
    assert_rnp_success(rnp_signature_get_creation(sig, &creation));
    assert_int_equal(creation, 1556630749);
    assert_rnp_success(rnp_signature_handle_destroy(sig));
    assert_rnp_success(rnp_key_is_valid(key, &valid));
    assert_false(valid);
    assert_rnp_success(rnp_key_valid_till(key, &till));
    assert_int_equal(till, 0);
    assert_rnp_success(rnp_key_handle_destroy(key));

    // load revoked key
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-revoked-key.asc"));
    // key is revoked
    assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
    assert_rnp_success(rnp_key_is_revoked(key, &revoked));
    assert_true(revoked);
    reason = NULL;
    assert_rnp_success(rnp_key_get_revocation_reason(key, &reason));
    assert_string_equal(reason, "Superseded key test.");
    rnp_buffer_destroy(reason);
    assert_rnp_success(rnp_key_is_superseded(key, &revoked));
    assert_true(revoked);
    assert_rnp_success(rnp_key_is_compromised(key, &revoked));
    assert_false(revoked);
    assert_rnp_success(rnp_key_is_retired(key, &revoked));
    assert_false(revoked);
    assert_rnp_success(rnp_key_get_revocation_signature(key, &sig));
    assert_non_null(sig);
    assert_rnp_success(rnp_signature_get_creation(sig, &creation));
    assert_int_equal(creation, 1556799806);
    assert_rnp_success(rnp_signature_handle_destroy(sig));
    assert_rnp_success(rnp_key_is_valid(key, &valid));
    assert_false(valid);
    assert_rnp_success(rnp_key_valid_till(key, &till));
    assert_int_equal(till, 1556799806);
    uint64_t till64 = 0;
    assert_rnp_success(rnp_key_valid_till64(key, &till64));
    assert_int_equal(till64, 1556799806);
    assert_rnp_success(rnp_key_handle_destroy(key));

    // cleanup
    rnp_ffi_destroy(ffi);
}

#define KEY_OUT_PATH "exported-key.asc"

TEST_F(rnp_tests, test_ffi_file_output)
{
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load two keys
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-pub.asc"));
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p521-pub.asc"));

    rnp_key_handle_t k256 = NULL;
    rnp_key_handle_t k521 = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &k256));
    assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p521", &k521));

    rnp_output_t output = NULL;
    // test output to path - must overwrite if exists
    assert_rnp_success(rnp_output_to_path(&output, KEY_OUT_PATH));
    assert_rnp_success(rnp_key_export(
      k256, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS));
    assert_rnp_success(rnp_output_destroy(output));
    assert_true(rnp_file_exists(KEY_OUT_PATH));
    off_t sz = file_size(KEY_OUT_PATH);
    assert_rnp_success(rnp_output_to_path(&output, KEY_OUT_PATH));
    assert_rnp_success(rnp_key_export(
      k521, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS));
    assert_rnp_success(rnp_output_destroy(output));
    assert_true(rnp_file_exists(KEY_OUT_PATH));
    assert_true(sz != file_size(KEY_OUT_PATH));
    sz = file_size(KEY_OUT_PATH);
    // test output to file - will fail without overwrite
    assert_rnp_failure(rnp_output_to_file(&output, KEY_OUT_PATH, 0));
    // fail with wrong flags
    assert_rnp_failure(rnp_output_to_file(&output, KEY_OUT_PATH, 0x100));
    // test output to random file - will succeed on creation and export but fail on finish.
    assert_rnp_success(rnp_output_to_file(&output, KEY_OUT_PATH, RNP_OUTPUT_FILE_RANDOM));
    assert_true(file_size(KEY_OUT_PATH) == sz);
    assert_rnp_success(
      rnp_key_export(k256, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS));
    assert_rnp_failure(rnp_output_finish(output));
    assert_rnp_success(rnp_output_destroy(output));
    // test output with random + overwrite - will succeed
    assert_rnp_success(rnp_output_to_file(
      &output, KEY_OUT_PATH, RNP_OUTPUT_FILE_RANDOM | RNP_OUTPUT_FILE_OVERWRITE));
    assert_true(file_size(KEY_OUT_PATH) == sz);
    assert_rnp_success(
      rnp_key_export(k256, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS));
    assert_rnp_success(rnp_output_finish(output));
    assert_rnp_success(rnp_output_destroy(output));
    assert_true(file_size(KEY_OUT_PATH) != sz);
    sz = file_size(KEY_OUT_PATH);
    // test output with just overwrite - will succeed
    assert_rnp_success(rnp_output_to_file(&output, KEY_OUT_PATH, RNP_OUTPUT_FILE_OVERWRITE));
    assert_true(file_size(KEY_OUT_PATH) == 0);
    assert_rnp_success(
      rnp_key_export(k521, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS));
    assert_rnp_success(rnp_output_finish(output));
    assert_rnp_success(rnp_output_destroy(output));
    assert_true(file_size(KEY_OUT_PATH) != sz);
    assert_int_equal(rnp_unlink(KEY_OUT_PATH), 0);
    // cleanup
    assert_rnp_success(rnp_key_handle_destroy(k256));
    assert_rnp_success(rnp_key_handle_destroy(k521));
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_stdout_output)
{
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-pub.asc"));

    rnp_key_handle_t k256 = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &k256));

    rnp_output_t output = NULL;
    assert_rnp_failure(rnp_output_to_stdout(NULL));
    assert_rnp_success(rnp_output_to_stdout(&output));
    assert_rnp_success(rnp_key_export(
      k256, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_key_handle_destroy(k256));
    rnp_ffi_destroy(ffi);
}

/* shrink the length to 1 packet
 * set packet length type as PGP_PTAG_OLD_LEN_1 and remove one octet from length header
 */
static std::vector<uint8_t>
shrink_len_2_to_1(const std::vector<uint8_t> &src)
{
    std::vector<uint8_t> dst = std::vector<uint8_t>();
    dst.reserve(src.size() - 1);
    dst.insert(dst.end(),
               PGP_PTAG_ALWAYS_SET | (PGP_PKT_PUBLIC_KEY << PGP_PTAG_OF_CONTENT_TAG_SHIFT) |
                 PGP_PTAG_OLD_LEN_1);
    // make sure the most significant octet of 2-octet length is actually zero
    if (src[1] != 0) {
        throw std::invalid_argument("src");
    }
    dst.insert(dst.end(), src[2]);
    dst.insert(dst.end(), src.begin() + 3, src.end());
    return dst;
}

/*
 * fake a packet with len = 0xEEEE
 */
static std::vector<uint8_t>
fake_len_EEEE(const std::vector<uint8_t> &src)
{
    std::vector<uint8_t> dst = std::vector<uint8_t>(src);
    dst[1] = 0xEE;
    dst[2] = 0xEE;
    return dst;
}

/*
 * fake a packet with len = 0x00
 */
static std::vector<uint8_t>
fake_len_0(const std::vector<uint8_t> &src)
{
    std::vector<uint8_t> dst = shrink_len_2_to_1(src);
    // erase subsequent octets for the packet to correspond the length
    uint8_t old_length = dst[1];
    dst.erase(dst.begin() + 2, dst.begin() + 2 + old_length);
    dst[1] = 0;
    return dst;
}

/* extend the length to 4 octets (preserving the value)
 * set packet length type as PGP_PTAG_OLD_LEN_4 and set 4 octet length instead of 2
 */
static std::vector<uint8_t>
extend_len_2_to_4(const std::vector<uint8_t> &src)
{
    std::vector<uint8_t> dst = std::vector<uint8_t>();
    dst.reserve(src.size() + 2);
    dst.insert(dst.end(), src.begin(), src.begin() + 3);
    dst[0] &= ~PGP_PTAG_OF_LENGTH_TYPE_MASK;
    dst[0] |= PGP_PTAG_OLD_LEN_4;
    dst.insert(dst.begin() + 1, 2, 0);
    dst.insert(dst.end(), src.begin() + 3, src.end());
    return dst;
}

static bool
import_public_keys_from_vector(std::vector<uint8_t> keyring)
{
    rnp_ffi_t ffi = NULL;
    rnp_ffi_create(&ffi, "GPG", "GPG");
    bool res = import_pub_keys(ffi, &keyring[0], keyring.size());
    rnp_ffi_destroy(ffi);
    return res;
}

TEST_F(rnp_tests, test_ffi_import_keys_check_pktlen)
{
    std::vector<uint8_t> keyring = file_to_vec("data/keyrings/2/pubring.gpg");
    // check tag
    // we are assuming that original key uses old format and packet length type is
    // PGP_PTAG_OLD_LEN_2
    assert_true(keyring.size() >= 5);
    uint8_t expected_tag = PGP_PTAG_ALWAYS_SET |
                           (PGP_PKT_PUBLIC_KEY << PGP_PTAG_OF_CONTENT_TAG_SHIFT) |
                           PGP_PTAG_OLD_LEN_2;
    assert_int_equal(expected_tag, 0x99);
    assert_int_equal(keyring[0], expected_tag);
    // original file can be loaded correctly
    assert_true(import_public_keys_from_vector(keyring));
    {
        // Shrink the packet length to 1 octet
        std::vector<uint8_t> keyring_valid_1 = shrink_len_2_to_1(keyring);
        assert_int_equal(keyring_valid_1.size(), keyring.size() - 1);
        assert_true(import_public_keys_from_vector(keyring_valid_1));
    }
    {
        // get invalid key with length 0
        std::vector<uint8_t> keyring_invalid_0 = fake_len_0(keyring);
        assert_false(import_public_keys_from_vector(keyring_invalid_0));
    }
    {
        // get invalid key with length 0xEEEE
        std::vector<uint8_t> keyring_invalid_EEEE = fake_len_EEEE(keyring);
        assert_int_equal(keyring_invalid_EEEE.size(), keyring.size());
        assert_false(import_public_keys_from_vector(keyring_invalid_EEEE));
    }
    {
        std::vector<uint8_t> keyring_len_4 = extend_len_2_to_4(keyring);
        assert_int_equal(keyring_len_4.size(), keyring.size() + 2);
        assert_true(import_public_keys_from_vector(keyring_len_4));
        // get invalid key with length 0xEEEEEEEE
        keyring_len_4[1] = 0xEE;
        keyring_len_4[2] = 0xEE;
        keyring_len_4[3] = 0xEE;
        keyring_len_4[4] = 0xEE;
        assert_false(import_public_keys_from_vector(keyring_len_4));
    }
}

TEST_F(rnp_tests, test_ffi_calculate_iterations)
{
    size_t iterations = 0;
    assert_rnp_failure(rnp_calculate_iterations(NULL, 500, &iterations));
    assert_rnp_failure(rnp_calculate_iterations("SHA256", 500, NULL));
    assert_rnp_failure(rnp_calculate_iterations("WRONG", 500, &iterations));
    assert_rnp_success(rnp_calculate_iterations("SHA256", 500, &iterations));
    assert_true(iterations > 65536);
}

static bool
check_features(const char *type, const char *json, size_t count)
{
    size_t got_count = 0;

    json_object *features = json_tokener_parse(json);
    if (!features) {
        return false;
    }
    bool res = false;
    if (!json_object_is_type(features, json_type_array)) {
        goto done;
    }
    got_count = json_object_array_length(features);
    if (got_count != count) {
        RNP_LOG("wrong feature count for %s: expected %zu, got %zu", type, count, got_count);
        goto done;
    }
    for (size_t i = 0; i < count; i++) {
        json_object *val = json_object_array_get_idx(features, i);
        const char * str = json_object_get_string(val);
        bool         supported = false;
        if (!str || rnp_supports_feature(type, str, &supported) || !supported) {
            goto done;
        }
    }

    res = true;
done:
    json_object_put(features);
    return res;
}

TEST_F(rnp_tests, test_ffi_supported_features)
{
    char *features = NULL;
    /* some edge cases */
    assert_rnp_failure(rnp_supported_features(NULL, &features));
    assert_rnp_failure(rnp_supported_features("something", NULL));
    assert_rnp_failure(rnp_supported_features(RNP_FEATURE_SYMM_ALG, NULL));
    assert_rnp_failure(rnp_supported_features("something", &features));
    /* symmetric algorithms */
    assert_rnp_success(rnp_supported_features("Symmetric Algorithm", &features));
    assert_non_null(features);
    bool has_sm2 = sm2_enabled();
    bool has_tf = twofish_enabled();
    bool has_brainpool = brainpool_enabled();
    bool has_idea = idea_enabled();
    assert_true(
      check_features(RNP_FEATURE_SYMM_ALG,
                     features,
                     7 + has_sm2 + has_tf + has_idea + blowfish_enabled() + cast5_enabled()));
    rnp_buffer_destroy(features);
    bool supported = false;
    assert_rnp_failure(rnp_supports_feature(NULL, "IDEA", &supported));
    assert_rnp_failure(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, NULL, &supported));
    assert_rnp_failure(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", NULL));
    assert_rnp_failure(rnp_supports_feature("WRONG", "IDEA", &supported));
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", &supported));
    assert_true(supported == has_idea);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "TRIPLEDES", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAST5", &supported));
    assert_int_equal(supported, cast5_enabled());
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "BLOWFISH", &supported));
    assert_int_equal(supported, blowfish_enabled());
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "AES128", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "AES192", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "AES256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "TWOFISH", &supported));
    assert_true(supported == has_tf);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAMELLIA128", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAMELLIA192", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAMELLIA256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "SM4", &supported));
    assert_true(supported == has_sm2);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "idea", &supported));
    assert_true(supported == has_idea);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "tripledes", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "cast5", &supported));
    assert_true(supported == cast5_enabled());
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "blowfish", &supported));
    assert_true(supported == blowfish_enabled());
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "aes128", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "aes192", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "aes256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "twofish", &supported));
    assert_true(supported == has_tf);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "camellia128", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "camellia192", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "camellia256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "sm4", &supported));
    assert_true(supported == has_sm2);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "wrong", &supported));
    assert_false(supported);
    /* aead algorithms */
    bool has_eax = aead_eax_enabled();
    bool has_ocb = aead_ocb_enabled();
    assert_rnp_success(rnp_supported_features(RNP_FEATURE_AEAD_ALG, &features));
    assert_non_null(features);
    assert_true(check_features(RNP_FEATURE_AEAD_ALG, features, 1 + has_eax + has_ocb));
    rnp_buffer_destroy(features);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "eax", &supported));
    assert_true(supported == has_eax);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "ocb", &supported));
    assert_true(supported == has_ocb);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "none", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "wrong", &supported));
    assert_false(supported);
    /* protection mode */
    assert_rnp_success(rnp_supported_features(RNP_FEATURE_PROT_MODE, &features));
    assert_non_null(features);
    assert_true(check_features(RNP_FEATURE_PROT_MODE, features, 1));
    rnp_buffer_destroy(features);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PROT_MODE, "cfb", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PROT_MODE, "wrong", &supported));
    assert_false(supported);
    /* public key algorithm */
    assert_rnp_success(rnp_supported_features(RNP_FEATURE_PK_ALG, &features));
    assert_non_null(features);
    assert_true(check_features(RNP_FEATURE_PK_ALG, features, 6 + has_sm2));
    rnp_buffer_destroy(features);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "RSA", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "DSA", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ELGAMAL", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ECDSA", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ECDH", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "EDDSA", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "SM2", &supported));
    assert_true(supported == has_sm2);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "rsa", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "dsa", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "elgamal", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ecdsa", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ecdh", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "eddsa", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "sm2", &supported));
    assert_true(supported == has_sm2);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "wrong", &supported));
    assert_false(supported);
    /* hash algorithm */
    assert_rnp_success(rnp_supported_features(RNP_FEATURE_HASH_ALG, &features));
    assert_non_null(features);
    assert_true(
      check_features(RNP_FEATURE_HASH_ALG, features, 8 + has_sm2 + ripemd160_enabled()));
    rnp_buffer_destroy(features);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "MD5", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA1", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "RIPEMD160", &supported));
    assert_true(supported == ripemd160_enabled());
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA384", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA512", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA224", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA3-256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA3-512", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SM3", &supported));
    assert_true(supported == has_sm2);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "md5", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha1", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "ripemd160", &supported));
    assert_true(supported == ripemd160_enabled());
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha384", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha512", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha224", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha3-256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha3-512", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sm3", &supported));
    assert_true(supported == has_sm2);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "wrong", &supported));
    assert_false(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "CRC24", &supported));
    assert_false(supported);
    /* compression algorithm */
    assert_rnp_success(rnp_supported_features(RNP_FEATURE_COMP_ALG, &features));
    assert_non_null(features);
    assert_true(check_features(RNP_FEATURE_COMP_ALG, features, 4));
    rnp_buffer_destroy(features);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "Uncompressed", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "Zlib", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "ZIP", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "BZIP2", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "wrong", &supported));
    assert_false(supported);
    /* elliptic curve */
    assert_rnp_success(rnp_supported_features(RNP_FEATURE_CURVE, &features));
    assert_non_null(features);
    assert_true(check_features(RNP_FEATURE_CURVE, features, 6 + has_sm2 + 3 * has_brainpool));
    rnp_buffer_destroy(features);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "NIST P-256", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "NIST P-384", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "NIST P-521", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "ed25519", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "curve25519", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP256r1", &supported));
    assert_true(supported == has_brainpool);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP384r1", &supported));
    assert_true(supported == has_brainpool);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP512r1", &supported));
    assert_true(supported == has_brainpool);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "secp256k1", &supported));
    assert_true(supported);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "SM2 P-256", &supported));
    assert_true(supported == has_sm2);
    assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "wrong", &supported));
    assert_false(supported);
}

TEST_F(rnp_tests, test_ffi_output_to_armor)
{
    rnp_ffi_t    ffi = NULL;
    rnp_output_t memory = NULL;
    rnp_output_t armor = NULL;
    rnp_input_t  input = NULL;

    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));

    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
    assert_non_null(key);

    assert_rnp_success(rnp_output_to_memory(&memory, 0));
    /* some edge cases */
    assert_rnp_failure(rnp_output_to_armor(NULL, &armor, "message"));
    assert_null(armor);
    assert_rnp_failure(rnp_output_to_armor(memory, NULL, "message"));
    assert_null(armor);
    assert_rnp_failure(rnp_output_to_armor(memory, &armor, "wrong"));
    assert_null(armor);
    /* export raw key to armored stream with 'message' header */
    assert_rnp_success(rnp_output_to_armor(memory, &armor, "message"));
    assert_rnp_success(rnp_key_export(key, armor, RNP_KEY_EXPORT_PUBLIC));
    assert_rnp_success(rnp_output_destroy(armor));
    uint8_t *buf = NULL;
    size_t   buf_len = 0;
    /* check contents to make sure it is correct armored stream */
    assert_rnp_success(rnp_output_memory_get_buf(memory, &buf, &buf_len, false));
    assert_non_null(buf);
    const char *hdr = "-----BEGIN PGP MESSAGE-----";
    assert_true(buf_len > strlen(hdr));
    assert_int_equal(strncmp((char *) buf, hdr, strlen(hdr)), 0);
    assert_rnp_success(rnp_input_from_memory(&input, buf, buf_len, false));
    rnp_output_t memory2 = NULL;
    assert_rnp_success(rnp_output_to_memory(&memory2, 0));
    assert_rnp_success(rnp_dearmor(input, memory2));
    rnp_output_destroy(memory2);
    rnp_input_destroy(input);

    rnp_key_handle_destroy(key);
    rnp_output_destroy(memory);
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_rnp_guess_contents)
{
    char *      msgt = NULL;
    rnp_input_t input = NULL;
    assert_rnp_failure(rnp_guess_contents(NULL, &msgt));

    assert_rnp_success(
      rnp_input_from_path(&input, "data/issue1188/armored_revocation_signature.pgp"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_int_equal(strcmp(msgt, "signature"), 0);
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-pub.pgp"));
    assert_rnp_failure(rnp_guess_contents(input, NULL));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "public key");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_key_merge/key-pub-just-subkey-1.pgp"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "public key");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-pub.asc"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "public key");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-sec.pgp"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "secret key");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-sec.asc"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "secret key");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_key_merge/key-sec-just-subkey-1.pgp"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "secret key");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_z/128mb.zip"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "message");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_z/4gb.bzip2.asc"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "message");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_signatures/source.txt.sig"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "signature");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_signatures/source.txt.sig.asc"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "signature");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_signatures/source.txt.asc.asc"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "cleartext");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_signatures/source.txt"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "unknown");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.marker"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "message");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.wrong-armor.asc"));
    assert_rnp_success(rnp_guess_contents(input, &msgt));
    assert_string_equal(msgt, "unknown");
    rnp_buffer_destroy(msgt);
    rnp_input_destroy(input);
}

TEST_F(rnp_tests, test_ffi_literal_filename)
{
    rnp_ffi_t     ffi = NULL;
    rnp_input_t   input = NULL;
    rnp_output_t  output = NULL;
    rnp_op_sign_t op = NULL;
    uint8_t *     signed_buf;
    size_t        signed_len;

    // init ffi
    test_ffi_init(&ffi);
    // init input
    test_ffi_init_sign_memory_input(&input, &output);
    // create signature operation
    assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
    // setup signature(s)
    test_ffi_setup_signatures(&ffi, &op);
    // setup filename and modification time
    assert_rnp_success(rnp_op_sign_set_file_name(op, "checkleak.dat"));
    assert_rnp_success(rnp_op_sign_set_file_name(op, NULL));
    assert_rnp_success(rnp_op_sign_set_file_name(op, "testfile.dat"));
    assert_rnp_success(rnp_op_sign_set_file_mtime(op, 12345678));
    // execute the operation
    assert_rnp_success(rnp_op_sign_execute(op));
    // make sure the output file was created
    assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
    assert_non_null(signed_buf);
    assert_true(signed_len > 0);

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_sign_destroy(op));
    op = NULL;

    // check the resulting stream for correct name/time
    assert_rnp_success(rnp_input_from_memory(&input, signed_buf, signed_len, false));
    char *json = NULL;
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    assert_non_null(json);

    std::string jstr = json;
    assert_true(jstr.find("\"filename\":\"testfile.dat\"") != std::string::npos);
    assert_true(jstr.find("\"timestamp\":12345678") != std::string::npos);

    assert_rnp_success(rnp_input_destroy(input));
    rnp_buffer_destroy(signed_buf);
    rnp_buffer_destroy(json);
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_op_set_hash)
{
    rnp_ffi_t     ffi = NULL;
    rnp_input_t   input = NULL;
    rnp_output_t  output = NULL;
    rnp_op_sign_t op = NULL;
    uint8_t *     signed_buf;
    size_t        signed_len;

    // init ffi
    test_ffi_init(&ffi);
    // init input
    test_ffi_init_sign_memory_input(&input, &output);
    // create signature operation
    assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
    // setup signature(s)
    test_ffi_setup_signatures(&ffi, &op);
    // make sure it doesn't fail on NULL hash value
    assert_rnp_failure(rnp_op_sign_set_hash(op, NULL));
    assert_rnp_failure(rnp_op_sign_set_hash(op, "Unknown"));
    assert_rnp_success(rnp_op_sign_set_hash(op, "SHA256"));
    // execute the operation with wrong password
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrong"));
    assert_int_equal(rnp_op_sign_execute(op), RNP_ERROR_BAD_PASSWORD);
    assert_rnp_success(rnp_op_sign_destroy(op));
    // execute the operation with valid password
    assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
    // setup signature(s)
    test_ffi_setup_signatures(&ffi, &op);
    assert_rnp_success(rnp_op_sign_execute(op));
    // make sure the output file was created
    assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
    assert_non_null(signed_buf);
    assert_true(signed_len > 0);

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_op_sign_destroy(op));

    rnp_buffer_destroy(signed_buf);
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_op_set_compression)
{
    rnp_ffi_t     ffi = NULL;
    rnp_input_t   input = NULL;
    rnp_output_t  output = NULL;
    rnp_op_sign_t op = NULL;
    uint8_t *     signed_buf;
    size_t        signed_len;

    // init ffi
    test_ffi_init(&ffi);
    // init input
    test_ffi_init_sign_memory_input(&input, &output);
    // create signature operation
    assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
    // setup signature(s)
    test_ffi_setup_signatures(&ffi, &op);
    // make sure it doesn't fail on NULL compression algorithm value
    assert_rnp_failure(rnp_op_sign_set_compression(op, NULL, 6));
    assert_rnp_failure(rnp_op_sign_set_compression(op, "Unknown", 6));
    assert_rnp_failure(rnp_op_sign_set_compression(NULL, "ZLib", 6));
    assert_rnp_success(rnp_op_sign_set_compression(op, "ZLib", 6));
    // execute the operation
    assert_rnp_success(rnp_op_sign_execute(op));
    // make sure the output file was created
    assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
    assert_non_null(signed_buf);
    assert_true(signed_len > 0);

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_op_sign_destroy(op));

    rnp_buffer_destroy(signed_buf);
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_aead_params)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_output_t     output = NULL;
    rnp_op_encrypt_t op = NULL;
    const char *plaintext = "Some data to encrypt using the AEAD-EAX and AEAD-OCB encryption.";

    // setup FFI
    test_ffi_init(&ffi);

    // write out some data
    str_to_file("plaintext", plaintext);
    // create input+output
    assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    assert_non_null(output);
    // create encrypt operation
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    // setup AEAD params
    assert_rnp_failure(rnp_op_encrypt_set_aead(NULL, "OCB"));
    assert_rnp_failure(rnp_op_encrypt_set_aead(op, NULL));
    assert_rnp_failure(rnp_op_encrypt_set_aead(op, "WRONG"));
    if (!aead_ocb_enabled()) {
        assert_rnp_failure(rnp_op_encrypt_set_aead(op, "OCB"));
    } else {
        assert_rnp_success(rnp_op_encrypt_set_aead(op, "OCB"));
    }
    assert_rnp_failure(rnp_op_encrypt_set_aead_bits(NULL, 10));
    assert_rnp_failure(rnp_op_encrypt_set_aead_bits(op, -1));
    assert_rnp_failure(rnp_op_encrypt_set_aead_bits(op, 60));
    assert_rnp_failure(rnp_op_encrypt_set_aead_bits(op, 17));
    assert_rnp_success(rnp_op_encrypt_set_aead_bits(op, 10));
    // add password (using all defaults)
    assert_rnp_success(rnp_op_encrypt_add_password(op, "pass1", NULL, 0, NULL));
    // setup compression
    assert_rnp_failure(rnp_op_encrypt_set_compression(NULL, "ZLIB", 6));
    assert_rnp_failure(rnp_op_encrypt_set_compression(op, NULL, 6));
    assert_rnp_failure(rnp_op_encrypt_set_compression(op, "WRONG", 6));
    assert_rnp_success(rnp_op_encrypt_set_compression(op, "ZLIB", 6));
    // set filename and mtime
    assert_rnp_failure(rnp_op_encrypt_set_file_name(NULL, "filename"));
    assert_rnp_success(rnp_op_encrypt_set_file_name(op, NULL));
    assert_rnp_success(rnp_op_encrypt_set_file_name(op, "filename"));
    assert_rnp_failure(rnp_op_encrypt_set_file_mtime(NULL, 1000));
    assert_rnp_success(rnp_op_encrypt_set_file_mtime(op, 1000));
    // execute the operation
    assert_rnp_success(rnp_op_encrypt_execute(op));
    // make sure the output file was created
    assert_true(rnp_file_exists("encrypted"));
    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_encrypt_destroy(op));
    op = NULL;

    // list packets
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    char *json = NULL;
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    json_object *jso = json_tokener_parse(json);
    rnp_buffer_destroy(json);
    assert_non_null(jso);
    assert_true(json_object_is_type(jso, json_type_array));
    /* check the symmetric-key encrypted session key packet */
    json_object *pkt = json_object_array_get_idx(jso, 0);
    assert_true(check_json_pkt_type(pkt, PGP_PKT_SK_SESSION_KEY));
    if (!aead_ocb_enabled()) {
        // if AEAD is not enabled then v4 encrypted packet will be created
        assert_true(check_json_field_int(pkt, "version", 4));
        assert_true(check_json_field_str(pkt, "algorithm.str", "AES-256"));
    } else {
        assert_true(check_json_field_int(pkt, "version", 5));
        assert_true(check_json_field_str(pkt, "aead algorithm.str", "OCB"));
    }
    /* check the aead-encrypted packet */
    pkt = json_object_array_get_idx(jso, 1);
    if (!aead_ocb_enabled()) {
        assert_true(check_json_pkt_type(pkt, PGP_PKT_SE_IP_DATA));
    } else {
        assert_true(check_json_pkt_type(pkt, PGP_PKT_AEAD_ENCRYPTED));
        assert_true(check_json_field_int(pkt, "version", 1));
        assert_true(check_json_field_str(pkt, "aead algorithm.str", "OCB"));
        assert_true(check_json_field_int(pkt, "chunk size", 10));
    }
    json_object_put(jso);

    /* decrypt */
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass1"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;
    // compare the decrypted file
    assert_true(file_equals("decrypted", plaintext, strlen(plaintext)));
    rnp_unlink("decrypted");

    // final cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_detached_verify_input)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    // init ffi
    test_ffi_init(&ffi);
    /* verify detached signature via rnp_op_verify_create - should not crash */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_stream_signatures/source.txt.sig"));
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_detached_cleartext_signed_input)
{
    rnp_ffi_t ffi = NULL;
    test_ffi_init(&ffi);
    /* verify detached signature with cleartext input - must fail */
    rnp_input_t inputmsg = NULL;
    assert_rnp_success(rnp_input_from_path(&inputmsg, "data/test_messages/message.txt"));
    rnp_input_t inputsig = NULL;
    assert_rnp_success(
      rnp_input_from_path(&inputsig, "data/test_messages/message.txt.cleartext-signed"));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, inputmsg, inputsig));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(inputmsg);
    rnp_input_destroy(inputsig);
    /* verify detached signature with signed/embedded input - must fail */
    assert_rnp_success(rnp_input_from_path(&inputmsg, "data/test_messages/message.txt"));
    assert_rnp_success(
      rnp_input_from_path(&inputsig, "data/test_messages/message.txt.empty.sig"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, inputmsg, inputsig));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(inputmsg);
    rnp_input_destroy(inputsig);
    /* verify detached signature as a whole message - must fail */
    assert_rnp_success(rnp_input_from_path(&inputmsg, "data/test_messages/message.txt.sig"));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, inputmsg, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    rnp_op_verify_destroy(verify);
    rnp_output_destroy(output);
    rnp_input_destroy(inputmsg);

    rnp_ffi_destroy(ffi);
}

static bool
check_signature(rnp_op_verify_t op, size_t idx, rnp_result_t status)
{
    rnp_op_verify_signature_t sig = NULL;
    if (rnp_op_verify_get_signature_at(op, idx, &sig)) {
        return false;
    }
    return rnp_op_verify_signature_get_status(sig) == status;
}

TEST_F(rnp_tests, test_ffi_op_verify_sig_count)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    // init ffi
    test_ffi_init(&ffi);

    /* signed message */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_failure(rnp_op_verify_create(NULL, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_create(&verify, NULL, input, output));
    assert_rnp_failure(rnp_op_verify_create(&verify, ffi, NULL, output));
    assert_rnp_failure(rnp_op_verify_create(&verify, ffi, input, NULL));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(NULL));
    assert_rnp_success(rnp_op_verify_execute(verify));
    size_t sigcount = 0;
    assert_rnp_failure(rnp_op_verify_get_signature_count(verify, NULL));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed with unknown key */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed.unknown"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_KEY_NOT_FOUND));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed with malformed signature (bad version) */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed.malfsig"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_UNKNOWN));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed with invalid signature (modified hash alg) */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed.invsig"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed without the signature */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed.nosig"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 0);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* detached signature */
    rnp_input_t source = NULL;
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt"));
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.sig"));
    assert_rnp_failure(rnp_op_verify_detached_create(NULL, ffi, source, input));
    assert_rnp_failure(rnp_op_verify_detached_create(&verify, NULL, source, input));
    assert_rnp_failure(rnp_op_verify_detached_create(&verify, ffi, NULL, input));
    assert_rnp_failure(rnp_op_verify_detached_create(&verify, ffi, source, NULL));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    /* detached text-mode signature */
    source = NULL;
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt"));
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.sig-text"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    source = NULL;
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt.crlf"));
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.sig-text"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    /* detached text-mode signature with trailing CR characters */
    source = NULL;
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&source, "data/test_messages/message-trailing-cr.txt"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message-trailing-cr.txt.sig-text"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    /* detached text-mode signature with CRLF on 32k boundary */
    source = NULL;
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&source, "data/test_messages/message-32k-crlf.txt"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message-32k-crlf.txt.sig"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    /* embedded text-mode signature with CRLF on 32k boundary */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message-32k-crlf.txt.gpg"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* malformed detached signature */
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt"));
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.sig.malf"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 0);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    /* malformed detached signature, wrong bitlen in MPI  */
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.sig.wrong-mpi-bitlen"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    /* encrypted message */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.encrypted"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    if (!aead_eax_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(verify));
    } else {
        assert_rnp_success(rnp_op_verify_execute(verify));
    }
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 0);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* encrypted and signed message */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-encrypted"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* cleartext signed message */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.cleartext-signed"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* cleartext signed message without newline */
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(
      &input, "data/test_messages/message.txt.cleartext-signed-nonewline"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 0);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* cleartext signed with malformed signature (wrong mpi len) */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.cleartext-malf"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_UNKNOWN));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* cleartext signed without the signature */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.cleartext-nosig"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 0);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed message without compression */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-no-z"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed and password-encrypted data with 0 compression algo */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-sym-none-z"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed message with one-pass with wrong version */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-no-z-malf"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* encrypted and signed message with marker packet */
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.marker"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* encrypted and signed message with marker packet, armored */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.marker.asc"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* encrypted and signed message with malformed marker packet */
    sigcount = 255;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.marker.malf"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* sha1 detached signature over the collision-suspicious data */
    /* allow sha1 temporary */
    rnp::SecurityRule allow_sha1(
      rnp::FeatureType::Hash, PGP_HASH_SHA1, rnp::SecurityLevel::Default, 1547856001);
    global_ctx.profile.add_rule(allow_sha1);
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/shattered-1.pdf"));
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/shattered-1.pdf.sig"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    /* sha1 detached signature over the document with collision*/
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/shattered-2.pdf"));
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/shattered-1.pdf.sig"));
    assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(source);
    rnp_input_destroy(input);

    /* sha1 attached signature over the collision-suspicious data */
    sigcount = 255;
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/shattered-2.pdf.gpg"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* remove sha1 rule */
    assert_true(global_ctx.profile.del_rule(allow_sha1));

    /* signed message with key which is now expired */
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
    import_pub_keys(ffi, "data/test_messages/expired_signing_key-pub.asc");
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "30FC0D776915BA44", &key));
    uint64_t till = 0;
    assert_rnp_success(rnp_key_valid_till64(key, &till));
    assert_int_equal(till, 1623424417);
    assert_rnp_success(rnp_key_handle_destroy(key));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-expired-key"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed message with subkey which is now expired */
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
    import_pub_keys(ffi, "data/test_messages/expired_signing_sub-pub.asc");
    key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "D93A47FD93191FD1", &key));
    till = 0;
    assert_rnp_success(rnp_key_valid_till64(key, &till));
    assert_int_equal(till, 1623933507);
    assert_rnp_success(rnp_key_handle_destroy(key));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-expired-sub"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed message with md5 hash */
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
    assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed.md5"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed message with md5 hash before the cut-off date */
    assert_true(import_all_keys(ffi, "data/test_key_edge_cases/key-rsa-2001-pub.asc"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-md5-before"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed message with md5 hash right after the cut-off date */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-md5-after"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* signed message with sha1 hash */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed.sha1"));
    assert_rnp_success(rnp_output_to_null(&output));
    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message signed with sha1 before the cut-off date */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-sha1-before"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_SUCCESS));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message signed with sha1 right after the cut-off date */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-sha1-after"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_op_verify_get_protection_info)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    // init ffi
    test_ffi_init(&ffi);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));

    /* message just signed */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    char *mode = NULL;
    char *cipher = NULL;
    bool  valid = true;
    assert_rnp_failure(rnp_op_verify_get_protection_info(NULL, &mode, &cipher, &valid));
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, NULL, NULL));
    assert_string_equal(mode, "none");
    rnp_buffer_destroy(mode);
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, NULL, &cipher, NULL));
    assert_string_equal(cipher, "none");
    rnp_buffer_destroy(cipher);
    valid = true;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, NULL, NULL, &valid));
    assert_false(valid);
    assert_rnp_failure(rnp_op_verify_get_protection_info(verify, NULL, NULL, NULL));
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "none");
    assert_string_equal(cipher, "none");
    assert_false(valid);
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message without MDC */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-no-mdc"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    mode = NULL;
    cipher = NULL;
    valid = true;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "cfb");
    assert_string_equal(cipher, "AES256");
    assert_false(valid);
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message with MDC */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.enc-mdc"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    mode = NULL;
    cipher = NULL;
    valid = false;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "cfb-mdc");
    assert_string_equal(cipher, "AES256");
    assert_true(valid);
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message with AEAD-OCB */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    if (!aead_ocb_enabled() || aead_ocb_aes_only()) {
        assert_rnp_failure(rnp_op_verify_execute(verify));
    } else {
        assert_rnp_success(rnp_op_verify_execute(verify));
    }
    mode = NULL;
    cipher = NULL;
    valid = false;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "aead-ocb");
    assert_string_equal(cipher, "CAMELLIA192");
    assert_true(valid == (aead_ocb_enabled() && !aead_ocb_aes_only()));
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message with AEAD-OCB AES-192 */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb-aes"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    if (!aead_ocb_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(verify));
    } else {
        assert_rnp_success(rnp_op_verify_execute(verify));
    }
    mode = NULL;
    cipher = NULL;
    valid = false;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "aead-ocb");
    assert_string_equal(cipher, "AES192");
    assert_true(valid == aead_ocb_enabled());
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* modified message with AEAD-OCB */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb-malf"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    mode = NULL;
    cipher = NULL;
    valid = false;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "aead-ocb");
    assert_string_equal(cipher, "CAMELLIA192");
    assert_false(valid);
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message with AEAD-EAX */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    if (!aead_eax_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(verify));
    } else {
        assert_rnp_success(rnp_op_verify_execute(verify));
    }
    mode = NULL;
    cipher = NULL;
    valid = false;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "aead-eax");
    assert_string_equal(cipher, "AES256");
    assert_true(valid == aead_eax_enabled());
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* modified message with AEAD-EAX */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax-malf"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    mode = NULL;
    cipher = NULL;
    valid = false;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "aead-eax");
    assert_string_equal(cipher, "AES256");
    assert_false(valid);
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    rnp_ffi_destroy(ffi);
}

static bool
getpasscb_for_key(rnp_ffi_t        ffi,
                  void *           app_ctx,
                  rnp_key_handle_t key,
                  const char *     pgp_context,
                  char *           buf,
                  size_t           buf_len)
{
    if (!key) {
        return false;
    }
    char *keyid = NULL;
    rnp_key_get_keyid(key, &keyid);
    if (!keyid) {
        return false;
    }
    const char *pass = "password";
    if (strcmp(keyid, (const char *) app_ctx)) {
        pass = "wrongpassword";
    }
    size_t pass_len = strlen(pass);
    rnp_buffer_destroy(keyid);

    if (pass_len >= buf_len) {
        return false;
    }
    memcpy(buf, pass, pass_len + 1);
    return true;
}

TEST_F(rnp_tests, test_ffi_op_verify_recipients_info)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    // init ffi
    test_ffi_init(&ffi);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));

    /* message just signed */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    /* check filename and mtime */
    char *   filename = NULL;
    uint32_t mtime = 0;
    assert_rnp_failure(rnp_op_verify_get_file_info(NULL, &filename, &mtime));
    assert_rnp_success(rnp_op_verify_get_file_info(verify, &filename, &mtime));
    assert_string_equal(filename, "message.txt");
    assert_int_equal(mtime, 1571991574);
    rnp_buffer_destroy(filename);
    filename = NULL;
    assert_rnp_success(rnp_op_verify_get_file_info(verify, &filename, NULL));
    assert_string_equal(filename, "message.txt");
    rnp_buffer_destroy(filename);
    mtime = 0;
    assert_rnp_success(rnp_op_verify_get_file_info(verify, NULL, &mtime));
    assert_int_equal(mtime, 1571991574);
    /* rnp_op_verify_get_recipient_count */
    assert_rnp_failure(rnp_op_verify_get_recipient_count(verify, NULL));
    size_t count = 255;
    assert_rnp_failure(rnp_op_verify_get_recipient_count(NULL, &count));
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 0);
    /* rnp_op_verify_get_recipient_at */
    rnp_recipient_handle_t recipient = NULL;
    assert_rnp_failure(rnp_op_verify_get_recipient_at(NULL, 0, &recipient));
    assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 0, NULL));
    assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
    assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 10, &recipient));
    /* rnp_op_verify_get_used_recipient */
    assert_rnp_failure(rnp_op_verify_get_used_recipient(NULL, &recipient));
    assert_rnp_failure(rnp_op_verify_get_used_recipient(verify, NULL));
    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
    assert_null(recipient);
    /* rnp_op_verify_get_symenc_count */
    assert_rnp_failure(rnp_op_verify_get_symenc_count(verify, NULL));
    count = 255;
    assert_rnp_failure(rnp_op_verify_get_symenc_count(NULL, &count));
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 0);
    /* rnp_op_verify_get_symenc_at */
    rnp_symenc_handle_t symenc = NULL;
    assert_rnp_failure(rnp_op_verify_get_symenc_at(NULL, 0, &symenc));
    assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 0, NULL));
    assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
    assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 10, &symenc));
    /* rnp_op_verify_get_used_symenc */
    assert_rnp_failure(rnp_op_verify_get_used_symenc(NULL, &symenc));
    assert_rnp_failure(rnp_op_verify_get_used_symenc(verify, NULL));
    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
    assert_null(symenc);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message without MDC: single recipient */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-no-mdc"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 1);
    assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 1, &recipient));
    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
    assert_non_null(recipient);
    char *alg = NULL;
    assert_rnp_failure(rnp_recipient_get_alg(NULL, &alg));
    assert_rnp_failure(rnp_recipient_get_alg(recipient, NULL));
    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
    assert_string_equal(alg, "RSA");
    rnp_buffer_destroy(alg);
    char *keyid = NULL;
    assert_rnp_failure(rnp_recipient_get_keyid(NULL, &keyid));
    assert_rnp_failure(rnp_recipient_get_keyid(recipient, NULL));
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_string_equal(keyid, "8A05B89FAD5ADED1");
    rnp_buffer_destroy(keyid);
    recipient = NULL;
    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
    assert_non_null(recipient);
    alg = NULL;
    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
    assert_string_equal(alg, "RSA");
    rnp_buffer_destroy(alg);
    keyid = NULL;
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_string_equal(keyid, "8A05B89FAD5ADED1");
    rnp_buffer_destroy(keyid);
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 0);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message with AEAD-OCB: single password */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    if (!aead_ocb_enabled() || aead_ocb_aes_only()) {
        assert_rnp_failure(rnp_op_verify_execute(verify));
    } else {
        assert_rnp_success(rnp_op_verify_execute(verify));
    }
    count = 255;
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 0);
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 1);
    assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
    assert_non_null(symenc);
    char *cipher = NULL;
    assert_rnp_failure(rnp_symenc_get_cipher(symenc, NULL));
    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
    assert_string_equal(cipher, "CAMELLIA192");
    rnp_buffer_destroy(cipher);
    char *aead = NULL;
    assert_rnp_failure(rnp_symenc_get_aead_alg(symenc, NULL));
    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
    assert_string_equal(aead, "OCB");
    rnp_buffer_destroy(aead);
    char *hash = NULL;
    assert_rnp_failure(rnp_symenc_get_hash_alg(symenc, NULL));
    assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
    assert_string_equal(hash, "SHA1");
    rnp_buffer_destroy(hash);
    char *s2k = NULL;
    assert_rnp_failure(rnp_symenc_get_s2k_type(symenc, NULL));
    assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
    assert_string_equal(s2k, "Iterated and salted");
    rnp_buffer_destroy(s2k);
    uint32_t iterations = 0;
    assert_rnp_failure(rnp_symenc_get_s2k_iterations(symenc, NULL));
    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
    assert_int_equal(iterations, 30408704);
    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
    if (!aead_ocb_enabled() || aead_ocb_aes_only()) {
        assert_null(symenc);
    } else {
        assert_non_null(symenc);
        cipher = NULL;
        assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
        assert_string_equal(cipher, "CAMELLIA192");
        rnp_buffer_destroy(cipher);
        aead = NULL;
        assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
        assert_string_equal(aead, "OCB");
        rnp_buffer_destroy(aead);
        hash = NULL;
        assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
        assert_string_equal(hash, "SHA1");
        rnp_buffer_destroy(hash);
        s2k = NULL;
        assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
        assert_string_equal(s2k, "Iterated and salted");
        rnp_buffer_destroy(s2k);
        iterations = 0;
        assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
        assert_int_equal(iterations, 30408704);
    }
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message with AEAD-OCB-AES: single password */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb-aes"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    if (!aead_ocb_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(verify));
    } else {
        assert_rnp_success(rnp_op_verify_execute(verify));
    }
    count = 255;
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 0);
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 1);
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
    assert_non_null(symenc);
    cipher = NULL;
    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
    assert_string_equal(cipher, "AES192");
    rnp_buffer_destroy(cipher);
    aead = NULL;
    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
    assert_string_equal(aead, "OCB");
    rnp_buffer_destroy(aead);
    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
    if (!aead_ocb_enabled()) {
        assert_null(symenc);
    } else {
        assert_non_null(symenc);
        cipher = NULL;
        assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
        assert_string_equal(cipher, "AES192");
        rnp_buffer_destroy(cipher);
        aead = NULL;
        assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
        assert_string_equal(aead, "OCB");
        rnp_buffer_destroy(aead);
    }
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* modified message with AEAD-EAX: one recipient and one password, decrypt with recipient
     */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax-malf"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    count = 255;
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 1);
    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
    assert_non_null(recipient);
    alg = NULL;
    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
    assert_string_equal(alg, "RSA");
    rnp_buffer_destroy(alg);
    keyid = NULL;
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_string_equal(keyid, "1ED63EE56FADC34D");
    rnp_buffer_destroy(keyid);
    recipient = NULL;
    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
    if (!aead_eax_enabled()) {
        assert_null(recipient);
    } else {
        assert_non_null(recipient);
        assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
        assert_string_equal(alg, "RSA");
        rnp_buffer_destroy(alg);
        assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
        assert_string_equal(keyid, "1ED63EE56FADC34D");
        rnp_buffer_destroy(keyid);
        assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
        assert_non_null(recipient);
        alg = NULL;
        assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
        assert_string_equal(alg, "RSA");
        rnp_buffer_destroy(alg);
        keyid = NULL;
        assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
        assert_string_equal(keyid, "1ED63EE56FADC34D");
        rnp_buffer_destroy(keyid);
        recipient = NULL;
        assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
        assert_non_null(recipient);
        assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
        assert_string_equal(alg, "RSA");
        rnp_buffer_destroy(alg);
        assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
        assert_string_equal(keyid, "1ED63EE56FADC34D");
        rnp_buffer_destroy(keyid);
    }

    count = 255;
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 1);
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
    assert_non_null(symenc);
    cipher = NULL;
    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
    assert_string_equal(cipher, "AES256");
    rnp_buffer_destroy(cipher);
    aead = NULL;
    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
    assert_string_equal(aead, "EAX");
    rnp_buffer_destroy(aead);
    hash = NULL;
    assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
    assert_string_equal(hash, "SHA256");
    rnp_buffer_destroy(hash);
    s2k = NULL;
    assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
    assert_string_equal(s2k, "Iterated and salted");
    rnp_buffer_destroy(s2k);
    iterations = 0;
    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
    assert_int_equal(iterations, 3932160);
    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
    assert_null(symenc);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message with AEAD-EAX: one recipient and one password, decrypt with password */
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    if (!aead_eax_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(verify));
    } else {
        assert_rnp_success(rnp_op_verify_execute(verify));
    }
    count = 255;
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 1);
    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
    assert_null(recipient);
    count = 255;
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 1);
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
    assert_non_null(symenc);
    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
    if (!aead_eax_enabled()) {
        assert_null(symenc);
    } else {
        assert_non_null(symenc);
        cipher = NULL;
        assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
        assert_string_equal(cipher, "AES256");
        rnp_buffer_destroy(cipher);
        aead = NULL;
        assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
        assert_string_equal(aead, "EAX");
        rnp_buffer_destroy(aead);
        hash = NULL;
        assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
        assert_string_equal(hash, "SHA256");
        rnp_buffer_destroy(hash);
        s2k = NULL;
        assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
        assert_string_equal(s2k, "Iterated and salted");
        rnp_buffer_destroy(s2k);
        iterations = 0;
        assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
        assert_int_equal(iterations, 3932160);
    }
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* message encrypted to 3 recipients and 2 passwords: password1, password2 */
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrongpassword"));
    assert_true(import_sec_keys(ffi, "data/keyrings/1/secring.gpg"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    count = 255;
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 3);
    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_string_equal(keyid, "1ED63EE56FADC34D");
    rnp_buffer_destroy(keyid);
    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 1, &recipient));
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_string_equal(keyid, "8A05B89FAD5ADED1");
    rnp_buffer_destroy(keyid);
    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 2, &recipient));
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_string_equal(keyid, "54505A936A4A970E");
    rnp_buffer_destroy(keyid);
    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
    assert_null(recipient);
    count = 255;
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 2);
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
    assert_int_equal(iterations, 3932160);
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
    assert_int_equal(iterations, 3276800);
    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
    assert_null(symenc);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password2"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
    assert_null(recipient);
    assert_int_equal(iterations, 3276800);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, getpasscb_for_key, (void *) "8A05B89FAD5ADED1"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
    assert_null(symenc);
    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_string_equal(keyid, "8A05B89FAD5ADED1");
    rnp_buffer_destroy(keyid);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_secret_sig_import)
{
    rnp_ffi_t   ffi = NULL;
    rnp_input_t input = NULL;

    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc"));
    rnp_key_handle_t key_handle = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
    bool locked = false;
    /* unlock secret key */
    assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
    assert_true(locked);
    assert_rnp_success(rnp_key_unlock(key_handle, "password"));
    assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
    assert_false(locked);
    /* import revocation signature */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_key_validity/alice-rev.pgp"));
    assert_rnp_success(rnp_import_signatures(ffi, input, 0, NULL));
    assert_rnp_success(rnp_input_destroy(input));
    /* make sure that key is still unlocked */
    assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
    assert_false(locked);
    /* import subkey */
    assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sub-sec.pgp"));
    /* make sure that primary key is still unlocked */
    assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
    assert_false(locked);
    /* unlock subkey and make sure it is unlocked after revocation */
    rnp_key_handle_t sub_handle = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub_handle));
    assert_rnp_success(rnp_key_unlock(sub_handle, "password"));
    assert_rnp_success(rnp_key_is_locked(sub_handle, &locked));
    assert_false(locked);
    assert_rnp_success(rnp_key_revoke(sub_handle, 0, "SHA256", "retired", "Custom reason"));
    assert_rnp_success(rnp_key_is_locked(sub_handle, &locked));
    assert_false(locked);
    assert_rnp_success(rnp_key_handle_destroy(sub_handle));
    assert_rnp_success(rnp_key_handle_destroy(key_handle));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

static bool
getpasscb_fail(rnp_ffi_t        ffi,
               void *           app_ctx,
               rnp_key_handle_t key,
               const char *     pgp_context,
               char *           buf,
               size_t           buf_len)
{
    return false;
}

static bool
getpasscb_context(rnp_ffi_t        ffi,
                  void *           app_ctx,
                  rnp_key_handle_t key,
                  const char *     pgp_context,
                  char *           buf,
                  size_t           buf_len)
{
    strncpy(buf, pgp_context, buf_len - 1);
    return true;
}

static bool
getpasscb_keyid(rnp_ffi_t        ffi,
                void *           app_ctx,
                rnp_key_handle_t key,
                const char *     pgp_context,
                char *           buf,
                size_t           buf_len)
{
    if (!key) {
        return false;
    }
    char *keyid = NULL;
    if (rnp_key_get_keyid(key, &keyid)) {
        return false;
    }
    strncpy(buf, keyid, buf_len - 1);
    rnp_buffer_destroy(keyid);
    return true;
}

TEST_F(rnp_tests, test_ffi_rnp_request_password)
{
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    /* check wrong parameters cases */
    char *password = NULL;
    assert_rnp_failure(rnp_request_password(ffi, NULL, "sign", &password));
    assert_null(password);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_failure(rnp_request_password(NULL, NULL, "sign", &password));
    assert_rnp_failure(rnp_request_password(ffi, NULL, "sign", NULL));
    /* now it should succeed */
    assert_rnp_success(rnp_request_password(ffi, NULL, "sign", &password));
    assert_string_equal(password, "password");
    rnp_buffer_destroy(password);
    /* let's try failing password provider */
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_fail, NULL));
    assert_rnp_failure(rnp_request_password(ffi, NULL, "sign", &password));
    /* let's try to return request context */
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_context, NULL));
    assert_rnp_success(rnp_request_password(ffi, NULL, "custom context", &password));
    assert_string_equal(password, "custom context");
    rnp_buffer_destroy(password);
    /* let's check whether key is correctly passed to handler */
    assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc"));
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
    assert_non_null(key);
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_keyid, NULL));
    assert_rnp_success(rnp_request_password(ffi, key, NULL, &password));
    assert_string_equal(password, "0451409669FFDE3C");
    rnp_buffer_destroy(password);
    assert_rnp_success(rnp_key_handle_destroy(key));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_ffi_mdc_8k_boundary)
{
    rnp_ffi_t   ffi = NULL;
    rnp_input_t input = NULL;

    test_ffi_init(&ffi);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));

    /* correctly process two messages */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message_mdc_8k_1.pgp"));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    /* check signature */
    size_t sig_count = 0;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
    assert_int_equal(sig_count, 1);
    rnp_op_verify_signature_t sig = NULL;
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    /* cleanup */
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));

    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message_mdc_8k_2.pgp"));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    /* check signature */
    sig_count = 0;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
    assert_int_equal(sig_count, 1);
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    /* cleanup */
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));

    /* let it gracefully fail on message 1 with the last byte cut */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message_mdc_8k_cut1.pgp"));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    /* cleanup */
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));

    /* let it gracefully fail on message 1 with the last 22 bytes (MDC size) cut */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message_mdc_8k_cut22.pgp"));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    /* cleanup */
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));

    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_ffi_decrypt_wrong_mpi_bits)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    // init ffi
    test_ffi_init(&ffi);

    /* 1024 bitcount instead of 1023 */
    rnp_op_verify_t op = NULL;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-1"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    if (!aead_eax_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(op));
    } else {
        assert_rnp_success(rnp_op_verify_execute(op));
    }
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* 1025 bitcount instead of 1023 */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-2"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    if (!aead_eax_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(op));
    } else {
        assert_rnp_success(rnp_op_verify_execute(op));
    }
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* 1031 bitcount instead of 1023 */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-3"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    if (!aead_eax_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(op));
    } else {
        assert_rnp_success(rnp_op_verify_execute(op));
    }
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* 1040 bitcount instead of 1023 */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-4"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    if (!aead_eax_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(op));
    } else {
        assert_rnp_success(rnp_op_verify_execute(op));
    }
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* 1017 bitcount instead of 1023 */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-5"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    if (!aead_eax_enabled()) {
        assert_rnp_failure(rnp_op_verify_execute(op));
    } else {
        assert_rnp_success(rnp_op_verify_execute(op));
    }
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_decrypt_edge_cases)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    test_ffi_init(&ffi);

    /* unknown algorithm in public-key encrypted session key */
    rnp_op_verify_t op = NULL;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-wrong-alg"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(op));
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* endless recursive compression packets, 'quine'.
     * Generated using the code by Taylor R. Campbell */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.zlib-quine"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(op));
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.zlib-quine"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.zlib-quine"));
    char *json = NULL;
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    assert_non_null(json);
    rnp_buffer_destroy(json);
    rnp_input_destroy(input);

    /* 128 levels of compression - fail decryption*/
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr.128-rounds"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(op));
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* but dumping will succeed */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr.128-rounds"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr.128-rounds"));
    json = NULL;
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    assert_non_null(json);
    rnp_buffer_destroy(json);
    rnp_input_destroy(input);

    /* 32 levels of compression + encryption */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr-encr.32-rounds"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(op));
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr-encr.32-rounds"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr-encr.32-rounds"));
    json = NULL;
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    assert_non_null(json);
    rnp_buffer_destroy(json);
    rnp_input_destroy(input);

    /* 31 levels of compression + encryption */
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr-encr.31-rounds"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(op));
    rnp_op_verify_destroy(op);
    rnp_input_destroy(input);
    uint8_t *buf = NULL;
    size_t   len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 7);
    assert_int_equal(memcmp(buf, "message", 7), 0);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr-encr.31-rounds"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.compr-encr.31-rounds"));
    json = NULL;
    assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
    assert_non_null(json);
    rnp_buffer_destroy(json);
    rnp_input_destroy(input);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_key_remove)
{
    rnp_ffi_t ffi = NULL;
    test_ffi_init(&ffi);

    rnp_key_handle_t key0 = NULL;
    rnp_key_handle_t key0_sub0 = NULL;
    rnp_key_handle_t key0_sub1 = NULL;
    rnp_key_handle_t key0_sub2 = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key0));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1d7e8a5393c997a8", &key0_sub1));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &key0_sub2));

    /* edge cases */
    assert_rnp_failure(rnp_key_remove(NULL, RNP_KEY_REMOVE_PUBLIC));
    assert_rnp_failure(rnp_key_remove(key0, 0));
    /* make sure we correctly remove public and secret keys */
    bool pub = false;
    assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
    assert_true(pub);
    bool sec = false;
    assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
    assert_true(sec);
    assert_rnp_success(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_PUBLIC));
    pub = true;
    assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
    assert_false(pub);
    sec = false;
    assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
    assert_true(sec);
    assert_rnp_failure(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_PUBLIC));
    rnp_key_handle_destroy(key0_sub2);
    /* locate it back */
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &key0_sub2));
    assert_non_null(key0_sub2);
    pub = true;
    assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
    assert_false(pub);
    sec = false;
    assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
    assert_true(sec);

    pub = false;
    assert_rnp_success(rnp_key_have_public(key0_sub0, &pub));
    assert_true(pub);
    sec = false;
    assert_rnp_success(rnp_key_have_secret(key0_sub0, &sec));
    assert_true(sec);
    assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
    pub = false;
    assert_rnp_success(rnp_key_have_public(key0_sub0, &pub));
    assert_true(pub);
    sec = true;
    assert_rnp_success(rnp_key_have_secret(key0_sub0, &sec));
    assert_false(sec);
    assert_rnp_failure(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
    rnp_key_handle_destroy(key0_sub0);
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));
    assert_non_null(key0_sub0);

    size_t count = 0;
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(count, 6);
    count = 0;
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 6);

    /* while there are 2 public and 1 secret subkey, this calculates only public */
    assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
    assert_int_equal(count, 2);

    assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_PUBLIC));
    assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
    assert_int_equal(count, 1);
    count = 0;
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(count, 5);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 6);

    assert_rnp_success(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_SECRET));
    assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
    assert_int_equal(count, 1);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 5);

    assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET));
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(count, 4);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 4);

    rnp_key_handle_destroy(key0_sub1);
    /* key0_sub1 should be left in keyring */
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1d7e8a5393c997a8", &key0_sub1));
    pub = false;
    assert_rnp_success(rnp_key_have_public(key0_sub1, &pub));
    assert_true(pub);
    sec = false;
    assert_rnp_success(rnp_key_have_secret(key0_sub1, &sec));
    assert_true(sec);

    rnp_key_handle_destroy(key0);
    rnp_key_handle_destroy(key0_sub0);
    rnp_key_handle_destroy(key0_sub1);
    rnp_key_handle_destroy(key0_sub2);

    /* let's import keys back */
    assert_true(import_pub_keys(ffi, "data/keyrings/1/pubring.gpg"));
    assert_true(import_sec_keys(ffi, "data/keyrings/1/secring.gpg"));

    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(count, 7);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 7);

    /* now try to remove the whole key */
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key0));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));

    assert_rnp_failure(
      rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
    assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
    assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_PUBLIC));

    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(count, 6);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 6);

    assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(count, 6);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 3);

    assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SUBKEYS));
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(count, 3);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 3);

    rnp_key_handle_destroy(key0);
    rnp_key_handle_destroy(key0_sub0);

    /* delete the second key all at once */
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "2fcadf05ffa501bb", &key0));
    assert_rnp_success(rnp_key_remove(
      key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
    assert_int_equal(count, 0);
    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
    assert_int_equal(count, 0);
    rnp_key_handle_destroy(key0);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_literal_packet)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    // init ffi
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    /* try rnp_decrypt() */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.literal"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    uint8_t *buf = NULL;
    size_t   len = 0;
    rnp_output_memory_get_buf(output, &buf, &len, false);
    std::string out;
    out.assign((char *) buf, len);
    assert_true(out == file_to_str("data/test_messages/message.txt"));
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* try rnp_op_verify() */
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.literal"));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    rnp_output_memory_get_buf(output, &buf, &len, false);
    out.assign((char *) buf, len);
    assert_true(out == file_to_str("data/test_messages/message.txt"));
    char *mode = NULL;
    char *cipher = NULL;
    bool  valid = true;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "none");
    assert_string_equal(cipher, "none");
    assert_false(valid);
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    size_t count = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &count));
    assert_int_equal(count, 0);
    count = 255;
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 0);
    count = 255;
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 0);
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    rnp_ffi_destroy(ffi);
}

/* This test checks that any exceptions thrown by the internal library
 * will not propagate beyond the FFI boundary.
 * In this case we (ab)use a callback to mimic this scenario.
 */
TEST_F(rnp_tests, test_ffi_exception)
{
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;

    // bad_alloc -> RNP_ERROR_OUT_OF_MEMORY
    {
        auto reader = [](void *app_ctx, void *buf, size_t len, size_t *read) {
            throw std::bad_alloc();
            return true;
        };
        assert_rnp_success(rnp_input_from_callback(&input, reader, NULL, NULL));
        assert_rnp_success(rnp_output_to_memory(&output, 0));
        assert_int_equal(RNP_ERROR_OUT_OF_MEMORY, rnp_output_pipe(input, output));
        rnp_input_destroy(input);
        rnp_output_destroy(output);
    }

    // runtime_error -> RNP_ERROR_GENERIC
    {
        auto reader = [](void *app_ctx, void *buf, size_t len, size_t *read) {
            throw std::runtime_error("");
            return true;
        };
        assert_rnp_success(rnp_input_from_callback(&input, reader, NULL, NULL));
        assert_rnp_success(rnp_output_to_memory(&output, 0));
        assert_int_equal(RNP_ERROR_GENERIC, rnp_output_pipe(input, output));
        rnp_input_destroy(input);
        rnp_output_destroy(output);
    }

    // everything else -> RNP_ERROR_GENERIC
    {
        auto reader = [](void *app_ctx, void *buf, size_t len, size_t *read) {
            throw 5;
            return true;
        };
        assert_rnp_success(rnp_input_from_callback(&input, reader, NULL, NULL));
        assert_rnp_success(rnp_output_to_memory(&output, 0));
        assert_int_equal(RNP_ERROR_GENERIC, rnp_output_pipe(input, output));
        rnp_input_destroy(input);
        rnp_output_destroy(output);
    }
}

TEST_F(rnp_tests, test_ffi_key_protection_change)
{
    rnp_ffi_t ffi = NULL;
    test_ffi_init(&ffi);

    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
    rnp_key_handle_t sub = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &sub));

    assert_rnp_success(rnp_key_unprotect(key, "password"));
    assert_rnp_success(rnp_key_unprotect(sub, "password"));

    bool protect = true;
    assert_rnp_success(rnp_key_is_protected(key, &protect));
    assert_false(protect);
    protect = true;
    assert_rnp_success(rnp_key_is_protected(sub, &protect));
    assert_false(protect);

    assert_rnp_success(rnp_key_protect(key, "password2", "Camellia128", NULL, "SHA1", 0));
    assert_rnp_success(rnp_key_protect(sub, "password2", "Camellia256", NULL, "SHA512", 0));
    protect = false;
    assert_rnp_success(rnp_key_is_protected(key, &protect));
    assert_true(protect);
    protect = false;
    assert_rnp_success(rnp_key_is_protected(sub, &protect));
    assert_true(protect);

    rnp_key_handle_destroy(key);
    rnp_key_handle_destroy(sub);

    reload_keyrings(&ffi);

    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &sub));

    protect = false;
    assert_rnp_success(rnp_key_is_protected(key, &protect));
    assert_true(protect);
    protect = false;
    assert_rnp_success(rnp_key_is_protected(sub, &protect));
    assert_true(protect);

    char *cipher = NULL;
    assert_rnp_success(rnp_key_get_protection_cipher(key, &cipher));
    assert_string_equal(cipher, "CAMELLIA128");
    rnp_buffer_destroy(cipher);
    char *hash = NULL;
    assert_rnp_success(rnp_key_get_protection_hash(key, &hash));
    assert_string_equal(hash, "SHA1");
    rnp_buffer_destroy(hash);
    cipher = NULL;
    assert_rnp_success(rnp_key_get_protection_cipher(sub, &cipher));
    assert_string_equal(cipher, "CAMELLIA256");
    rnp_buffer_destroy(cipher);
    hash = NULL;
    assert_rnp_success(rnp_key_get_protection_hash(sub, &hash));
    assert_string_equal(hash, "SHA512");
    rnp_buffer_destroy(hash);

    assert_rnp_failure(rnp_key_unlock(key, "password"));
    assert_rnp_failure(rnp_key_unlock(sub, "password"));

    assert_rnp_success(rnp_key_unlock(key, "password2"));
    assert_rnp_success(rnp_key_unlock(sub, "password2"));

    protect = false;
    assert_rnp_success(rnp_key_is_protected(key, &protect));
    assert_true(protect);
    protect = false;
    assert_rnp_success(rnp_key_is_protected(sub, &protect));
    assert_true(protect);

    assert_rnp_success(rnp_key_protect(key, "password3", "AES256", NULL, "SHA512", 10000000));
    assert_rnp_success(rnp_key_protect(sub, "password3", "AES128", NULL, "SHA1", 10000000));
    protect = false;
    assert_rnp_success(rnp_key_is_protected(key, &protect));
    assert_true(protect);
    protect = false;
    assert_rnp_success(rnp_key_is_protected(sub, &protect));
    assert_true(protect);

    rnp_key_handle_destroy(key);
    rnp_key_handle_destroy(sub);

    reload_keyrings(&ffi);

    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &sub));

    protect = false;
    assert_rnp_success(rnp_key_is_protected(key, &protect));
    assert_true(protect);
    protect = false;
    assert_rnp_success(rnp_key_is_protected(sub, &protect));
    assert_true(protect);

    cipher = NULL;
    assert_rnp_success(rnp_key_get_protection_cipher(key, &cipher));
    assert_string_equal(cipher, "AES256");
    rnp_buffer_destroy(cipher);
    hash = NULL;
    assert_rnp_success(rnp_key_get_protection_hash(key, &hash));
    assert_string_equal(hash, "SHA512");
    rnp_buffer_destroy(hash);
    size_t iterations = 0;
    assert_rnp_success(rnp_key_get_protection_iterations(key, &iterations));
    assert_true(iterations >= 10000000);
    cipher = NULL;
    assert_rnp_success(rnp_key_get_protection_cipher(sub, &cipher));
    assert_string_equal(cipher, "AES128");
    rnp_buffer_destroy(cipher);
    hash = NULL;
    assert_rnp_success(rnp_key_get_protection_hash(sub, &hash));
    assert_string_equal(hash, "SHA1");
    rnp_buffer_destroy(hash);
    iterations = 0;
    assert_rnp_success(rnp_key_get_protection_iterations(sub, &iterations));
    assert_true(iterations >= 10000000);

    assert_rnp_failure(rnp_key_unlock(key, "password"));
    assert_rnp_failure(rnp_key_unlock(sub, "password"));

    assert_rnp_success(rnp_key_unlock(key, "password3"));
    assert_rnp_success(rnp_key_unlock(sub, "password3"));

    rnp_key_handle_destroy(key);
    rnp_key_handle_destroy(sub);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_set_log_fd)
{
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_rnp_failure(rnp_ffi_set_log_fd(NULL, 0));
    assert_rnp_failure(rnp_ffi_set_log_fd(ffi, 100));
    int file_fd = rnp_open("tests.txt", O_RDWR | O_CREAT | O_TRUNC, 0777);
    assert_true(file_fd > 0);
    assert_rnp_success(rnp_ffi_set_log_fd(ffi, file_fd));
    rnp_input_t input = NULL;
    const char *msg = "hello";
    assert_rnp_success(
      rnp_input_from_memory(&input, (const uint8_t *) msg, strlen(msg), true));
    char *saved_env = NULL;
    if (getenv(RNP_LOG_CONSOLE)) {
        saved_env = strdup(getenv(RNP_LOG_CONSOLE));
    }
    setenv(RNP_LOG_CONSOLE, "1", 1);
    assert_rnp_failure(rnp_load_keys(ffi, "GPG", input, 119));
    assert_rnp_success(rnp_input_destroy(input));
    rnp_ffi_destroy(ffi);
    if (saved_env) {
        assert_int_equal(0, setenv(RNP_LOG_CONSOLE, saved_env, 1));
        free(saved_env);
    } else {
        unsetenv(RNP_LOG_CONSOLE);
    }
#ifndef _WIN32
    assert_int_equal(fcntl(file_fd, F_GETFD), -1);
    assert_int_equal(errno, EBADF);
#endif
    char buffer[128] = {0};
    file_fd = rnp_open("tests.txt", O_RDONLY, 0);
    int64_t rres = read(file_fd, buffer, sizeof(buffer));
    assert_true(rres > 0);
    assert_non_null(strstr(buffer, "unexpected flags remaining: 0x"));
    close(file_fd);
}

TEST_F(rnp_tests, test_ffi_security_profile)
{
    rnp_ffi_t ffi = NULL;
    rnp_ffi_create(&ffi, "GPG", "GPG");
    /* check predefined rules */
    uint32_t flags = 0;
    uint64_t from = 0;
    uint32_t level = 0;
    assert_rnp_failure(
      rnp_get_security_rule(NULL, RNP_FEATURE_HASH_ALG, "SHA1", 0, &flags, &from, &level));
    assert_rnp_failure(rnp_get_security_rule(ffi, NULL, "SHA1", 0, &flags, &from, &level));
    assert_rnp_failure(
      rnp_get_security_rule(ffi, RNP_FEATURE_SYMM_ALG, "AES256", 0, &flags, &from, &level));
    assert_rnp_failure(
      rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "Unknown", 0, &flags, &from, &level));
    assert_rnp_failure(
      rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", 0, NULL, NULL, NULL));
    /* default rule */
    from = 256;
    assert_rnp_success(
      rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA256", 0, &flags, &from, &level));
    assert_int_equal(level, RNP_SECURITY_DEFAULT);
    assert_int_equal(from, 0);
    /* MD5 */
    from = 256;
    assert_rnp_success(
      rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "MD5", 0, &flags, &from, &level));
    assert_int_equal(from, 0);
    assert_int_equal(level, RNP_SECURITY_DEFAULT);
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
    assert_int_equal(from, MD5_FROM);
    assert_int_equal(level, RNP_SECURITY_INSECURE);
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "MD5", MD5_FROM - 1, &flags, &from, &level));
    assert_int_equal(from, 0);
    assert_int_equal(flags, 0);
    assert_int_equal(level, RNP_SECURITY_DEFAULT);
    /* Override it */
    assert_rnp_failure(rnp_add_security_rule(
      NULL, RNP_FEATURE_HASH_ALG, "MD5", RNP_SECURITY_OVERRIDE, 0, RNP_SECURITY_DEFAULT));
    assert_rnp_failure(rnp_add_security_rule(
      ffi, RNP_FEATURE_SYMM_ALG, "MD5", RNP_SECURITY_OVERRIDE, 0, RNP_SECURITY_DEFAULT));
    assert_rnp_failure(
      rnp_add_security_rule(ffi, NULL, "MD5", RNP_SECURITY_OVERRIDE, 0, RNP_SECURITY_DEFAULT));
    assert_rnp_failure(rnp_add_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, NULL, RNP_SECURITY_OVERRIDE, 0, RNP_SECURITY_DEFAULT));
    assert_rnp_failure(rnp_add_security_rule(ffi,
                                             RNP_FEATURE_HASH_ALG,
                                             "MD5",
                                             RNP_SECURITY_OVERRIDE | 0x17,
                                             0,
                                             RNP_SECURITY_DEFAULT));
    assert_rnp_failure(
      rnp_add_security_rule(ffi, RNP_FEATURE_HASH_ALG, "MD5", RNP_SECURITY_OVERRIDE, 0, 25));
    assert_rnp_success(rnp_add_security_rule(ffi,
                                             RNP_FEATURE_HASH_ALG,
                                             "MD5",
                                             RNP_SECURITY_OVERRIDE,
                                             MD5_FROM - 1,
                                             RNP_SECURITY_DEFAULT));
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
    assert_int_equal(from, MD5_FROM - 1);
    assert_int_equal(flags, RNP_SECURITY_OVERRIDE);
    assert_int_equal(level, RNP_SECURITY_DEFAULT);
    /* Remove and check back */
    size_t removed = 0;
    assert_rnp_failure(rnp_remove_security_rule(NULL,
                                                RNP_FEATURE_HASH_ALG,
                                                "MD5",
                                                RNP_SECURITY_DEFAULT,
                                                RNP_SECURITY_OVERRIDE,
                                                MD5_FROM - 1,
                                                &removed));
    assert_rnp_failure(rnp_remove_security_rule(ffi,
                                                RNP_FEATURE_SYMM_ALG,
                                                "MD5",
                                                RNP_SECURITY_DEFAULT,
                                                RNP_SECURITY_OVERRIDE,
                                                MD5_FROM - 1,
                                                &removed));
    assert_rnp_success(rnp_remove_security_rule(ffi,
                                                RNP_FEATURE_HASH_ALG,
                                                "SHA256",
                                                RNP_SECURITY_DEFAULT,
                                                RNP_SECURITY_OVERRIDE,
                                                MD5_FROM - 1,
                                                &removed));
    assert_int_equal(removed, 0);
    removed = 1;
    assert_rnp_success(rnp_remove_security_rule(ffi,
                                                RNP_FEATURE_HASH_ALG,
                                                "MD5",
                                                RNP_SECURITY_INSECURE,
                                                RNP_SECURITY_OVERRIDE,
                                                MD5_FROM - 1,
                                                &removed));
    assert_int_equal(removed, 0);
    removed = 1;
    assert_rnp_success(rnp_remove_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "MD5", RNP_SECURITY_DEFAULT, 0, MD5_FROM - 1, &removed));
    assert_int_equal(removed, 0);
    removed = 1;
    assert_rnp_success(rnp_remove_security_rule(ffi,
                                                RNP_FEATURE_HASH_ALG,
                                                "MD5",
                                                RNP_SECURITY_DEFAULT,
                                                RNP_SECURITY_OVERRIDE,
                                                MD5_FROM,
                                                &removed));
    assert_int_equal(removed, 0);
    assert_rnp_success(rnp_remove_security_rule(ffi,
                                                RNP_FEATURE_HASH_ALG,
                                                "MD5",
                                                RNP_SECURITY_DEFAULT,
                                                RNP_SECURITY_OVERRIDE,
                                                MD5_FROM - 1,
                                                &removed));
    assert_int_equal(removed, 1);
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
    assert_int_equal(from, MD5_FROM);
    assert_int_equal(level, RNP_SECURITY_INSECURE);
    assert_int_equal(flags, 0);
    /* Add for data sigs only */
    assert_rnp_success(rnp_add_security_rule(ffi,
                                             RNP_FEATURE_HASH_ALG,
                                             "MD5",
                                             RNP_SECURITY_VERIFY_DATA,
                                             MD5_FROM + 1,
                                             RNP_SECURITY_DEFAULT));
    flags = RNP_SECURITY_VERIFY_DATA;
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
    assert_int_equal(from, MD5_FROM + 1);
    assert_int_equal(flags, RNP_SECURITY_VERIFY_DATA);
    assert_int_equal(level, RNP_SECURITY_DEFAULT);
    /* Add for key sigs only */
    assert_rnp_success(rnp_add_security_rule(ffi,
                                             RNP_FEATURE_HASH_ALG,
                                             "MD5",
                                             RNP_SECURITY_VERIFY_KEY,
                                             MD5_FROM + 2,
                                             RNP_SECURITY_DEFAULT));
    flags = RNP_SECURITY_VERIFY_KEY;
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
    assert_int_equal(from, MD5_FROM + 2);
    assert_int_equal(flags, RNP_SECURITY_VERIFY_KEY);
    assert_int_equal(level, RNP_SECURITY_DEFAULT);
    /* Remove added two rules */
    assert_rnp_success(rnp_remove_security_rule(ffi,
                                                RNP_FEATURE_HASH_ALG,
                                                "MD5",
                                                RNP_SECURITY_DEFAULT,
                                                RNP_SECURITY_VERIFY_DATA,
                                                MD5_FROM + 1,
                                                &removed));
    assert_int_equal(removed, 1);
    assert_rnp_success(rnp_remove_security_rule(ffi,
                                                RNP_FEATURE_HASH_ALG,
                                                "MD5",
                                                RNP_SECURITY_DEFAULT,
                                                RNP_SECURITY_VERIFY_KEY,
                                                MD5_FROM + 2,
                                                &removed));
    assert_int_equal(removed, 1);
    /* Remove all */
    removed = 0;
    assert_rnp_failure(rnp_remove_security_rule(ffi, NULL, NULL, 0, 0x17, 0, &removed));
    assert_rnp_success(rnp_remove_security_rule(ffi, NULL, NULL, 0, 0, 0, &removed));
    assert_int_equal(removed, 3);
    rnp_ffi_destroy(ffi);
    rnp_ffi_create(&ffi, "GPG", "GPG");
    /* Remove all rules for hash */
    assert_rnp_failure(
      rnp_remove_security_rule(ffi, RNP_FEATURE_SYMM_ALG, NULL, 0, 0, 0, &removed));
    removed = 0;
    assert_rnp_success(
      rnp_remove_security_rule(ffi, RNP_FEATURE_HASH_ALG, NULL, 0, 0, 0, &removed));
    assert_int_equal(removed, 3);
    rnp_ffi_destroy(ffi);
    rnp_ffi_create(&ffi, "GPG", "GPG");
    /* Remove all rules for specific hash */
    assert_rnp_success(rnp_remove_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "MD5", 0, RNP_SECURITY_REMOVE_ALL, 0, &removed));
    assert_int_equal(removed, 1);
    assert_rnp_success(rnp_remove_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "SHA1", 0, RNP_SECURITY_REMOVE_ALL, 0, &removed));
    assert_int_equal(removed, 2);
    rnp_ffi_destroy(ffi);
    rnp_ffi_create(&ffi, "GPG", "GPG");
    /* SHA1 - ancient times */
    from = 256;
    flags = 0;
    assert_rnp_success(
      rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", 0, &flags, &from, &level));
    assert_int_equal(from, 0);
    assert_int_equal(level, RNP_SECURITY_DEFAULT);
    assert_int_equal(flags, 0);
    /* SHA1 - now, data verify disabled, key sig verify is enabled */
    flags = 0;
    auto now = time(NULL);
    bool sha1_cutoff = now > SHA1_KEY_FROM;
    /* This would pick default rule closer to the date independent on usage */
    assert_rnp_success(
      rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", now, &flags, &from, &level));
    auto expect_from = sha1_cutoff ? SHA1_KEY_FROM : SHA1_DATA_FROM;
    auto expect_usage = sha1_cutoff ? RNP_SECURITY_VERIFY_KEY : RNP_SECURITY_VERIFY_DATA;
    assert_int_equal(from, expect_from);
    assert_int_equal(level, RNP_SECURITY_INSECURE);
    assert_int_equal(flags, expect_usage);
    flags = 0;
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "SHA1", SHA1_DATA_FROM - 1, &flags, &from, &level));
    assert_int_equal(from, 0);
    assert_int_equal(level, RNP_SECURITY_DEFAULT);
    flags = RNP_SECURITY_VERIFY_DATA;
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "SHA1", time(NULL), &flags, &from, &level));
    assert_int_equal(from, SHA1_DATA_FROM);
    assert_int_equal(level, RNP_SECURITY_INSECURE);
    assert_int_equal(flags, RNP_SECURITY_VERIFY_DATA);
    flags = RNP_SECURITY_VERIFY_KEY;
    assert_rnp_success(
      rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", now, &flags, &from, &level));
    expect_from = sha1_cutoff ? SHA1_KEY_FROM : 0;
    auto expect_level = sha1_cutoff ? RNP_SECURITY_INSECURE : RNP_SECURITY_DEFAULT;
    expect_usage = sha1_cutoff ? RNP_SECURITY_VERIFY_KEY : 0;
    assert_int_equal(from, expect_from);
    assert_int_equal(level, expect_level);
    assert_int_equal(flags, expect_usage);
    flags = RNP_SECURITY_VERIFY_KEY;
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "SHA1", SHA1_KEY_FROM + 5, &flags, &from, &level));
    assert_int_equal(from, SHA1_KEY_FROM);
    assert_int_equal(level, RNP_SECURITY_INSECURE);
    assert_int_equal(flags, RNP_SECURITY_VERIFY_KEY);
    flags = 0;
    assert_rnp_success(rnp_get_security_rule(
      ffi, RNP_FEATURE_HASH_ALG, "SHA1", SHA1_KEY_FROM + 5, &flags, &from, &level));
    assert_int_equal(from, SHA1_KEY_FROM);
    assert_int_equal(level, RNP_SECURITY_INSECURE);
    assert_int_equal(flags, RNP_SECURITY_VERIFY_KEY);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_result_to_string)
{
    const char *          result_string = NULL;
    rnp_result_t          code;
    std::set<std::string> stringset;

    result_string = rnp_result_to_string(RNP_SUCCESS);
    assert_string_equal(result_string, "Success");

    /* Cover all defined error code ranges,
     * check that each defined
     * code has corresponding unique string */

    std::vector<std::pair<rnp_result_t, rnp_result_t>> error_codes = {
      {RNP_ERROR_GENERIC, RNP_ERROR_NULL_POINTER},
      {RNP_ERROR_ACCESS, RNP_ERROR_WRITE},
      {RNP_ERROR_BAD_STATE, RNP_ERROR_SIGNATURE_UNKNOWN},
      {RNP_ERROR_NOT_ENOUGH_DATA, RNP_ERROR_EOF}};

    for (auto &range : error_codes) {
        for (code = range.first; code <= range.second; code++) {
            result_string = rnp_result_to_string(code);

            assert_int_not_equal(strcmp("Unsupported error code", result_string), 0);

            auto search = stringset.find(result_string);

            /* Make sure returned error string is not already returned for other codes */
            assert_true(search == stringset.end());

            stringset.insert(result_string);
        }
    }
}
