/*
 * 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 "support.h"
#include "rnp_tests.h"
#include "str-utils.h"
#include <librepgp/stream-ctx.h>
#include "pgp-key.h"
#include "librepgp/stream-armor.h"
#include "ffi-priv-types.h"

#ifdef _MSC_VER
#include "uniwin.h"
#include <shlwapi.h>
#else
#include <sys/types.h>
#include <sys/param.h>
#endif

#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>

#include <crypto.h>
#include <pgp-key.h>
#include <fstream>
#include <vector>
#include <algorithm>

#ifndef WINSHELLAPI
#include <ftw.h>
#endif

#ifdef _WIN32
int
setenv(const char *name, const char *value, int overwrite)
{
    if (getenv(name) && !overwrite) {
        return 0;
    }
    char varbuf[512] = {0};
    snprintf(varbuf, sizeof(varbuf) - 1, "%s=%s", name, value);
    return _putenv(varbuf);
}

int
unsetenv(const char *name)
{
    char varbuf[512] = {0};
    snprintf(varbuf, sizeof(varbuf) - 1, "%s=", name);
    return _putenv(varbuf);
}
#endif

std::string
file_to_str(const std::string &path)
{
    // TODO: wstring path _WIN32
    std::ifstream infile(path);
    return std::string(std::istreambuf_iterator<char>(infile),
                       std::istreambuf_iterator<char>());
}

std::vector<uint8_t>
file_to_vec(const std::string &path)
{
    // TODO: wstring path _WIN32
    std::ifstream stream(path, std::ios::in | std::ios::binary);
    return std::vector<uint8_t>((std::istreambuf_iterator<char>(stream)),
                                std::istreambuf_iterator<char>());
}

void
str_to_file(const std::string &path, const char *str)
{
    std::ofstream stream(path, std::ios::out | std::ios::binary);
    stream.write(str, strlen(str));
}

off_t
file_size(const char *path)
{
    struct stat path_stat;
    if (rnp_stat(path, &path_stat) != -1) {
        if (S_ISDIR(path_stat.st_mode)) {
            return -1;
        }
        return path_stat.st_size;
    }
    return -1;
}

/* Concatenate multiple strings into a full path.
 * A directory separator is added between components.
 * Must be called in between va_start and va_end.
 * Final argument of calling function must be NULL.
 */
void
vpaths_concat(char *buffer, size_t buffer_size, const char *first, va_list ap)
{
    size_t      length = strlen(first);
    const char *s;

    assert_true(length < buffer_size);

    memset(buffer, 0, buffer_size);

    strncpy(buffer, first, buffer_size - 1);
    while ((s = va_arg(ap, const char *))) {
        length += strlen(s) + 1;
        assert_true(length < buffer_size);
        strncat(buffer, "/", buffer_size - 1);
        strncat(buffer, s, buffer_size - 1);
    }
}

/* Concatenate multiple strings into a full path.
 * Final argument must be NULL.
 */
char *
paths_concat(char *buffer, size_t buffer_length, const char *first, ...)
{
    va_list ap;

    va_start(ap, first);
    vpaths_concat(buffer, buffer_length, first, ap);
    va_end(ap);
    return buffer;
}

/* Concatenate multiple strings into a full path and
 * check that the file exists.
 * Final argument must be NULL.
 */
int
path_rnp_file_exists(const char *first, ...)
{
    va_list ap;
    char    buffer[512] = {0};

    va_start(ap, first);
    vpaths_concat(buffer, sizeof(buffer), first, ap);
    va_end(ap);
    return rnp_file_exists(buffer);
}

/* Concatenate multiple strings into a full path and
 * create the directory.
 * Final argument must be NULL.
 */
void
path_mkdir(mode_t mode, const char *first, ...)
{
    va_list ap;
    char    buffer[512];

    va_start(ap, first);
    vpaths_concat(buffer, sizeof(buffer), first, ap);
    va_end(ap);

    assert_int_equal(0, RNP_MKDIR(buffer, mode));
}

#ifndef WINSHELLAPI
static int
remove_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
{
    int ret = remove(fpath);
    if (ret)
        perror(fpath);

    return ret;
}
#endif

