/* Copyright (c) (2018,2019) Apple Inc. All rights reserved.
 *
 * corecrypto is licensed under Apple Inc.’s Internal Use License Agreement (which
 * is contained in the License.txt file distributed with corecrypto) and only to 
 * people who accept that license. IMPORTANT:  Any license rights granted to you by 
 * Apple Inc. (if any) are limited to internal use within your organization only on 
 * devices and computers you own or control, for the sole purpose of verifying the 
 * security characteristics and correct functioning of the Apple Software.  You may 
 * not, directly or indirectly, redistribute the Apple Software or any portions thereof.
 */
#include "testmore.h"
#include "testbyteBuffer.h"
#include "testccnBuffer.h"

#if (CCSCRYPT == 0)
entryPoint(ccscrypt_tests, "ccscrypt test")
#else
#include <corecrypto/ccscrypt.h>
#include "ccscrypt_internal.h"
#include "cc_priv.h"

static void
test_ccscrypt_salsa20_8(void)
{
    char *input_hex = "7e879a214f3ec9867ca940e641718f26baee555b8c61c1b50df846116dcd3b1dee24f319df9b3d8514121e4b5ac5aa3276021d2909c74829edebc68db8b8c25e";
    char *output_hex = "a41f859c6608cc993b81cacb020cef05044b2181a2fd337dfd7b1c6396682f29b4393168e3c9e6bcfe6bc5b7a06d96bae424cc102c91745c24ad673dc7618f81";
    byteBuffer input_bytes = hexStringToBytes((char *) input_hex);
    byteBuffer output_bytes = hexStringToBytes((char *) output_hex);

    uint8_t output[64];
    ccscrypt_salsa20_8(input_bytes->bytes, output);

    is(cc_cmp_safe(sizeof(output), output, output_bytes->bytes), 0, "ccscrypt_salsa20_8 test vector from RFC7914 $8 failed");

    free(input_bytes);
    free(output_bytes);
}

static void
test_ccscrypt_blockmix_salsa8(void)
{
    char *input_hex = "f7ce0b653d2d72a4108cf5abe912ffdd777616dbbb27a70e8204f3ae2d0f6fad89f68f4811d1e87bcc3bd7400a9ffd29094f0184639574f39ae5a1315217bcd7894991447213bb226c25b54da86370fbcd984380374666bb8ffcb5bf40c254b067d27c51ce4ad5fed829c90b505a571b7f4d1cad6a523cda770e67bceaaf7e89";
    char *output_hex = "a41f859c6608cc993b81cacb020cef05044b2181a2fd337dfd7b1c6396682f29b4393168e3c9e6bcfe6bc5b7a06d96bae424cc102c91745c24ad673dc7618f8120edc975323881a80540f64c162dcd3c21077cfe5f8d5fe2b1a4168f953678b77d3b3d803b60e4ab920996e59b4d53b65d2a225877d5edf5842cb9f14eefe425";

    byteBuffer input_bytes = hexStringToBytes((char *) input_hex);
    byteBuffer output_bytes = hexStringToBytes((char *) output_hex);

    uint8_t Y[128];
    ccscrypt_blockmix_salsa8(input_bytes->bytes, Y, 1);

    is(cc_cmp_safe(output_bytes->len, output_bytes->bytes, input_bytes->bytes), 0, "ccscrypt_blockmix_salsa8 test vector from RFC7914 $9 failed");

    free(input_bytes);
    free(output_bytes);
}

static void
test_ccscrypt_romix(void)
{
    char *input_hex = "f7ce0b653d2d72a4108cf5abe912ffdd777616dbbb27a70e8204f3ae2d0f6fad89f68f4811d1e87bcc3bd7400a9ffd29094f0184639574f39ae5a1315217bcd7894991447213bb226c25b54da86370fbcd984380374666bb8ffcb5bf40c254b067d27c51ce4ad5fed829c90b505a571b7f4d1cad6a523cda770e67bceaaf7e89";
    char *output_hex = "79ccc193629debca047f0b70604bf6b62ce3dd4a9626e355fafc6198e6ea2b46d58413673b99b029d665c357601fb426a0b2f4bba200ee9f0a43d19b571a9c71ef1142e65d5a266fddca832ce59faa7cac0b9cf1be2bffca300d01ee387619c4ae12fd4438f203a0e4e1c47ec314861f4e9087cb33396a6873e8f9d2539a4b8e";

    byteBuffer input_bytes = hexStringToBytes((char *) input_hex);
    byteBuffer output_bytes = hexStringToBytes((char *) output_hex);

    size_t r = 1;
    size_t N = 16;
    uint8_t V[128 * r * N];
    uint8_t X[128 * r];
    uint8_t Y[128 * r];
    ccscrypt_romix(r, input_bytes->bytes, N, V, X, Y);

    is(cc_cmp_safe(output_bytes->len, output_bytes->bytes, input_bytes->bytes), 0, "ccscrypt_romix test vector from RFC7914 $10 failed");

    free(input_bytes);
    free(output_bytes);
}

