/*
 * Copyright (c) 2017-2020 [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 <algorithm>
#include <set>
#include "../librekey/key_store_pgp.h"
#include "pgp-key.h"

#include "rnp_tests.h"
#include "support.h"

/* This test adds some fake keys to a key store and tests some of
 * the search functions.
 */
TEST_F(rnp_tests, test_key_store_search)
{
    // create our store
    rnp_key_store_t *store = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);
    store->disable_validation = true;

    // some fake key data
    static const struct {
        const char *keyid;
        size_t      count;      // number of keys like this to add to the store
        const char *userids[5]; // NULL terminator required on array and strings
    } testdata[] = {{"000000000000AAAA", 1, {"user1-1", NULL}},
                    {"000000000000BBBB", 2, {"user2", "user1-2", NULL}},
                    {"000000000000CCCC", 1, {"user3", NULL}},
                    {"FFFFFFFFFFFFFFFF", 0, {NULL}}};
    // add our fake test keys
    for (size_t i = 0; i < ARRAY_SIZE(testdata); i++) {
        for (size_t n = 0; n < testdata[i].count; n++) {
            pgp_key_t key;

            key.pkt().tag = PGP_PKT_PUBLIC_KEY;
            key.pkt().version = PGP_V4;
            key.pkt().alg = PGP_PKA_RSA;

            // set the keyid
            assert_true(rnp::hex_decode(
              testdata[i].keyid, (uint8_t *) key.keyid().data(), key.keyid().size()));
            // keys should have different grips otherwise rnp_key_store_add_key will fail here
            pgp_key_grip_t &grip = (pgp_key_grip_t &) key.grip();
            assert_true(rnp::hex_decode(testdata[i].keyid, grip.data(), grip.size()));
            grip[0] = (uint8_t) n;
            // and fingerprint
            pgp_fingerprint_t &fp = (pgp_fingerprint_t &) key.fp();
            assert_true(
              rnp::hex_decode(testdata[i].keyid, fp.fingerprint, PGP_FINGERPRINT_SIZE));
            fp.fingerprint[0] = (uint8_t) n;
            fp.length = PGP_FINGERPRINT_SIZE;
            // set the userids
            for (size_t uidn = 0; testdata[i].userids[uidn]; uidn++) {
                pgp_transferable_userid_t tuid;
                tuid.uid.tag = PGP_PKT_USER_ID;
                tuid.uid.uid_len = strlen(testdata[i].userids[uidn]);
                tuid.uid.uid = (uint8_t *) malloc(tuid.uid.uid_len);
                assert_non_null(tuid.uid.uid);
                memcpy(tuid.uid.uid, testdata[i].userids[uidn], tuid.uid.uid_len);
                key.add_uid(tuid);
            }
            // add to the store
            assert_true(rnp_key_store_add_key(store, &key));
        }
    }

    // keyid search
    for (size_t i = 0; i < ARRAY_SIZE(testdata); i++) {
        std::string           keyid = testdata[i].keyid;
        std::set<pgp_key_t *> seen_keys;
        for (pgp_key_t *key = rnp_tests_get_key_by_id(store, keyid); key;
             key = rnp_tests_get_key_by_id(store, keyid, key)) {
            // check that the keyid actually matches
            assert_true(cmp_keyid(key->keyid(), keyid));
            // check that we have not already encountered this key pointer
            assert_int_equal(seen_keys.count(key), 0);
            // keep track of what key pointers we have seen
            seen_keys.insert(key);
        }
        assert_int_equal(seen_keys.size(), testdata[i].count);
    }
    // keyid search (by_name)
    for (size_t i = 0; i < ARRAY_SIZE(testdata); i++) {
        std::set<pgp_key_t *> seen_keys;
        pgp_key_t *           key = NULL;
        key = rnp_tests_get_key_by_id(store, testdata[i].keyid);
        while (key) {
            // check that the keyid actually matches
            assert_true(cmp_keyid(key->keyid(), testdata[i].keyid));
            // check that we have not already encountered this key pointer
            assert_int_equal(seen_keys.count(key), 0);
            // keep track of what key pointers we have seen
            seen_keys.insert(key);
            // this only returns false on error, regardless of whether it found a match
            key = rnp_tests_get_key_by_id(store, testdata[i].keyid, key);
        }
        // check the count
        assert_int_equal(seen_keys.size(), testdata[i].count);
    }

    // userid search (literal)
    for (auto &key : store->keys) {
        for (size_t i = 0; i < key.uid_count(); i++) {
            key.get_uid(i).valid = true;
        }
    }
    for (size_t i = 0; i < ARRAY_SIZE(testdata); i++) {
        for (size_t uidn = 0; testdata[i].userids[uidn]; uidn++) {
            std::set<pgp_key_t *> seen_keys;
            const std::string     userid = testdata[i].userids[uidn];
            pgp_key_t *           key = rnp_tests_key_search(store, userid);
            while (key) {
                // check that the userid actually matches
                bool found = false;
                for (unsigned j = 0; j < key->uid_count(); j++) {
                    if (key->get_uid(j).str == userid) {
                        found = true;
                    }
                }
                assert_true(found);
                // check that we have not already encountered this key pointer
                assert_int_equal(seen_keys.count(key), 0);
                // keep track of what key pointers we have seen
                seen_keys.insert(key);
                key = rnp_tests_get_key_by_id(store, testdata[i].keyid, key);
            }
            // check the count
            assert_int_equal(seen_keys.size(), testdata[i].count);
        }
    }

    // cleanup
    delete store;
}