static const char *
get_tmp()
{
    const char *tmp = getenv("TEMP");
    return tmp ? tmp : "/tmp";
}

static bool
is_tmp_path(const char *path)
{
    char *rlpath = realpath(path, NULL);
    if (!rlpath) {
        rlpath = strdup(path);
    }
    const char *tmp = get_tmp();
    char *      rltmp = realpath(tmp, NULL);
    if (!rltmp) {
        rltmp = strdup(tmp);
    }
    bool res = rlpath && rltmp && !strncmp(rlpath, rltmp, strlen(rltmp));
    free(rlpath);
    free(rltmp);
    return res;
}

/* Recursively remove a directory.
 * The path must be located in /tmp, for safety.
 */
void
delete_recursively(const char *path)
{
    bool relative =
#ifdef _MSC_VER
      PathIsRelativeA(path);
#else
      *path != '/';
#endif
    std::string fullpath = path;
    if (relative) {
        char *cwd = getcwd(NULL, 0);
        fullpath = rnp::path::append(cwd, fullpath);
        free(cwd);
    }
    /* sanity check, we should only be purging things from /tmp/ */
    assert_true(is_tmp_path(fullpath.c_str()));

#ifdef WINSHELLAPI
    SHFILEOPSTRUCTA fileOp = {};
    fileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
    assert_true(fullpath.size() < MAX_PATH);
    char newFrom[MAX_PATH + 1];
    strcpy_s(newFrom, fullpath.c_str());
    newFrom[fullpath.size() + 1] = NULL; // two NULLs are required
    fileOp.pFrom = newFrom;
    fileOp.pTo = NULL;
    fileOp.wFunc = FO_DELETE;
    fileOp.hNameMappings = NULL;
    fileOp.hwnd = NULL;
    fileOp.lpszProgressTitle = NULL;
    SHFileOperationA(&fileOp);
#else
    nftw(path, remove_cb, 64, FTW_DEPTH | FTW_PHYS);
#endif
}

void
copy_recursively(const char *src, const char *dst)
{
    assert_true(src != nullptr);
    /* sanity check, we should only be copying things to /tmp/ */
    assert_true(is_tmp_path(dst));

#ifdef WINSHELLAPI
    SHFILEOPSTRUCTA fileOp = {};
    fileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR;
    fileOp.pFrom = src;
    fileOp.pTo = dst;
    assert_true(strlen(src) < MAX_PATH);
    char newFrom[MAX_PATH + 1];
    strcpy_s(newFrom, src);
    newFrom[strlen(src) + 1] = NULL; // two NULLs are required
    fileOp.pFrom = newFrom;
    assert_true(strlen(dst) < MAX_PATH);
    char newTo[MAX_PATH + 1];
    strcpy_s(newTo, dst);
    newTo[strlen(dst) + 1] = NULL; // two NULLs are required
    fileOp.wFunc = FO_COPY;
    fileOp.hNameMappings = NULL;
    fileOp.hwnd = NULL;
    fileOp.lpszProgressTitle = NULL;
    assert_int_equal(0, SHFileOperationA(&fileOp));
#else
    // TODO: maybe use fts or something less hacky
    char buf[2048];
#ifndef _WIN32
    snprintf(buf, sizeof(buf), "cp -a '%s' '%s'", src, dst);
#else
    snprintf(buf, sizeof(buf), "xcopy \"%s\" \"%s\" /I /Q /E /Y", src, dst);
#endif // _WIN32
    assert_int_equal(0, system(buf));
#endif // WINSHELLAPI
}

/* Creates and returns a temporary directory path.
 * Caller must free the string.
 */