typedef struct {
    uint8_t *password;
    uint8_t *salt;
    uint64_t N;
    uint32_t r;
    uint32_t p;
    size_t dk_len;
    char *dk;
} test_ccscrypt_vector;

static const test_ccscrypt_vector test_ccscrypt_vectors[] = {
    {
        .password = NULL,
        .salt = NULL,
        .N = 16,
        .r = 1,
        .p = 1,
        .dk_len = 64,
        .dk = "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906",
    },
    {
        .password = (uint8_t *)"password",
        .salt = (uint8_t *)"NaCl",
        .N = 1024,
        .r = 8,
        .p = 16,
        .dk_len = 64,
        .dk = "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640",
    },
    {
        .password = (uint8_t *)"pleaseletmein",
        .salt = (uint8_t *)"SodiumChloride",
        .N = 16384,
        .r = 8,
        .p = 1,
        .dk_len = 64,
        .dk = "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887",
    },
#if !(defined(TARGET_OS_WATCH) || defined(TARGET_OS_BRIDGE))
    {
        .password = (uint8_t *)"pleaseletmein",
        .salt = (uint8_t *)"SodiumChloride",
        .N = 1048576,
        .r = 8,
        .p = 1,
        .dk_len = 64,
        .dk = "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4",
    },
#endif // !(defined(TARGET_OS_WATCH) || defined(TARGET_OS_BRIDGE))
};

static const size_t test_ccscrypt_vectors_len = CC_ARRAY_LEN(test_ccscrypt_vectors);

static void
test_ccscrypt(void)
{
    for (size_t i = 0; i < test_ccscrypt_vectors_len; i++) {
        test_ccscrypt_vector test = test_ccscrypt_vectors[i];

        byteBuffer expected = hexStringToBytes((char *)test.dk);
        uint8_t actual[test.dk_len];

        uint8_t *password = (uint8_t *)test.password;
        size_t password_len = password == NULL ? 0 : strlen((char *)test.password);
        uint8_t *salt = (uint8_t *)test.salt;
        size_t salt_len = salt == NULL ? 0 : strlen((char *)test.salt);

        int64_t buffer_size = ccscrypt_storage_size(test.N, test.r, test.p);
        uint8_t *buffer = (uint8_t *)malloc((size_t)buffer_size);
        memset(buffer, 0, (size_t)buffer_size);

        ccscrypt(password_len, test.password, salt_len, test.salt, buffer, test.N, test.r, test.p, test.dk_len, actual);
        free(buffer);

        is(cc_cmp_safe(test.dk_len, actual, expected->bytes), 0, "test_ccscrypt test %zu failed", i);

        free(expected);
    }
}

static void
test_ccscrypt_valid_parameters(void)
{
    uint64_t N = 0;
    uint32_t r = 0;
    uint32_t p = 0;

    is(ccscrypt_valid_parameters(N, r, p), CCERR_PARAMETER, "ccscrypt_valid_parameters(%llu, %u, %u) failed", N, r, p);

    N = 3; // not power of 2
    is(ccscrypt_valid_parameters(N, r, p), CCERR_PARAMETER, "ccscrypt_valid_parameters(%llu, %u, %u) failed", N, r, p);

    N = 2;
    r = 1;
    p = (UINT32_MAX - 1) * 32 / (128 * r); // p = ((2^32-1) * 32) / (128 * r)
    is(ccscrypt_valid_parameters(N, r, p), 0, "ccscrypt_valid_parameters(%llu, %u, %u) failed", N, r, p);

    p = p + 1; // p > ((2^32-1) * 32) / (128 * r)
    is(ccscrypt_valid_parameters(N, r, p), CCERR_PARAMETER, "ccscrypt_valid_parameters(%llu, %u, %u) failed", N, r, p);
}

int ccscrypt_tests(TM_UNUSED int argc, TM_UNUSED char *const *argv)
{
    plan_tests(7 + test_ccscrypt_vectors_len);

    test_ccscrypt_salsa20_8();
    test_ccscrypt_blockmix_salsa8();
    test_ccscrypt_romix();
    test_ccscrypt();
    test_ccscrypt_valid_parameters();

    return 0;
}
#endif // (CCSCRYPT != 0)

