/*
 * Copyright 2019-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <mongocrypt-marking-private.h>

#include "mongocrypt-buffer-private.h"
#include "test-mongocrypt-assert.h"
#include "test-mongocrypt.h"

#include "mongocrypt-endian-private.h"

#define TEST_STRING "?????" /* 3F 3F 3F 3F 3F */
#define TEST_INT 5555555    /* 54 C5 63 */

static void _get_bytes(const void *in, char *out, int len) {
    const unsigned char *src = in;
    char *dest = out;

    for (int i = 0; i < len; i++, dest += 3) {
        ASSERT(3 == bson_snprintf(dest, 4, "%02X ", src[i]));
    }
    dest[-1] = '\0';
}

static bool assert_excess_bytes_removed(char *key, char *wrapped, char *unwrapped, uint32_t type, bson_value_t *out) {
    _mongocrypt_buffer_t plaintext = {0};
    _mongocrypt_marking_t marking = {0};
    bson_iter_t iter;
    bson_t wrapper = BSON_INITIALIZER;
    char actual[100] = {0};
    bool ret = false;
    bson_t bson = BSON_INITIALIZER;

    BSON_APPEND_UTF8(&bson, "str_key", TEST_STRING);
    BSON_APPEND_INT32(&bson, "int_key", TEST_INT);

    bson_iter_init_find(&iter, &bson, key);
    memcpy(&marking.u.fle1.v_iter, &iter, sizeof(bson_iter_t));

    bson_append_iter(&wrapper, "", 0, &marking.u.fle1.v_iter);
    _get_bytes(bson_get_data(&wrapper), actual, wrapper.len);
    BSON_ASSERT(0 == strcmp(wrapped, actual));

    _mongocrypt_buffer_from_iter(&plaintext, &(&marking)->u.fle1.v_iter);
    _get_bytes(plaintext.data, actual, plaintext.len);
    BSON_ASSERT(0 == strcmp(unwrapped, actual));

    _mongocrypt_marking_cleanup(&marking);
    bson_destroy(&wrapper);

    ret = _mongocrypt_buffer_to_bson_value(&plaintext, type, out);

    _mongocrypt_buffer_cleanup(&plaintext);
    bson_destroy(&bson);
    return ret;
}