#if defined(HAVE_MKDTEMP)
char *
make_temp_dir()
{
    char rltmp[PATH_MAX] = {0};
    if (!realpath(get_tmp(), rltmp)) {
        printf("Fatal: realpath on tmp folder failed. Error %d.\n", errno);
        return NULL;
    }

    const char *tmplate = "/rnp-gtest-XXXXXX";
    char *      buffer = (char *) calloc(1, strlen(rltmp) + strlen(tmplate) + 1);
    if (buffer == NULL) {
        return NULL;
    }
    memcpy(buffer, rltmp, strlen(rltmp));
    memcpy(buffer + strlen(rltmp), tmplate, strlen(tmplate));
    buffer[strlen(rltmp) + strlen(tmplate)] = '\0';
    char *res = mkdtemp(buffer);
    if (!res) {
        free(buffer);
    }
    return res;
}
#elif defined(HAVE__TEMPNAM)
char *
make_temp_dir()
{
    const int MAX_ATTEMPTS = 10;
    for (int i = 0; i < MAX_ATTEMPTS; i++) {
        char *dir = _tempnam(NULL, "rnp-gtest-");
        if (!dir) {
            fprintf(stderr, "_tempnam failed to generate temporary path");
            continue;
        }
        if (RNP_MKDIR(dir, S_IRWXU)) {
            fprintf(stderr, "Failed to create temporary directory");
            free(dir);
            continue;
        }
        return dir;
    }
    fprintf(stderr, "Failed to make temporary directory, aborting");
    return NULL;
}
#else
#error Unsupported platform
#endif

void
clean_temp_dir(const char *path)
{
    if (!getenv("RNP_KEEP_TEMP")) {
        delete_recursively(path);
    }
}

bool
bin_eq_hex(const uint8_t *data, size_t len, const char *val)
{
    size_t stlen = strlen(val);
    if (stlen != len * 2) {
        return false;
    }

    std::vector<uint8_t> dec(len);
    rnp::hex_decode(val, dec.data(), len);
    return !memcmp(data, dec.data(), len);
}

bool
hex2mpi(pgp_mpi_t *val, const char *hex)
{
    const size_t hex_len = strlen(hex);
    size_t       buf_len = hex_len / 2;
    bool         ok;

    uint8_t *buf = NULL;

    buf = (uint8_t *) malloc(buf_len);

    if (buf == NULL) {
        return false;
    }

    rnp::hex_decode(hex, buf, buf_len);

    ok = mem2mpi(val, buf, buf_len);
    free(buf);
    return ok;
}

bool
cmp_keyid(const pgp_key_id_t &id, const std::string &val)
{
    return bin_eq_hex(id.data(), id.size(), val.c_str());
}

bool
cmp_keyfp(const pgp_fingerprint_t &fp, const std::string &val)
{
    return bin_eq_hex(fp.fingerprint, fp.length, val.c_str());
}

void
test_ffi_init(rnp_ffi_t *ffi)
{
    // 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", "data/keyrings/1/secring.gpg"));
}

bool
mpi_empty(const pgp_mpi_t &val)
{
    pgp_mpi_t zero{};
    return (val.len == 0) && !memcmp(val.mpi, zero.mpi, PGP_MPINT_SIZE);
}

bool
write_pass_to_pipe(int fd, size_t count)
{
    const char *const password = "passwordforkeygeneration\n";
    for (size_t i = 0; i < count; i++) {
        const char *p = password;
        ssize_t     remaining = strlen(p);

        do {
            ssize_t written = write(fd, p, remaining);
            if (written <= 0) {
                perror("write");
                return false;
            }
            p += written;
            remaining -= written;
        } while (remaining);
    }
    return true;
}

bool
setupPasswordfd(int *pipefd)
{
    bool ok = false;

    if (pipe(pipefd) == -1) {
        perror("pipe");
        goto end;
    }
    // write it twice for normal keygen (primary+sub)
    if (!write_pass_to_pipe(pipefd[1], 2)) {
        close(pipefd[1]);
        goto end;
    }
    ok = true;

end:
    close(pipefd[1]);
    return ok;
}

