/*
 * 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.
 */

/* For each field, check a valid value, invalid value, missing value */

#include "bson/bson.h"
#include "test-mongocrypt.h"

/* Create a basis key document, but exclude some fields. */
static void
_recreate_excluding (_mongocrypt_tester_t *tester, bson_t *out, va_list args)
{
   bson_t tmp;

   BSON_ASSERT (_mongocrypt_binary_to_bson (
      TEST_FILE ("./test/data/key-document-full.json"), &tmp));

   /* copy to out */
   bson_destroy (out);
   bson_init (out);
   bson_copy_to_excluding_noinit_va (&tmp, out, "", args);
}

static void
_recreate_and_reset (_mongocrypt_tester_t *tester,
                     bson_t *key_bson,
                     mongocrypt_status_t *status,
                     ...)
{
   va_list args;

   va_start (args, status);
   _recreate_excluding (tester, key_bson, args);
   va_end (args);
   _mongocrypt_status_reset (status);
}


static void
_parse_ok (bson_t *key_bson, mongocrypt_status_t *status)
{
   _mongocrypt_key_doc_t *key_doc = _mongocrypt_key_new ();
   ASSERT_OK_STATUS (_mongocrypt_key_parse_owned (key_bson, key_doc, status),
                     status);
   _mongocrypt_key_destroy (key_doc);
}


static void
_parse_fails (bson_t *key_bson, mongocrypt_status_t *status, const char *msg)
{
   _mongocrypt_key_doc_t *key_doc = _mongocrypt_key_new ();

   ASSERT_FAILS_STATUS (
      _mongocrypt_key_parse_owned (key_bson, key_doc, status), status, msg);
   _mongocrypt_key_destroy (key_doc);
}