static void _test_mongocrypt_buffer_from_iter(_mongocrypt_tester_t *tester) {
    /*
     * This section explains the purpose of each byte in a BSON document. This is
     * used to extract only the value of a BSON document for later storage. Below
     * is an example of the leftmost derivation of one of the BSON documents
     * used for this test.
     *
     * NOTES:
     * - When used as a unary operator, * means that the repetition can occur 0
     *   or more times.
     *
     * - int32     4 bytes (32-bit signed integer, two's complement)
     * - (byte*)   Zero or more modified UTF-8 encoded characters followed by
     *             '\x00'. The (byte*) MUST NOT contain '\x00', hence it is
     *             not full UTF-8.
     *
     * RULES:
     * 1. document ::=  int32 e_list "\x00"     int32 is the total number of
     *                                          bytes comprising the doc.
     * 2. e_list   ::=  element e_list
     *              |   ""
     * 3. element  ::=  "\x02" e_name string    UTF-8 string
     *              |   "\x10" e_name int32 	  32-bit integer
     * 4. e_name   ::=  cstring                 Key name
     * 5. string   ::=  int32 (byte*) "\x00"
     * 6. cstring  ::=  (byte*) "\x00"
     *
     * BELOW IS A LEFTMOST DERIVATION:
     * Let doc = { "" : "?????" }
     *
     * -  doc  ::= int32 e_list "\x00"
     *
     * -- rule2 -> int32 element e_list "\x00"
     * -- rule3 -> int32 "\x02" e_name string e_list "\x00"
     * -- rule4 -> int32 "\x02" cstring string e_list "\x00"
     * -- rule6 -> int32 "\x02" (byte*) "\x00" string e_list "\x00"
     * -- key   -> int32 "\x02" "" "\x00" string e_list "\x00"
     ** The key is an empty string, i.e. 0 bytes **
     * -- rule5 -> int32 "\x02" "" "\x00" int32 (byte*) "\x00" e_list "\x00"
     * -- value -> int32 "\x02" "" "\x00" int32=6 "?????" "\x00" e_list "\x00"
     ** Above, the value is set. The int32 before the value is the size of the **
     ** value in bytes, plus one for the the null char. **
     * -- rule2 -> int32=17 "\x02" "" "\x00" int32=6 "?????" "\x00" "" "\x00"
     *
     * Finally, we have the byte sequence:
     *    "11000000 02 "" 00 06000000 "?????" 00 00"
     *
     * Note, the hexcode for '?' is '3F'. Grouping the sequence by byte for
     * readability, more precisely we have:
     *    "11 00 00 00 02 00 06 00 00 00 3F 3F 3F 3F 3F 00 00"
     *
     * with the value, including its length and null terminator being:
     *    "06 00 00 00 3F 3F 3F 3F 3F 00".
     * This is what we will store.
     */

    bson_t wrapper = BSON_INITIALIZER;
    bson_value_t out;

    BSON_ASSERT(assert_excess_bytes_removed("str_key",
                                            "11 00 00 00 02 00 06 00 00 00 3F 3F 3F 3F 3F 00 00",
                                            /** no prefix **/ "06 00 00 00 3F 3F 3F 3F 3F 00",
                                            0x02, /* string type */
                                            &out));

    BSON_ASSERT(out.value_type == BSON_TYPE_UTF8);
    BSON_ASSERT(0 == strcmp(TEST_STRING, out.value.v_utf8.str));
    BSON_ASSERT(5 == out.value.v_utf8.len);
    bson_value_destroy(&out);

    BSON_ASSERT(assert_excess_bytes_removed("int_key",
                                            "0B 00 00 00 10 00 63 C5 54 00 00",
                                            /** no prefix **/ "63 C5 54 00",
                                            0x10, /* int type */
                                            &out));

    BSON_ASSERT(out.value_type == BSON_TYPE_INT32);
    BSON_ASSERT(TEST_INT == out.value.v_int32);

    BSON_ASSERT(!assert_excess_bytes_removed("int_key",
                                             "0B 00 00 00 10 00 63 C5 54 00 00",
                                             /** no prefix **/ "63 C5 54 00",
                                             0x99, /* invalid type */
                                             &out));
    bson_destroy(&wrapper);
}

static void _test_mongocrypt_buffer_copy_from_data_and_size(_mongocrypt_tester_t *tester) {
    _mongocrypt_buffer_t buf;
    const uint8_t data[] = {0, 1, 2};

    ASSERT(_mongocrypt_buffer_copy_from_data_and_size(&buf, data, 3));
    ASSERT_CMPBYTES(data, sizeof(data), buf.data, buf.len);
    _mongocrypt_buffer_cleanup(&buf);
}

static void _test_mongocrypt_buffer_steal_from_data_and_size(_mongocrypt_tester_t *tester) {
    _mongocrypt_buffer_t buf;
    uint8_t *data = bson_malloc0(3);

    data[0] = 0;
    data[1] = 1;
    data[2] = 2;
    ASSERT(_mongocrypt_buffer_steal_from_data_and_size(&buf, data, 3));
    ASSERT_CMPBYTES(data, 3, buf.data, buf.len);
    _mongocrypt_buffer_cleanup(&buf);
}

static void _test_mongocrypt_buffer_steal_from_string(_mongocrypt_tester_t *tester) {
    _mongocrypt_buffer_t buf;
    char *str = bson_strdup("foo");

    ASSERT(_mongocrypt_buffer_steal_from_string(&buf, str));
    ASSERT_STREQUAL((const char *)buf.data, str);
    _mongocrypt_buffer_cleanup(&buf);
}

static void _test_mongocrypt_buffer_copy_from_uint64_le(_mongocrypt_tester_t *tester) {
    _mongocrypt_buffer_t expect;
    _mongocrypt_buffer_t got;

    _mongocrypt_buffer_copy_from_hex(&expect, "0100000000000000");
    _mongocrypt_buffer_copy_from_uint64_le(&got, 0x0000000000000001ULL);
    ASSERT_CMPBYTES(expect.data, expect.len, got.data, got.len);
    _mongocrypt_buffer_cleanup(&expect);
    _mongocrypt_buffer_cleanup(&got);

    _mongocrypt_buffer_copy_from_hex(&expect, "1122334455667788");
    _mongocrypt_buffer_copy_from_uint64_le(&got, 0x8877665544332211ULL);
    ASSERT_CMPBYTES(expect.data, expect.len, got.data, got.len);
    _mongocrypt_buffer_cleanup(&expect);
    _mongocrypt_buffer_cleanup(&got);
}