static bool
setup_rnp_cfg(rnp_cfg &cfg, const char *ks_format, const char *homedir, int *pipefd)
{
    bool res;
    char pubpath[MAXPATHLEN];
    char secpath[MAXPATHLEN];
    char homepath[MAXPATHLEN];

    /* set password fd if any */
    if (pipefd) {
        if (!(res = setupPasswordfd(pipefd))) {
            return res;
        }
        cfg.set_int(CFG_PASSFD, pipefd[0]);
        // pipefd[0] will be closed via passfp
        pipefd[0] = -1;
    }
    /* setup keyring paths */
    if (homedir == NULL) {
        /* if we use default homedir then we append '.rnp' and create directory as well */
        homedir = getenv("HOME");
        paths_concat(homepath, sizeof(homepath), homedir, ".rnp", NULL);
        if (!rnp_dir_exists(homepath)) {
            path_mkdir(0700, homepath, NULL);
        }
        homedir = homepath;
    }

    if (homedir == NULL) {
        return false;
    }

    cfg.set_str(CFG_KR_PUB_FORMAT, ks_format);
    cfg.set_str(CFG_KR_SEC_FORMAT, ks_format);

    if (strcmp(ks_format, RNP_KEYSTORE_GPG) == 0) {
        paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_GPG, NULL);
        paths_concat(secpath, MAXPATHLEN, homedir, SECRING_GPG, NULL);
    } else if (strcmp(ks_format, RNP_KEYSTORE_KBX) == 0) {
        paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_KBX, NULL);
        paths_concat(secpath, MAXPATHLEN, homedir, SECRING_KBX, NULL);
    } else if (strcmp(ks_format, RNP_KEYSTORE_G10) == 0) {
        paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_G10, NULL);
        paths_concat(secpath, MAXPATHLEN, homedir, SECRING_G10, NULL);
    } else if (strcmp(ks_format, RNP_KEYSTORE_GPG21) == 0) {
        paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_KBX, NULL);
        paths_concat(secpath, MAXPATHLEN, homedir, SECRING_G10, NULL);
        cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_KBX);
        cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_G10);
    } else {
        return false;
    }

    cfg.set_str(CFG_KR_PUB_PATH, (char *) pubpath);
    cfg.set_str(CFG_KR_SEC_PATH, (char *) secpath);
    return true;
}

bool
setup_cli_rnp_common(cli_rnp_t *rnp, const char *ks_format, const char *homedir, int *pipefd)
{
    rnp_cfg cfg;
    if (!setup_rnp_cfg(cfg, ks_format, homedir, pipefd)) {
        return false;
    }

    /*initialize the basic RNP structure. */
    return rnp->init(cfg);
}

void
cli_set_default_rsa_key_desc(rnp_cfg &cfg, const char *hashalg)
{
    cfg.set_int(CFG_NUMBITS, 1024);
    cfg.set_str(CFG_HASH, hashalg);
    cfg.set_int(CFG_S2K_ITER, 1);
    cli_rnp_set_generate_params(cfg);
}

// this is a password callback that will always fail
bool
failing_password_callback(const pgp_password_ctx_t *ctx,
                          char *                    password,
                          size_t                    password_size,
                          void *                    userdata)
{
    return false;
}

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

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

bool
ffi_string_password_provider(rnp_ffi_t        ffi,
                             void *           app_ctx,
                             rnp_key_handle_t key,
                             const char *     pgp_context,
                             char *           buf,
                             size_t           buf_len)
{
    size_t pass_len = strlen((const char *) app_ctx);
    if (pass_len >= buf_len) {
        return false;
    }
    memcpy(buf, app_ctx, pass_len + 1);
    return true;
}

// this is a password callback that should never be called
bool
asserting_password_callback(const pgp_password_ctx_t *ctx,
                            char *                    password,
                            size_t                    password_size,
                            void *                    userdata)
{
    EXPECT_TRUE(false);
    return false;
}

// this is a password callback that just copies the string in userdata to
// the password buffer
bool
string_copy_password_callback(const pgp_password_ctx_t *ctx,
                              char *                    password,
                              size_t                    password_size,
                              void *                    userdata)
{
    const char *str = (const char *) userdata;
    strncpy(password, str, password_size - 1);
    return true;
}

void
unused_getkeycb(rnp_ffi_t   ffi,
                void *      app_ctx,
                const char *identifier_type,
                const char *identifier,
                bool        secret)
{
    EXPECT_TRUE(false);
}

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

bool
starts_with(const std::string &data, const std::string &match)
{
    return data.find(match) == 0;
}

