/*
 * Copyright 2021-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 "test_kms_assert.h"
#include "test_kms_util.h"

#include "kms_message/kms_kmip_response_parser.h"

#include "kms_kmip_response_parser_private.h"

/* The following sample data come from section 9.1.2 of
 * http://docs.oasis-open.org/kmip/spec/v1.4/os/kmip-spec-v1.4-os.html. The
 * data describes "A Structure containing an Enumeration, value 254, followed
 * by an Integer, value 255, having tags 420004 and 420005 respectively." */
static const char *const SAMPLE_KMIP =
   "42 00 20 | 01 | 00 00 00 20 | 42 00 04 | 05 | 00 00 00 04 | 00 00 00 FE 00 "
   "00 00 00 | 42 00 05 | 02 | 00 00 00 04 | 00 00 00 FF 00 00 00 00";

/* SAMPLE_KMIP_FIRST_LENGTH is the length of message after the first tag, type,
 * and length. */
static const int SAMPLE_KMIP_FIRST_LENGTH = 32;

/* SAMPLE_KMIP_LARGE_LENGTH is a byte size larger than the message. */
static const int SAMPLE_KMIP_LARGE_LENGTH = 1024;

void kms_kmip_response_parser_test (void); // -Wmissing-prototypes: for testing only.
void
kms_kmip_response_parser_test (void)
{
   kms_response_parser_t *parser;
   uint8_t *data;
   size_t outlen;
   int32_t want_bytes;
   kms_response_t *res;
   bool ok;

   data = hex_to_data (SAMPLE_KMIP, &outlen);
   parser = kms_kmip_response_parser_new (NULL);

   want_bytes =
      kms_response_parser_wants_bytes (parser, SAMPLE_KMIP_LARGE_LENGTH);
   ASSERT_CMPINT (KMS_KMIP_RESPONSE_PARSER_FIRST_LENGTH, ==, want_bytes);

   /* A smaller maximum size caps the requested bytes. */
   want_bytes = kms_response_parser_wants_bytes (parser, 1);
   ASSERT_CMPINT (1, ==, want_bytes);

   /* Feed one byte */
   ok = kms_response_parser_feed (parser, data, 1);
   ASSERT_PARSER_OK (parser);
   ASSERT (ok);

   want_bytes =
      kms_response_parser_wants_bytes (parser, SAMPLE_KMIP_LARGE_LENGTH);
   ASSERT_CMPINT (KMS_KMIP_RESPONSE_PARSER_FIRST_LENGTH - 1, ==, want_bytes);

   /* Feed the remaining bytes. */
   ok = kms_response_parser_feed (
      parser, data + 1, KMS_KMIP_RESPONSE_PARSER_FIRST_LENGTH - 1);
   ASSERT_PARSER_OK (parser);
   ASSERT (ok);

   /* The parser knows first length. Expect the parser wants the remaining
    * length. */
   want_bytes =
      kms_response_parser_wants_bytes (parser, SAMPLE_KMIP_LARGE_LENGTH);
   ASSERT_CMPINT (want_bytes, ==, SAMPLE_KMIP_FIRST_LENGTH);

   ok = kms_response_parser_feed (parser,
                                  data + KMS_KMIP_RESPONSE_PARSER_FIRST_LENGTH,
                                  SAMPLE_KMIP_FIRST_LENGTH);
   ASSERT_PARSER_OK (parser);
   ASSERT (ok);

   /* Parser has full message. */
   want_bytes =
      kms_response_parser_wants_bytes (parser, SAMPLE_KMIP_LARGE_LENGTH);
   ASSERT_CMPINT (want_bytes, ==, 0);

   res = kms_response_parser_get_response (parser);
   ASSERT_PARSER_OK (parser);
   ASSERT (res);
   kms_response_destroy (res);

   kms_response_parser_destroy (parser);
   free (data);
}

static void
feed_full_response (kms_response_parser_t *parser, uint8_t *data)
{
   uint32_t i = 0;
   int32_t want_bytes;
   bool ok;

   want_bytes =
      kms_response_parser_wants_bytes (parser, SAMPLE_KMIP_LARGE_LENGTH);
   ASSERT_CMPINT (KMS_KMIP_RESPONSE_PARSER_FIRST_LENGTH, ==, want_bytes);
   while (want_bytes > 0) {
      ok = kms_response_parser_feed (parser, data + i, want_bytes);
      ASSERT_PARSER_OK (parser);
      ASSERT (ok);
      i += want_bytes;
      want_bytes =
         kms_response_parser_wants_bytes (parser, SAMPLE_KMIP_LARGE_LENGTH);
   }
}

void kms_kmip_response_parser_reuse_test (void); // -Wmissing-prototypes: for testing only.
void
kms_kmip_response_parser_reuse_test (void)
{
   kms_response_parser_t *parser;
   uint8_t *data;
   size_t outlen;
   kms_response_t *res;

   data = hex_to_data (SAMPLE_KMIP, &outlen);
   parser = kms_kmip_response_parser_new (NULL);

   feed_full_response (parser, data);
   ASSERT_PARSER_OK (parser);

   res = kms_response_parser_get_response (parser);
   ASSERT_PARSER_OK (parser);
   ASSERT (res);
   kms_response_destroy (res);

   /* Feed another full response. */
   feed_full_response (parser, data);
   ASSERT_PARSER_OK (parser);

   res = kms_response_parser_get_response (parser);
   ASSERT_PARSER_OK (parser);
   ASSERT (res);
   kms_response_destroy (res);

   kms_response_parser_destroy (parser);
   free (data);
}

void kms_kmip_response_parser_excess_test (void); // -Wmissing-prototypes: for testing only.
void
kms_kmip_response_parser_excess_test (void)
{
   kms_response_parser_t *parser;
   uint8_t *data;
   size_t outlen;
   bool ok;

   data = hex_to_data (SAMPLE_KMIP, &outlen);
   parser = kms_kmip_response_parser_new (NULL);

   feed_full_response (parser, data);
   ASSERT_PARSER_OK (parser);

   ok = kms_response_parser_feed (parser, data, 1);
   ASSERT_PARSER_ERROR (parser, "KMIP parser was fed too much data");
   ASSERT (!ok);
   kms_response_parser_destroy (parser);
   free (data);
}

void kms_kmip_response_parser_notenough_test (void); // -Wmissing-prototypes: for testing only.
void
kms_kmip_response_parser_notenough_test (void)
{
   kms_response_parser_t *parser;
   uint8_t *data;
   size_t outlen;
   kms_response_t *res;
   bool ok;

   data = hex_to_data (SAMPLE_KMIP, &outlen);
   parser = kms_kmip_response_parser_new (NULL);

   ok = kms_response_parser_feed (
      parser, data, KMS_KMIP_RESPONSE_PARSER_FIRST_LENGTH);
   ASSERT_PARSER_OK (parser);
   ASSERT (ok);

   res = kms_response_parser_get_response (parser);
   ASSERT_PARSER_ERROR (parser, "KMIP parser does not have a complete message");
   ASSERT (!res);

   kms_response_parser_destroy (parser);
   free (data);
}