static void
test_mongocrypt_key_parsing (_mongocrypt_tester_t *tester)
{
   bson_t key_bson = BSON_INITIALIZER;
   mongocrypt_status_t *status;
   mongocrypt_binary_t *uuid;

   uuid = TEST_BIN (16);
   status = mongocrypt_status_new ();

   /* successful case. */
   _recreate_and_reset (tester, &key_bson, status, NULL);
   _parse_ok (&key_bson, status);

   /* unrecognized fields */
   _recreate_and_reset (tester, &key_bson, status, NULL);
   BSON_APPEND_INT32 (&key_bson, "invalid", 123);
   _parse_fails (&key_bson, status, "unrecognized field 'invalid'");

   /* malformed BSON */
   _recreate_and_reset (tester, &key_bson, status, NULL);
   /* mess with the length to corrupt the BSON. */
   ((uint8_t *) bson_get_data (&key_bson))[4] = 0xFF;
   _parse_fails (&key_bson, status, "invalid BSON");

   /* _id: missing. */
   _recreate_and_reset (tester, &key_bson, status, "_id", NULL);
   _parse_fails (&key_bson, status, "invalid key, no '_id'");
   /* _id: wrong type. */
   _recreate_and_reset (tester, &key_bson, status, "_id", NULL);
   BSON_APPEND_INT32 (&key_bson, "_id", 123);
   _parse_fails (&key_bson, status, "invalid key, '_id' is not a UUID");
   /* _id: invalid binary subtype. */
   _recreate_and_reset (tester, &key_bson, status, "_id", NULL);
   BSON_APPEND_BINARY (
      &key_bson, "_id", BSON_SUBTYPE_BINARY, uuid->data, uuid->len);
   _parse_fails (&key_bson, status, "invalid key, '_id' is not a UUID");
   /* _id: invalid UUID length. */
   _recreate_and_reset (tester, &key_bson, status, "_id", NULL);
   BSON_APPEND_BINARY (&key_bson, "_id", BSON_SUBTYPE_UUID, uuid->data, 5);
   _parse_fails (&key_bson, status, "invalid key, '_id' is not a UUID");

   /* version: missing (ok) */
   _recreate_and_reset (tester, &key_bson, status, "version", NULL);
   _parse_ok (&key_bson, status);
   /* version: wrong type */
   _recreate_and_reset (tester, &key_bson, status, "version", NULL);
   BSON_APPEND_UTF8 (&key_bson, "version", "abc");
   _parse_fails (&key_bson, status, "invalid 'version'");
   /* version: > 0 */
   _recreate_and_reset (tester, &key_bson, status, "version", NULL);
   BSON_APPEND_INT32 (&key_bson, "version", 1);
   _parse_fails (&key_bson, status, "unsupported key document version");

   /* keyMaterial: missing. */
   _recreate_and_reset (tester, &key_bson, status, "keyMaterial", NULL);
   _parse_fails (&key_bson, status, "invalid key, no 'keyMaterial'");
   /* keyMaterial: wrong type. */
   _recreate_and_reset (tester, &key_bson, status, "keyMaterial", NULL);
   BSON_APPEND_INT32 (&key_bson, "keyMaterial", 1);
   _parse_fails (&key_bson, status, "invalid 'keyMaterial', expected binary");
   /* keyMaterial: wrong subtype. */
   _recreate_and_reset (tester, &key_bson, status, "keyMaterial", NULL);
   BSON_APPEND_BINARY (
      &key_bson, "keyMaterial", BSON_SUBTYPE_UUID, uuid->data, uuid->len);
   _parse_fails (
      &key_bson, status, "invalid 'keyMaterial', expected subtype 0");

   /* masterKey: missing. */
   _recreate_and_reset (tester, &key_bson, status, "masterKey", NULL);
   _parse_fails (&key_bson, status, "invalid key, no 'masterKey'");
   /* masterKey: missing provider. */
   _recreate_and_reset (tester, &key_bson, status, "masterKey", NULL);
   bson_concat (&key_bson, TMP_BSON ("{'masterKey': { }}"));
   _parse_fails (&key_bson, status, "expected UTF-8 provider");
   /* masterKey: wrong provider. */
   _recreate_and_reset (tester, &key_bson, status, "masterKey", NULL);
   bson_concat (&key_bson, TMP_BSON ("{'masterKey': { 'provider': 'bad' }}"));
   _parse_fails (&key_bson, status, "unrecognized KMS provider");
   /* masterKey: provider=aws, missing key */
   _recreate_and_reset (tester, &key_bson, status, "masterKey", NULL);
   bson_concat (
      &key_bson,
      TMP_BSON ("{'masterKey': { 'provider': 'aws', 'region': 'us-east-1' }}"));
   _parse_fails (&key_bson, status, "expected UTF-8 key");
   /* masterKey: provider=aws, missing region */
   _recreate_and_reset (tester, &key_bson, status, "masterKey", NULL);
   bson_concat (
      &key_bson,
      TMP_BSON ("{'masterKey': { 'provider': 'aws', 'key': 'cmk-string' }}"));
   _parse_fails (&key_bson, status, "expected UTF-8 region");
   /* masterKey: provider=aws, bad region */
   _recreate_and_reset (tester, &key_bson, status, "masterKey", NULL);
   bson_concat (&key_bson,
                TMP_BSON ("{'masterKey': { 'provider': 'aws', "
                          "'key': 'cmk-string', 'region': 1 }}"));
   _parse_fails (&key_bson, status, "expected UTF-8 region");

   /* creationDate: missing */
   _recreate_and_reset (tester, &key_bson, status, "creationDate", NULL);
   _parse_fails (&key_bson, status, "invalid key, no 'creationDate'");
   /* creationDate: wrong type */
   _recreate_and_reset (tester, &key_bson, status, "creationDate", NULL);
   BSON_APPEND_UTF8 (&key_bson, "creationDate", "abc");
   _parse_fails (&key_bson, status, "invalid 'creationDate', expect datetime");

   /* updateDate: missing */
   _recreate_and_reset (tester, &key_bson, status, "updateDate", NULL);
   _parse_fails (&key_bson, status, "invalid key, no 'updateDate'");

   /* updateDate: wrong type */
   _recreate_and_reset (tester, &key_bson, status, "updateDate", NULL);
   BSON_APPEND_UTF8 (&key_bson, "updateDate", "abc");
   _parse_fails (&key_bson, status, "invalid 'updateDate', expect datetime");

   /* status: missing */
   _recreate_and_reset (tester, &key_bson, status, "status", NULL);
   _parse_fails (&key_bson, status, "invalid key, no 'status'");

   /* masterKey: azure */
   _recreate_and_reset (tester, &key_bson, status, "masterKey", NULL);
   bson_concat (
      &key_bson,
      TMP_BSON (
         "{'masterKey': { 'provider': 'azure', 'keyVaultEndpoint': "
         "'abc.example.com', 'keyName': 'test', 'keyVersion': 'abc' }}"));
   _parse_ok (&key_bson, status);

   /* masterKey: gcp */
   _recreate_and_reset (tester, &key_bson, status, "masterKey", NULL);
   bson_concat (
      &key_bson,
      TMP_BSON ("{'masterKey': { 'provider': 'gcp', 'endpoint': "
                "'abc.example.com', 'projectId': 'project', 'location': "
                "'global', 'keyRing': 'ring', 'keyName': 'name' }}"));
   _parse_ok (&key_bson, status);

   mongocrypt_status_destroy (status);
   bson_destroy (&key_bson);
}