bool
ends_with(const std::string &data, const std::string &match)
{
    return data.size() >= match.size() &&
           data.substr(data.size() - match.size(), match.size()) == match;
}

std::string
fmt(const char *format, ...)
{
    int     size;
    va_list ap;

    va_start(ap, format);
    size = vsnprintf(NULL, 0, format, ap);
    va_end(ap);

    // +1 for terminating null
    std::string buf(size + 1, '\0');

    va_start(ap, format);
    size = vsnprintf(&buf[0], buf.size(), format, ap);
    va_end(ap);

    // drop terminating null
    buf.resize(size);
    return buf;
}

std::string
strip_eol(const std::string &str)
{
    size_t endpos = str.find_last_not_of("\r\n");
    if (endpos != std::string::npos) {
        return str.substr(0, endpos + 1);
    }
    return str;
}

std::string
lowercase(const std::string &str)
{
    std::string res = str;
    std::transform(
      res.begin(), res.end(), res.begin(), [](unsigned char ch) { return std::tolower(ch); });
    return res;
}

static bool
jso_get_field(json_object *obj, json_object **fld, const std::string &name)
{
    if (!obj || !json_object_is_type(obj, json_type_object)) {
        return false;
    }
    return json_object_object_get_ex(obj, name.c_str(), fld);
}

bool
check_json_field_str(json_object *obj, const std::string &field, const std::string &value)
{
    json_object *fld = NULL;
    if (!jso_get_field(obj, &fld, field)) {
        return false;
    }
    if (!json_object_is_type(fld, json_type_string)) {
        return false;
    }
    const char *jsoval = json_object_get_string(fld);
    return jsoval && (value == jsoval);
}

bool
check_json_field_int(json_object *obj, const std::string &field, int value)
{
    json_object *fld = NULL;
    if (!jso_get_field(obj, &fld, field)) {
        return false;
    }
    if (!json_object_is_type(fld, json_type_int)) {
        return false;
    }
    return json_object_get_int(fld) == value;
}

bool
check_json_field_bool(json_object *obj, const std::string &field, bool value)
{
    json_object *fld = NULL;
    if (!jso_get_field(obj, &fld, field)) {
        return false;
    }
    if (!json_object_is_type(fld, json_type_boolean)) {
        return false;
    }
    // 'json_object_get_boolean' returns 'json_bool' which is 'int' on Windows
    // but bool on other platforms
    return (json_object_get_boolean(fld) ? true : false) == value;
}

bool
check_json_pkt_type(json_object *pkt, int tag)
{
    if (!pkt || !json_object_is_type(pkt, json_type_object)) {
        return false;
    }
    json_object *hdr = NULL;
    if (!json_object_object_get_ex(pkt, "header", &hdr)) {
        return false;
    }
    if (!json_object_is_type(hdr, json_type_object)) {
        return false;
    }
    return check_json_field_int(hdr, "tag", tag);
}

pgp_key_t *
rnp_tests_get_key_by_id(rnp_key_store_t *keyring, const std::string &keyid, pgp_key_t *after)
{
    if (!keyring || keyid.empty() || !rnp::is_hex(keyid)) {
        return NULL;
    }
    pgp_key_id_t keyid_bin = {};
    size_t       binlen = rnp::hex_decode(keyid.c_str(), keyid_bin.data(), keyid_bin.size());
    if (binlen > PGP_KEY_ID_SIZE) {
        return NULL;
    }
    pgp_key_search_t search(PGP_KEY_SEARCH_KEYID);
    search.by.keyid = keyid_bin;
    return rnp_key_store_search(keyring, &search, after);
}

pgp_key_t *
rnp_tests_get_key_by_grip(rnp_key_store_t *keyring, const std::string &grip)
{
    if (!keyring || grip.empty() || !rnp::is_hex(grip)) {
        return NULL;
    }
    pgp_key_grip_t grip_bin = {};
    size_t         binlen = rnp::hex_decode(grip.c_str(), grip_bin.data(), grip_bin.size());
    if (binlen > PGP_KEY_GRIP_SIZE) {
        return NULL;
    }
    return rnp_tests_get_key_by_grip(keyring, grip_bin);
}