TEST_F(rnp_tests, test_key_store_search_by_name)
{
    const pgp_key_t *key;
    pgp_key_t *      primsec;
    pgp_key_t *      subsec;
    pgp_key_t *      primpub;
    pgp_key_t *      subpub;

    // load pubring
    rnp_key_store_t *pub_store =
      new rnp_key_store_t(PGP_KEY_STORE_KBX, "data/keyrings/3/pubring.kbx", global_ctx);
    assert_true(rnp_key_store_load_from_path(pub_store, NULL));
    // load secring
    rnp_key_store_t *sec_store =
      new rnp_key_store_t(PGP_KEY_STORE_G10, "data/keyrings/3/private-keys-v1.d", global_ctx);
    pgp_key_provider_t key_provider(rnp_key_provider_store, pub_store);
    assert_true(rnp_key_store_load_from_path(sec_store, &key_provider));

    /* Main key fingerprint and id:
       4F2E62B74E6A4CD333BC19004BE147BB22DF1E60, 4BE147BB22DF1E60
       Subkey fingerprint and id:
       10793E367EE867C32E358F2AA49BAE05C16E8BC8, A49BAE05C16E8BC8
    */

    /* Find keys and subkeys by fingerprint, id and userid */
    primsec = rnp_tests_get_key_by_fpr(sec_store, "4F2E62B74E6A4CD333BC19004BE147BB22DF1E60");
    assert_non_null(primsec);
    key = rnp_tests_get_key_by_id(sec_store, "4BE147BB22DF1E60");
    assert_true(key == primsec);
    subsec = rnp_tests_get_key_by_fpr(sec_store, "10793E367EE867C32E358F2AA49BAE05C16E8BC8");
    assert_non_null(subsec);
    assert_true(primsec != subsec);
    key = rnp_tests_get_key_by_id(sec_store, "A49BAE05C16E8BC8");
    assert_true(key == subsec);

    primpub = rnp_tests_get_key_by_fpr(pub_store, "4F2E62B74E6A4CD333BC19004BE147BB22DF1E60");
    assert_non_null(primpub);
    assert_true(primsec != primpub);
    subpub = rnp_tests_get_key_by_fpr(pub_store, "10793E367EE867C32E358F2AA49BAE05C16E8BC8");
    assert_true(primpub != subpub);
    assert_true(subpub != subsec);
    key = rnp_tests_key_search(pub_store, "test1");
    assert_true(key == primpub);

    /* Try other searches */
    key = rnp_tests_get_key_by_fpr(sec_store, "4f2e62b74e6a4cd333bc19004be147bb22df1e60");
    assert_true(key == primsec);
    key = rnp_tests_get_key_by_fpr(sec_store, "0x4f2e62b74e6a4cd333bc19004be147bb22df1e60");
    assert_true(key == primsec);
    key = rnp_tests_get_key_by_id(pub_store, "4BE147BB22DF1E60");
    assert_true(key == primpub);
    key = rnp_tests_get_key_by_id(pub_store, "4be147bb22df1e60");
    assert_true(key == primpub);
    key = rnp_tests_get_key_by_id(pub_store, "0x4be147bb22df1e60");
    assert_true(key == primpub);
    key = rnp_tests_get_key_by_id(pub_store, "22df1e60");
    assert_null(key);
    key = rnp_tests_get_key_by_id(pub_store, "0x22df1e60");
    assert_null(key);
    key = rnp_tests_get_key_by_id(pub_store, "4be1 47bb 22df 1e60");
    assert_true(key == primpub);
    key = rnp_tests_get_key_by_id(pub_store, "4be147bb 22df1e60");
    assert_true(key == primpub);
    key = rnp_tests_get_key_by_id(pub_store, "    4be147bb\t22df1e60   ");
    assert_true(key == primpub);
    key = rnp_tests_get_key_by_id(pub_store, "test1");
    assert_null(key);
    /* Try negative searches */
    assert_null(rnp_tests_get_key_by_fpr(sec_store, "4f2e62b74e6a4cd333bc19004be147bb22df1e"));
    assert_null(rnp_tests_get_key_by_fpr(sec_store, "2e62b74e6a4cd333bc19004be147bb22df1e60"));
    assert_null(rnp_tests_get_key_by_id(sec_store, "4be147bb22dfle60"));
    assert_null(rnp_tests_get_key_by_id(sec_store, ""));
    assert_null(rnp_tests_get_key_by_id(sec_store, "test11"));
    assert_null(rnp_tests_get_key_by_id(sec_store, "atest1"));

    // cleanup
    delete pub_store;
    delete sec_store;
}