static void
test_mongocrypt_key_alt_name_from_iter (_mongocrypt_tester_t *tester)
{
   mongocrypt_status_t *status;
   bson_iter_t iter;
   bson_t *test;
   _mongocrypt_key_alt_name_t *key_alt_names;

   status = mongocrypt_status_new ();

   /* Empty alt names */
   test = TMP_BSON ("{'test': []}");
   bson_iter_init_find (&iter, test, "test");
   ASSERT_OK_STATUS (
      _mongocrypt_key_alt_name_from_iter (&iter, &key_alt_names, status),
      status);
   BSON_ASSERT (NULL == key_alt_names);

   /* One alt name */
   test = TMP_BSON ("{'test': ['a']}");
   bson_iter_init_find (&iter, test, "test");
   ASSERT_OK_STATUS (
      _mongocrypt_key_alt_name_from_iter (&iter, &key_alt_names, status),
      status);
   BSON_ASSERT (
      0 == strcmp ("a", _mongocrypt_key_alt_name_get_string (key_alt_names)));
   BSON_ASSERT (NULL == key_alt_names->next);
   _mongocrypt_key_alt_name_destroy_all (key_alt_names);

   /* Two alt names */
   test = TMP_BSON ("{'test': ['a', 'b']}");
   bson_iter_init_find (&iter, test, "test");
   ASSERT_OK_STATUS (
      _mongocrypt_key_alt_name_from_iter (&iter, &key_alt_names, status),
      status);
   BSON_ASSERT (
      0 == strcmp ("b", _mongocrypt_key_alt_name_get_string (key_alt_names)));
   BSON_ASSERT (
      0 ==
      strcmp ("a", _mongocrypt_key_alt_name_get_string (key_alt_names->next)));
   BSON_ASSERT (NULL == key_alt_names->next->next);
   _mongocrypt_key_alt_name_destroy_all (key_alt_names);

   /* Invalid alt names */
   test = TMP_BSON ("{'test': ['a', 1]}");
   bson_iter_init_find (&iter, test, "test");
   ASSERT_FAILS_STATUS (
      _mongocrypt_key_alt_name_from_iter (&iter, &key_alt_names, status),
      status,
      "unexpected non-UTF8");

   /* Duplicate alt names */
   test = TMP_BSON ("{'test': ['b', 'a', 'c', 'a']}");
   bson_iter_init_find (&iter, test, "test");
   ASSERT_FAILS_STATUS (
      _mongocrypt_key_alt_name_from_iter (&iter, &key_alt_names, status),
      status,
      "duplicate");

   mongocrypt_status_destroy (status);
}


void
_mongocrypt_tester_install_key (_mongocrypt_tester_t *tester)
{
   INSTALL_TEST (test_mongocrypt_key_parsing);
   INSTALL_TEST (test_mongocrypt_key_alt_name_from_iter);
}