pgp_key_t *
rnp_tests_get_key_by_grip(rnp_key_store_t *keyring, const pgp_key_grip_t &grip)
{
    if (!keyring) {
        return NULL;
    }
    pgp_key_search_t search(PGP_KEY_SEARCH_GRIP);
    search.by.grip = grip;
    return rnp_key_store_search(keyring, &search, NULL);
}

pgp_key_t *
rnp_tests_get_key_by_fpr(rnp_key_store_t *keyring, const std::string &keyid)
{
    if (!keyring || keyid.empty() || !rnp::is_hex(keyid)) {
        return NULL;
    }
    std::vector<uint8_t> keyid_bin(PGP_FINGERPRINT_SIZE, 0);
    size_t binlen = rnp::hex_decode(keyid.c_str(), keyid_bin.data(), keyid_bin.size());
    if (binlen > PGP_FINGERPRINT_SIZE) {
        return NULL;
    }
    pgp_fingerprint_t fp = {{}, static_cast<unsigned>(binlen)};
    memcpy(fp.fingerprint, keyid_bin.data(), binlen);
    return rnp_key_store_get_key_by_fpr(keyring, fp);
}

pgp_key_t *
rnp_tests_key_search(rnp_key_store_t *keyring, const std::string &uid)
{
    if (!keyring || uid.empty()) {
        return NULL;
    }

    pgp_key_search_t srch_userid(PGP_KEY_SEARCH_USERID);
    strncpy(srch_userid.by.userid, uid.c_str(), sizeof(srch_userid.by.userid));
    srch_userid.by.userid[sizeof(srch_userid.by.userid) - 1] = '\0';
    return rnp_key_store_search(keyring, &srch_userid, NULL);
}

void
reload_pubring(rnp_ffi_t *ffi)
{
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_save_keys(*ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
    assert_rnp_success(rnp_ffi_destroy(*ffi));

    /* get output */
    uint8_t *buf = NULL;
    size_t   len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    rnp_input_t input = NULL;
    assert_rnp_success(rnp_input_from_memory(&input, buf, len, false));

    /* re-init ffi and load keys */
    assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG"));
    assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_input_destroy(input));
}

void
reload_keyrings(rnp_ffi_t *ffi)
{
    rnp_output_t outpub = NULL;
    assert_rnp_success(rnp_output_to_memory(&outpub, 0));
    assert_rnp_success(rnp_save_keys(*ffi, "GPG", outpub, RNP_LOAD_SAVE_PUBLIC_KEYS));
    rnp_output_t outsec = NULL;
    assert_rnp_success(rnp_output_to_memory(&outsec, 0));
    assert_rnp_success(rnp_save_keys(*ffi, "GPG", outsec, RNP_LOAD_SAVE_SECRET_KEYS));
    assert_rnp_success(rnp_ffi_destroy(*ffi));
    /* re-init ffi and load keys */
    assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG"));

    uint8_t *buf = NULL;
    size_t   len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(outpub, &buf, &len, false));
    rnp_input_t input = NULL;
    assert_rnp_success(rnp_input_from_memory(&input, buf, len, false));
    assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(outpub));

    assert_rnp_success(rnp_output_memory_get_buf(outsec, &buf, &len, false));
    assert_rnp_success(rnp_input_from_memory(&input, buf, len, false));
    assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_SECRET_KEYS, NULL));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(outsec));
}

static bool
load_keys_internal(rnp_ffi_t          ffi,
                   const std::string &format,
                   const std::string &path,
                   bool               secret)
{
    if (path.empty()) {
        return true;
    }
    rnp_input_t input = NULL;
    if (rnp_input_from_path(&input, path.c_str())) {
        return false;
    }
    bool res = !rnp_load_keys(ffi,
                              format.c_str(),
                              input,
                              secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS);
    rnp_input_destroy(input);
    return res;
}

bool
load_keys_gpg(rnp_ffi_t ffi, const std::string &pub, const std::string &sec)
{
    return load_keys_internal(ffi, "GPG", pub, false) &&
           load_keys_internal(ffi, "GPG", sec, true);
}

