/* Copyright (C) 2021       Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include <igloo/tap.h>
#include <igloo/igloo.h>
#include <igloo/error.h>
#include <igloo/digest.h>
#include <igloo/cs.h>

#define X1024 \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

static igloo_ro_t g_instance;

static const struct {
    const char *algo;
    const char *in;
    const char *out;
} test_vectors_digest[] = {
    {"SHA3-224", "",
        "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7"},
    {"SHA3-224", "a",
        "9e86ff69557ca95f405f081269685b38e3a819b309ee942f482b6a8b"},
    {"SHA3-224", X1024,
        "760d9f6ed07138642df19655c3a123c2c7bf1f905e7e043d0f7b9bc5"},
    {"SHA3-224", "Hello World!",
        "716596afadfa17cd1cb35133829a02b03e4eed398ce029ce78a2161d"},
    {"SHA3-256", "",
        "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"},
    {"SHA3-256", "a",
        "80084bf2fba02475726feb2cab2d8215eab14bc6bdd8bfb2c8151257032ecd8b"},
    {"SHA3-256", X1024,
        "8700cadddb300593ea5dd80e5539ca2fab38c727dee9dc5abbe2c45e6b2cfc29"},
    {"SHA3-256", "Hello World!",
        "d0e47486bbf4c16acac26f8b653592973c1362909f90262877089f9c8a4536af"},
    {"SHA3-384", "",
        "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004"},
    {"SHA3-384", "a",
        "1815f774f320491b48569efec794d249eeb59aae46d22bf77dafe25c5edc28d7ea44f93ee1234aa88f61c91912a4ccd9"},
    {"SHA3-384", X1024,
        "490986508f3386004a389630cfedd6baeaf87cb12f948dc226933802244af6ef7bc7beb657b158d75ffbd581dd59b7d1"},
    {"SHA3-384", "Hello World!",
        "f324cbd421326a2abaedf6f395d1a51e189d4a71c755f531289e519f079b224664961e385afcc37da348bd859f34fd1c"},
    {"SHA3-512", "",
        "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26"},
    {"SHA3-512", "a",
        "697f2d856172cb8309d6b8b97dac4de344b549d4dee61edfb4962d8698b7fa803f4f93ff24393586e28b5b957ac3d1d369420ce53332712f997bd336d09ab02a"},
    {"SHA3-512", X1024,
        "62f68241641a6d705216a3b7615e2c6d990646473f380d2800fdc145f60d6fefec4fbc7c8bb02dd0b1568b16f57138966afd91346ac9d7deab1a27202f898e83"},
    {"SHA3-512", "Hello World!",
        "32400b5e89822de254e8d5d94252c52bdcb27a3562ca593e980364d9848b8041b98eabe16c1a6797484941d2376864a1b0e248b0f7af8b1555a778c336a5bf48"}
};

static const char * algos_good[] = {
    "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512"
};

static const char * algos_bad[] = {
    "INVALID-ALGO"
};

static const struct {
    const char *algo;
    const char *key;
    const char *in;
    const char *out;
} test_vectors_hmac[] = {
    {"SHA3-224", "", "",
        "1b9044e0d5bb4ef944bc00f1b26c483ac3e222f4640935d089a49083"},
    {"SHA3-224", "a", "b",
        "ea60cb1f8e895ff5e38a38b1e4708a86ce4b37d48e420f7e933f829d"},
    {"SHA3-224", "a", X1024,
        "d8df22730a591fce0cd74ca56e4f208e3ba6b63806a99cc055683ed9"},
    {"SHA3-224", X1024, "b",
        "b2e17a221834980aeec621b90767364998602fb7054ea03f57ed1843"},
    {"SHA3-256", "", "",
        "e841c164e5b4f10c9f3985587962af72fd607a951196fc92fb3a5251941784ea"},
    {"SHA3-256", "a", "b",
        "13835d8432b56b8e45f24e849e092949392d419100b91e52292ef85a18aae462"},
    {"SHA3-256", "a", X1024,
        "003645e614770497d02a686c0fa01eae26300692d845f8492c581c45c90ba9f1"},
    {"SHA3-256", X1024, "b",
        "b1a0b0f1d678d2c1f1480210625f5280c9d472884d21c12056231bf6c3b4c91d"},
    {"SHA3-384", "", "",
        "adca89f07bbfbeaf58880c1572379ea2416568fd3b66542bd42599c57c4567e6ae086299ea216c6f3e7aef90b6191d24"},
    {"SHA3-384", "a", "b",
        "6230c63191fd61bb9fd710085b9460f067f4db6ac4f34ff17baa5814b56d08ebe8b2b025f0f93f0e2694f2c9930c2670"},
    {"SHA3-384", "a", X1024,
        "5c36d5e887da48c5b290e52db56b00e1e8a3e5724180b4e907aeaa3a1ab6f67d283b54a3358e697eecdda9f1ce8bed2f"},
    {"SHA3-384", X1024, "b",
        "18be7dfe84db0e404edfc955e30a7974595d7ffa4863e903974eebaf408e1d7a13ecac1c219c2bf48d350d527ef6b8f6"},
    {"SHA3-512", "", "",
        "cbcf45540782d4bc7387fbbf7d30b3681d6d66cc435cafd82546b0fce96b367ea79662918436fba442e81a01d0f9592dfcd30f7a7a8f1475693d30be4150ca84"},
    {"SHA3-512", "a", "b",
        "df10884c315d916232e8d842a66a44db76503d510464526ef2e728f3a20649bfd2748758d793a44313aead56c4b5846c295a118e07ac42741fde5cb84c2712bb"},
    {"SHA3-512", "a", X1024,
        "4291f312de13853f9b640b328984161881c38fcaacfa01c7e2220f0592dee17d495d4d58ddef660b29b5d0db81ac870a3c783e62e9724595350e5bfd23ee1a47"},
    {"SHA3-512", X1024, "b",
        "a664de9f66cc5b99c91452edf1fae1e87573ce68bbd0dc449eb4f819a154ad599ba8d39d39df7c8ce1d4e7ad3e3401bcd50bd434428509b39ff5c91af7d0df7f"}
};

static igloo_error_t digest_test_inner(const char *algo, const char *in, const char *out)
{
    igloo_error_t ret = igloo_ERROR_GENERIC;
    size_t inlen = strlen(in);
    igloo_digest_t *digest = NULL;

    igloo_tap_test_success("create new digest object", igloo_digest_new(&digest, g_instance, algo));
    if (digest) {
        char buf[1024];
        ssize_t res;
        bool resok;

        igloo_tap_test("write to digest object", igloo_digest_write(digest, in, inlen) == (ssize_t)inlen);

        res = igloo_digest_read(digest, buf, sizeof(buf));
        resok = res >= 1 && res <= (ssize_t)sizeof(buf);
        igloo_tap_test("read from digest object", resok);
        if (resok) {
            char *tmp;
            igloo_error_t error = igloo_cs_to_hex(buf, &tmp, res);
            igloo_tap_test_success("cs_to_hex", error);
            if (error == igloo_ERROR_NONE) {
                if (strcmp(tmp, out) == 0)
                    ret = igloo_ERROR_NONE;
                igloo_tap_test_success("digest match", ret);
                free(tmp);
            }
        }

        igloo_tap_test_success("unref digest object", igloo_ro_unref(&digest));
    }

    return ret;
}

static void digest_test(const char *algo, const char *in, const char *out)
{
    char buf[1024];

    snprintf(buf, sizeof(buf), "digest (%s) of \"%s\"", algo, in);

    igloo_tap_test_success(buf, digest_test_inner(algo, in, out));
}

static void test_digest_by_vectors(void)
{
    size_t i;

    for (i = 0; i < (sizeof(test_vectors_digest)/sizeof(*test_vectors_digest)); i++) {
        digest_test(test_vectors_digest[i].algo, test_vectors_digest[i].in, test_vectors_digest[i].out);
    }
}

static void test_digest_algos(void) {
    size_t i;

    for (i = 0; i < (sizeof(algos_good)/sizeof(*algos_good)); i++) {
        igloo_digest_t *digest = NULL;
        char buf[1024];

        snprintf(buf, sizeof(buf), "digest creation with good algo %s", algos_good[i]);

        igloo_tap_test_success(buf, igloo_digest_new(&digest, g_instance, algos_good[i]));
        igloo_tap_test("digest object not null", !igloo_ro_is_null(digest));
        igloo_tap_test_success("digest object unref", igloo_ro_unref(&digest));
    }

    for (i = 0; i < (sizeof(algos_bad)/sizeof(*algos_bad)); i++) {
        igloo_digest_t *digest = NULL;
        char buf[1024];

        snprintf(buf, sizeof(buf), "digest creation with bad algo %s", algos_bad[i]);

        igloo_tap_test_error(buf, igloo_ERROR_NOSYS, igloo_digest_new(&digest, g_instance, algos_bad[i]));
        igloo_tap_test("digest object is null", igloo_ro_is_null(digest));
    }
}

static void test_digest_api_errors(void)
{
    igloo_digest_t *digest;
    char a;

    igloo_tap_test_error("igloo_digest_new self=NULL", igloo_ERROR_FAULT, igloo_digest_new(NULL, g_instance, "SHA3-512"));
    igloo_tap_test_error("igloo_digest_new instance=NULL", igloo_ERROR_FAULT, igloo_digest_new(&digest, igloo_RO_NULL, "SHA3-512"));
    igloo_tap_test_error("igloo_digest_new algo=NULL", igloo_ERROR_FAULT, igloo_digest_new(&digest, g_instance, NULL));

    igloo_tap_test_error("igloo_digest_copy old=NULL", igloo_ERROR_FAULT, igloo_digest_copy(&digest, g_instance, NULL));
    igloo_tap_test_error("igloo_digest_copy new=NULL", igloo_ERROR_FAULT, igloo_digest_copy(NULL, g_instance, NULL));

    igloo_tap_test("igloo_digest_write digest=NULL", igloo_digest_write(NULL, "", 0) == -1);
    igloo_tap_test("igloo_digest_write data=NULL", igloo_digest_write(NULL, NULL, 0) == -1);

    igloo_tap_test("igloo_digest_read digest=NULL", igloo_digest_read(NULL, &a, 1) == -1);
    igloo_tap_test("igloo_digest_read buf=NULL", igloo_digest_read(NULL, NULL, 0) == -1);
}

static igloo_error_t hmac_test_inner(const char *algo, const char *key, const char *in, const char *out)
{
    igloo_error_t ret = igloo_ERROR_GENERIC;
    size_t inlen = strlen(in);
    igloo_hmac_t *hmac = NULL;

    igloo_tap_test_success("create new hmac object", igloo_hmac_new(&hmac, g_instance, algo, key, strlen(key)));
    if (hmac) {
        char buf[1024];
        ssize_t res;
        bool resok;

        igloo_tap_test("write to hmac object", igloo_hmac_write(hmac, in, inlen) == (ssize_t)inlen);

        res = igloo_hmac_read(hmac, buf, sizeof(buf));
        printf("# res=%zi\n", res);
        resok = res >= 1 && res <= (ssize_t)sizeof(buf);
        igloo_tap_test("read from hmac object", resok);
        if (resok) {
            char *tmp;
            igloo_error_t error = igloo_cs_to_hex(buf, &tmp, res);
            igloo_tap_test_success("cs_to_hex", error);
            if (error == igloo_ERROR_NONE) {
                if (strcmp(tmp, out) == 0)
                    ret = igloo_ERROR_NONE;
                igloo_tap_test_success("hmac match", ret);
                free(tmp);
            }
        }

        igloo_tap_test_success("unref hmac object", igloo_ro_unref(&hmac));
    }

    return ret;
}

static void hmac_test(const char *algo, const char *key, const char *in, const char *out)
{
    char buf[1024];

    snprintf(buf, sizeof(buf), "HMAC (%s) with key \"%s\" of \"%s\"", algo, key, in);

    igloo_tap_test_success(buf, hmac_test_inner(algo, key, in, out));
}

static void test_hmac_by_vectors(void)
{
    size_t i;

    for (i = 0; i < (sizeof(test_vectors_hmac)/sizeof(*test_vectors_hmac)); i++) {
        hmac_test(test_vectors_hmac[i].algo, test_vectors_hmac[i].key, test_vectors_hmac[i].in, test_vectors_hmac[i].out);
    }
}

static void test_hmac_api_errors(void)
{
    igloo_hmac_t *hmac;
    char a;

    igloo_tap_test_error("igloo_hmac_new self=NULL", igloo_ERROR_FAULT, igloo_hmac_new(NULL, g_instance, "SHA3-512", "", 0));
    igloo_tap_test_error("igloo_hmac_new instance=NULL", igloo_ERROR_FAULT, igloo_hmac_new(&hmac, NULL, "SHA3-512", "", 0));
    igloo_tap_test_error("igloo_hmac_new algo=NULL", igloo_ERROR_FAULT, igloo_hmac_new(&hmac, g_instance, NULL, "", 0));
    igloo_tap_test_error("igloo_hmac_new key=NULL", igloo_ERROR_FAULT, igloo_hmac_new(&hmac, g_instance, "SHA3-512", NULL, 0));

    igloo_tap_test_error("igloo_hmac_copy old=NULL", igloo_ERROR_FAULT, igloo_hmac_copy(&hmac, g_instance, NULL));
    igloo_tap_test_error("igloo_hmac_copy new=NULL", igloo_ERROR_FAULT, igloo_hmac_copy(NULL, g_instance, NULL));

    igloo_tap_test("igloo_hmac_write hmac=NULL", igloo_hmac_write(NULL, "", 0) == -1);
    igloo_tap_test("igloo_hmac_write data=NULL", igloo_hmac_write(NULL, NULL, 0) == -1);

    igloo_tap_test("igloo_hmac_read hmac=NULL", igloo_hmac_read(NULL, &a, 1) == -1);
    igloo_tap_test("igloo_hmac_read buf=NULL", igloo_hmac_read(NULL, NULL, 0) == -1);
}

int main (void) {
    igloo_tap_init();
    igloo_tap_exit_on(igloo_TAP_EXIT_ON_FIN|igloo_TAP_EXIT_ON_BAIL_OUT, NULL);

    igloo_tap_test_success("igloo_initialize", igloo_initialize(&g_instance));
    if (igloo_ro_is_null(g_instance)) {
        igloo_tap_bail_out("Can not get an instance");
        return EXIT_FAILURE; // return failure as we should never reach this point!
    }

    igloo_tap_group_run("digest by vectors", test_digest_by_vectors);
    igloo_tap_group_run("digest algos", test_digest_algos);
    igloo_tap_group_run("digest API errors", test_digest_api_errors);

    igloo_tap_group_run("HMAC by vectors", test_hmac_by_vectors);
    igloo_tap_group_run("HMAC API errors", test_hmac_api_errors);

    igloo_tap_test_success("unref instance", igloo_ro_unref(&g_instance));
    igloo_tap_fin();

    return EXIT_FAILURE; // return failure as we should never reach this point!
}