static void _test_mongocrypt_buffer_from_subrange(_mongocrypt_tester_t *tester) {
    _mongocrypt_buffer_t input;
    _mongocrypt_buffer_t expect;
    _mongocrypt_buffer_t got;

    _mongocrypt_buffer_copy_from_hex(&input, "010203");
    _mongocrypt_buffer_copy_from_hex(&expect, "01");
    ASSERT(_mongocrypt_buffer_from_subrange(&got, &input, 0, 1));
    ASSERT_CMPBYTES(expect.data, expect.len, got.data, got.len);
    _mongocrypt_buffer_cleanup(&expect);
    _mongocrypt_buffer_cleanup(&input);

    _mongocrypt_buffer_copy_from_hex(&input, "010203");
    _mongocrypt_buffer_copy_from_hex(&expect, "010203");
    ASSERT(_mongocrypt_buffer_from_subrange(&got, &input, 0, 3));
    ASSERT_CMPBYTES(expect.data, expect.len, got.data, got.len);
    _mongocrypt_buffer_cleanup(&expect);
    _mongocrypt_buffer_cleanup(&input);

    _mongocrypt_buffer_copy_from_hex(&input, "010203");
    _mongocrypt_buffer_copy_from_hex(&expect, "0203");
    ASSERT(_mongocrypt_buffer_from_subrange(&got, &input, 1, 2));
    ASSERT_CMPBYTES(expect.data, expect.len, got.data, got.len);
    _mongocrypt_buffer_cleanup(&expect);
    _mongocrypt_buffer_cleanup(&input);

    _mongocrypt_buffer_copy_from_hex(&input, "010203");
    ASSERT(!_mongocrypt_buffer_from_subrange(&got, &input, 0, 4));
    _mongocrypt_buffer_cleanup(&input);
}

static void _test_mongocrypt_buffer_copy_from_string_as_bson_value(_mongocrypt_tester_t *tester) {
    _mongocrypt_buffer_t buf;
    _mongocrypt_buffer_t expectedLenBuf;
    const char *data = "foobar";

    // expect output to contain 4-byte length + data + null string terminator
    size_t expectedLen = sizeof(int32_t) + strlen(data) + sizeof(uint8_t);
    _mongocrypt_buffer_copy_from_hex(&expectedLenBuf, "07000000");

    _mongocrypt_buffer_copy_from_string_as_bson_value(&buf, data, (int)strlen(data));
    ASSERT(buf.len == expectedLen);

    // check 4-byte length
    ASSERT_CMPBYTES(expectedLenBuf.data, expectedLenBuf.len, buf.data, expectedLenBuf.len);
    // check data + null byte
    ASSERT_CMPBYTES((const uint8_t *)data,
                    strlen(data) + 1,
                    buf.data + expectedLenBuf.len,
                    buf.len - expectedLenBuf.len);
    _mongocrypt_buffer_cleanup(&buf);
    _mongocrypt_buffer_cleanup(&expectedLenBuf);
}

void _mongocrypt_tester_install_buffer(_mongocrypt_tester_t *tester) {
    INSTALL_TEST(_test_mongocrypt_buffer_from_iter);
    INSTALL_TEST(_test_mongocrypt_buffer_copy_from_data_and_size);
    INSTALL_TEST(_test_mongocrypt_buffer_steal_from_data_and_size);
    INSTALL_TEST(_test_mongocrypt_buffer_steal_from_string);
    INSTALL_TEST(_test_mongocrypt_buffer_copy_from_uint64_le);
    INSTALL_TEST(_test_mongocrypt_buffer_from_subrange);
    INSTALL_TEST(_test_mongocrypt_buffer_copy_from_string_as_bson_value);
}