bool
load_keys_kbx_g10(rnp_ffi_t ffi, const std::string &pub, const std::string &sec)
{
    return load_keys_internal(ffi, "KBX", pub, false) &&
           load_keys_internal(ffi, "G10", sec, true);
}

static bool
import_keys(rnp_ffi_t ffi, const std::string &path, uint32_t flags)
{
    rnp_input_t input = NULL;
    if (rnp_input_from_path(&input, path.c_str())) {
        return false;
    }
    bool res = !rnp_import_keys(ffi, input, flags, NULL);
    rnp_input_destroy(input);
    return res;
}

bool
import_all_keys(rnp_ffi_t ffi, const std::string &path)
{
    return import_keys(ffi, path, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS);
}

bool
import_pub_keys(rnp_ffi_t ffi, const std::string &path)
{
    return import_keys(ffi, path, RNP_LOAD_SAVE_PUBLIC_KEYS);
}

bool
import_sec_keys(rnp_ffi_t ffi, const std::string &path)
{
    return import_keys(ffi, path, RNP_LOAD_SAVE_SECRET_KEYS);
}

static bool
import_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len, uint32_t flags)
{
    rnp_input_t input = NULL;
    if (rnp_input_from_memory(&input, data, len, false)) {
        return false;
    }
    bool res = !rnp_import_keys(ffi, input, flags, NULL);
    rnp_input_destroy(input);
    return res;
}

bool
import_all_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len, uint32_t flags)
{
    return import_keys(
      ffi, data, len, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | flags);
}

bool
import_pub_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len)
{
    return import_keys(ffi, data, len, RNP_LOAD_SAVE_PUBLIC_KEYS);
}

bool
import_sec_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len)
{
    return import_keys(ffi, data, len, RNP_LOAD_SAVE_SECRET_KEYS);
}

std::vector<uint8_t>
export_key(rnp_key_handle_t key, bool armored, bool secret)
{
    uint32_t flags = RNP_KEY_EXPORT_SUBKEYS;
    if (armored) {
        flags = flags | RNP_KEY_EXPORT_ARMORED;
    }
    if (secret) {
        flags = flags | RNP_KEY_EXPORT_SECRET;
    } else {
        flags = flags | RNP_KEY_EXPORT_PUBLIC;
    }
    rnp_output_t output = NULL;
    rnp_output_to_memory(&output, 0);
    rnp_key_export(key, output, flags);
    size_t   len = 0;
    uint8_t *buf = NULL;
    rnp_output_memory_get_buf(output, &buf, &len, false);
    std::vector<uint8_t> res(buf, buf + len);
    rnp_output_destroy(output);
    return res;
}

#if 0
void
dump_key_stdout(rnp_key_handle_t key, bool secret)
{
    auto pub = export_key(key, true, false);
    printf("%.*s", (int) pub.size(), (char *) pub.data());
    if (!secret) {
        return;
    }
    auto sec = export_key(key, true, true);
    printf("%.*s", (int) sec.size(), (char *) sec.data());
}
#endif

bool
write_transferable_key(pgp_transferable_key_t &key, pgp_dest_t &dst, bool armor)
{
    pgp_key_sequence_t keys;
    keys.keys.push_back(key);
    return write_transferable_keys(keys, &dst, armor);
}

bool
write_transferable_keys(pgp_key_sequence_t &keys, pgp_dest_t *dst, bool armor)
{
    std::unique_ptr<rnp::ArmoredDest> armdst;
    if (armor) {
        pgp_armored_msg_t msgtype = PGP_ARMORED_PUBLIC_KEY;
        if (!keys.keys.empty() && is_secret_key_pkt(keys.keys.front().key.tag)) {
            msgtype = PGP_ARMORED_SECRET_KEY;
        }
        armdst = std::unique_ptr<rnp::ArmoredDest>(new rnp::ArmoredDest(*dst, msgtype));
        dst = &armdst->dst();
    }

    for (auto &key : keys.keys) {
        /* main key */
        key.key.write(*dst);
        /* revocation and direct-key signatures */
        for (auto &sig : key.signatures) {
            sig.write(*dst);
        }
        /* user ids/attrs and signatures */
        for (auto &uid : key.userids) {
            uid.uid.write(*dst);
            for (auto &sig : uid.signatures) {
                sig.write(*dst);
            }
        }
        /* subkeys with signatures */
        for (auto &skey : key.subkeys) {
            skey.subkey.write(*dst);
            for (auto &sig : skey.signatures) {
                sig.write(*dst);
            }
        }
    }
    return !dst->werr;
}

bool
check_uid_valid(rnp_key_handle_t key, size_t idx, bool valid)
{
    rnp_uid_handle_t uid = NULL;
    if (rnp_key_get_uid_handle_at(key, idx, &uid)) {
        return false;
    }
    bool val = !valid;
    rnp_uid_is_valid(uid, &val);
    rnp_uid_handle_destroy(uid);
    return val == valid;
}

bool
check_uid_primary(rnp_key_handle_t key, size_t idx, bool primary)
{
    rnp_uid_handle_t uid = NULL;
    if (rnp_key_get_uid_handle_at(key, idx, &uid)) {
        return false;
    }
    bool prim = !primary;
    rnp_uid_is_primary(uid, &prim);
    rnp_uid_handle_destroy(uid);
    return prim == primary;
}

bool
check_key_valid(rnp_key_handle_t key, bool validity)
{
    bool valid = !validity;
    if (rnp_key_is_valid(key, &valid)) {
        return false;
    }
    return valid == validity;
}

uint32_t
get_key_expiry(rnp_key_handle_t key)
{
    uint32_t expiry = (uint32_t) -1;
    rnp_key_get_expiration(key, &expiry);
    return expiry;
}

size_t
get_key_uids(rnp_key_handle_t key)
{
    size_t count = (size_t) -1;
    rnp_key_get_uid_count(key, &count);
    return count;
}

bool
check_sub_valid(rnp_key_handle_t key, size_t idx, bool validity)
{
    rnp_key_handle_t sub = NULL;
    if (rnp_key_get_subkey_at(key, idx, &sub)) {
        return false;
    }
    bool valid = !validity;
    rnp_key_is_valid(sub, &valid);
    rnp_key_handle_destroy(sub);
    return valid == validity;
}

rnp_key_handle_t
bogus_key_handle(rnp_ffi_t ffi)
{
    rnp_key_handle_t handle = (rnp_key_handle_t) calloc(1, sizeof(*handle));
    handle->ffi = ffi;
    handle->pub = NULL;
    handle->sec = NULL;
    handle->locator.type = PGP_KEY_SEARCH_KEYID;
    return handle;
}

bool
sm2_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_PK_ALG, "SM2", &enabled) && enabled;
}

bool
aead_eax_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "EAX", &enabled) && enabled;
}

bool
aead_ocb_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "OCB", &enabled) && enabled;
}

bool
aead_ocb_aes_only()
{
    return aead_ocb_enabled() && !strcmp(rnp_backend_string(), "OpenSSL");
}

bool
twofish_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "Twofish", &enabled) && enabled;
}

bool
idea_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", &enabled) && enabled;
}

bool
brainpool_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP256r1", &enabled) && enabled;
}

bool
blowfish_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "BLOWFISH", &enabled) && enabled;
}

bool
cast5_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAST5", &enabled) && enabled;
}

bool
ripemd160_enabled()
{
    bool enabled = false;
    return !rnp_supports_feature(RNP_FEATURE_HASH_ALG, "RIPEMD160", &enabled) && enabled;
}

bool
test_load_gpg_check_key(rnp_key_store_t *pub, rnp_key_store_t *sec, const char *id)
{
    pgp_key_t *key = rnp_tests_get_key_by_id(pub, id);
    if (!key) {
        return false;
    }
    if (!(key = rnp_tests_get_key_by_id(sec, id))) {
        return false;
    }
    pgp_password_provider_t pswd_prov(string_copy_password_callback, (void *) "password");
    return key->is_protected() && key->unlock(pswd_prov) && key->lock();
}
