/** @file
 *
 *  Regression Tests for HPACK
 *
 *  @section license License
 *
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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 "HPACK.h"
#include "HuffmanCodec.h"
#include "tscore/TestBox.h"

// Constants for regression test
const static int DYNAMIC_TABLE_SIZE_FOR_REGRESSION_TEST = 256;
const static int BUFSIZE_FOR_REGRESSION_TEST            = 128;
const static int MAX_TEST_FIELD_NUM                     = 8;
const static int MAX_REQUEST_HEADER_SIZE                = 131072;
const static int MAX_TABLE_SIZE                         = 4096;

/***********************************************************************************
 *                                                                                 *
 *                       Regression test for HPACK                                 *
 *                                                                                 *
 *  Some test cases are based on examples of specification.                        *
 *  - https://tools.ietf.org/html/rfc7541#appendix-C                               *
 *                                                                                 *
 ***********************************************************************************/

// [RFC 7541] C.2.4. Indexed Header Field
const static struct {
  int index;
  char *raw_name;
  char *raw_value;
  uint8_t *encoded_field;
  int encoded_field_len;
} indexed_test_case[] = {{2, (char *)":method", (char *)"GET", (uint8_t *)"\x82", 1}};

// [RFC 7541] C.2. Header Field Representation Examples
const static struct {
  char *raw_name;
  char *raw_value;
  int index;
  HpackField type;
  uint8_t *encoded_field;
  int encoded_field_len;
} literal_test_case[] = {{(char *)"custom-key", (char *)"custom-header", 0, HpackField::INDEXED_LITERAL,
                          (uint8_t *)"\x40\x0a"
                                     "custom-key\x0d"
                                     "custom-header",
                          26},
                         {(char *)"custom-key", (char *)"custom-header", 0, HpackField::NOINDEX_LITERAL,
                          (uint8_t *)"\x00\x0a"
                                     "custom-key\x0d"
                                     "custom-header",
                          26},
                         {(char *)"custom-key", (char *)"custom-header", 0, HpackField::NEVERINDEX_LITERAL,
                          (uint8_t *)"\x10\x0a"
                                     "custom-key\x0d"
                                     "custom-header",
                          26},
                         {(char *)":path", (char *)"/sample/path", 4, HpackField::INDEXED_LITERAL,
                          (uint8_t *)"\x44\x0c"
                                     "/sample/path",
                          14},
                         {(char *)":path", (char *)"/sample/path", 4, HpackField::NOINDEX_LITERAL,
                          (uint8_t *)"\x04\x0c"
                                     "/sample/path",
                          14},
                         {(char *)":path", (char *)"/sample/path", 4, HpackField::NEVERINDEX_LITERAL,
                          (uint8_t *)"\x14\x0c"
                                     "/sample/path",
                          14},
                         {(char *)"password", (char *)"secret", 0, HpackField::INDEXED_LITERAL,
                          (uint8_t *)"\x40\x08"
                                     "password\x06"
                                     "secret",
                          17},
                         {(char *)"password", (char *)"secret", 0, HpackField::NOINDEX_LITERAL,
                          (uint8_t *)"\x00\x08"
                                     "password\x06"
                                     "secret",
                          17},
                         {(char *)"password", (char *)"secret", 0, HpackField::NEVERINDEX_LITERAL,
                          (uint8_t *)"\x10\x08"
                                     "password\x06"
                                     "secret",
                          17},
                         // with Huffman Coding
                         {(char *)"custom-key", (char *)"custom-header", 0, HpackField::INDEXED_LITERAL,
                          (uint8_t *)"\x40"
                                     "\x88\x25\xa8\x49\xe9\x5b\xa9\x7d\x7f"
                                     "\x89\x25\xa8\x49\xe9\x5a\x72\x8e\x42\xd9",
                          20},
                         {(char *)"custom-key", (char *)"custom-header", 0, HpackField::NOINDEX_LITERAL,
                          (uint8_t *)"\x00"
                                     "\x88\x25\xa8\x49\xe9\x5b\xa9\x7d\x7f"
                                     "\x89\x25\xa8\x49\xe9\x5a\x72\x8e\x42\xd9",
                          20},
                         {(char *)"custom-key", (char *)"custom-header", 0, HpackField::NEVERINDEX_LITERAL,
                          (uint8_t *)"\x10"
                                     "\x88\x25\xa8\x49\xe9\x5b\xa9\x7d\x7f"
                                     "\x89\x25\xa8\x49\xe9\x5a\x72\x8e\x42\xd9",
                          20},
                         {(char *)":path", (char *)"/sample/path", 4, HpackField::INDEXED_LITERAL,
                          (uint8_t *)"\x44"
                                     "\x89\x61\x03\xa6\xba\x0a\xc5\x63\x4c\xff",
                          11},
                         {(char *)":path", (char *)"/sample/path", 4, HpackField::NOINDEX_LITERAL,
                          (uint8_t *)"\x04"
                                     "\x89\x61\x03\xa6\xba\x0a\xc5\x63\x4c\xff",
                          11},
                         {(char *)":path", (char *)"/sample/path", 4, HpackField::NEVERINDEX_LITERAL,
                          (uint8_t *)"\x14"
                                     "\x89\x61\x03\xa6\xba\x0a\xc5\x63\x4c\xff",
                          11},
                         {(char *)"password", (char *)"secret", 0, HpackField::INDEXED_LITERAL,
                          (uint8_t *)"\x40"
                                     "\x86\xac\x68\x47\x83\xd9\x27"
                                     "\x84\x41\x49\x61\x53",
                          13},
                         {(char *)"password", (char *)"secret", 0, HpackField::NOINDEX_LITERAL,
                          (uint8_t *)"\x00"
                                     "\x86\xac\x68\x47\x83\xd9\x27"
                                     "\x84\x41\x49\x61\x53",
                          13},
                         {(char *)"password", (char *)"secret", 0, HpackField::NEVERINDEX_LITERAL,
                          (uint8_t *)"\x10"
                                     "\x86\xac\x68\x47\x83\xd9\x27"
                                     "\x84\x41\x49\x61\x53",
                          13}};

// [RFC 7541] C.3. Request Examples without Huffman Coding - C.3.1. First Request
// [RFC 7541] C.4. Request Examples with Huffman Coding - C.4.1. First Request
const static struct {
  char *raw_name;
  char *raw_value;
} raw_field_request_test_case[][MAX_TEST_FIELD_NUM] = {{
                                                         {(char *)":method", (char *)"GET"},
                                                         {(char *)":scheme", (char *)"http"},
                                                         {(char *)":path", (char *)"/"},
                                                         {(char *)":authority", (char *)"www.example.com"},
                                                         {(char *)"", (char *)""} // End of this test case
                                                       },
                                                       {
                                                         {(char *)":method", (char *)"GET"},
                                                         {(char *)":scheme", (char *)"http"},
                                                         {(char *)":path", (char *)"/"},
                                                         {(char *)":authority", (char *)"www.example.com"},
                                                         {(char *)"", (char *)""} // End of this test case
                                                       }};
const static struct {
  uint8_t *encoded_field;
  int encoded_field_len;
} encoded_field_request_test_case[] = {{(uint8_t *)"\x40"
                                                   "\x7:method"
                                                   "\x3GET"
                                                   "\x40"
                                                   "\x7:scheme"
                                                   "\x4http"
                                                   "\x40"
                                                   "\x5:path"
                                                   "\x1/"
                                                   "\x40"
                                                   "\xa:authority"
                                                   "\xfwww.example.com",
                                        64},
                                       {(uint8_t *)"\x40"
                                                   "\x85\xb9\x49\x53\x39\xe4"
                                                   "\x83\xc5\x83\x7f"
                                                   "\x40"
                                                   "\x85\xb8\x82\x4e\x5a\x4b"
                                                   "\x83\x9d\x29\xaf"
                                                   "\x40"
                                                   "\x84\xb9\x58\xd3\x3f"
                                                   "\x81\x63"
                                                   "\x40"
                                                   "\x88\xb8\x3b\x53\x39\xec\x32\x7d\x7f"
                                                   "\x8c\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff",
                                        53}};

// [RFC 7541] C.6. Response Examples with Huffman Coding
const static struct {
  char *raw_name;
  char *raw_value;
} raw_field_response_test_case[][MAX_TEST_FIELD_NUM] = {
  {
    {(char *)":status", (char *)"302"},
    {(char *)"cache-control", (char *)"private"},
    {(char *)"date", (char *)"Mon, 21 Oct 2013 20:13:21 GMT"},
    {(char *)"location", (char *)"https://www.example.com"},
    {(char *)"", (char *)""} // End of this test case
  },
  {
    {(char *)":status", (char *)"307"},
    {(char *)"cache-control", (char *)"private"},
    {(char *)"date", (char *)"Mon, 21 Oct 2013 20:13:21 GMT"},
    {(char *)"location", (char *)"https://www.example.com"},
    {(char *)"", (char *)""} // End of this test case
  },
  {
    {(char *)":status", (char *)"200"},
    {(char *)"cache-control", (char *)"private"},
    {(char *)"date", (char *)"Mon, 21 Oct 2013 20:13:22 GMT"},
    {(char *)"location", (char *)"https://www.example.com"},
    {(char *)"content-encoding", (char *)"gzip"},
    {(char *)"set-cookie", (char *)"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
    {(char *)"", (char *)""} // End of this test case
  }};
const static struct {
  uint8_t *encoded_field;
  int encoded_field_len;
} encoded_field_response_test_case[] = {{(uint8_t *)"\x48\x82"
                                                    "\x64\x02"
                                                    "\x58\x85"
                                                    "\xae\xc3\x77\x1a\x4b"
                                                    "\x61\x96"
                                                    "\xd0\x7a\xbe\x94\x10\x54\xd4\x44\xa8\x20\x05\x95\x04\x0b\x81\x66"
                                                    "\xe0\x82\xa6\x2d\x1b\xff"
                                                    "\x6e\x91"
                                                    "\x9d\x29\xad\x17\x18\x63\xc7\x8f\x0b\x97\xc8\xe9\xae\x82\xae\x43"
                                                    "\xd3",
                                         54},
                                        {(uint8_t *)"\x48\x83"
                                                    "\x64\x0e\xff"
                                                    "\xc1"
                                                    "\xc0"
                                                    "\xbf",
                                         8},
                                        {(uint8_t *)"\x88"
                                                    "\xc1"
                                                    "\x61\x96"
                                                    "\xd0\x7a\xbe\x94\x10\x54\xd4\x44\xa8\x20\x05\x95\x04\x0b\x81\x66"
                                                    "\xe0\x84\xa6\x2d\x1b\xff"
                                                    "\xc0"
                                                    "\x5a\x83"
                                                    "\x9b\xd9\xab"
                                                    "\x77\xad"
                                                    "\x94\xe7\x82\x1d\xd7\xf2\xe6\xc7\xb3\x35\xdf\xdf\xcd\x5b\x39\x60"
                                                    "\xd5\xaf\x27\x08\x7f\x36\x72\xc1\xab\x27\x0f\xb5\x29\x1f\x95\x87"
                                                    "\x31\x60\x65\xc0\x03\xed\x4e\xe5\xb1\x06\x3d\x50\x07",
                                         79}};
const static struct {
  uint32_t size;
  char *name;
  char *value;
} dynamic_table_response_test_case[][MAX_TEST_FIELD_NUM] = {
  {
    {63, (char *)"location", (char *)"https://www.example.com"},
    {65, (char *)"date", (char *)"Mon, 21 Oct 2013 20:13:21 GMT"},
    {52, (char *)"cache-control", (char *)"private"},
    {42, (char *)":status", (char *)"302"},
    {0, (char *)"", (char *)""} // End of this test case
  },
  {
    {42, (char *)":status", (char *)"307"},
    {63, (char *)"location", (char *)"https://www.example.com"},
    {65, (char *)"date", (char *)"Mon, 21 Oct 2013 20:13:21 GMT"},
    {52, (char *)"cache-control", (char *)"private"},
    {0, (char *)"", (char *)""} // End of this test case
  },
  {
    {98, (char *)"set-cookie", (char *)"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
    {52, (char *)"content-encoding", (char *)"gzip"},
    {65, (char *)"date", (char *)"Mon, 21 Oct 2013 20:13:22 GMT"},
    {0, (char *)"", (char *)""} // End of this test case
  }};

/***********************************************************************************
 *                                                                                 *
 *                                Regression test codes                            *
 *                                                                                 *
 ***********************************************************************************/

REGRESSION_TEST(HPACK_EncodeIndexedHeaderField)(RegressionTest *t, int, int *pstatus)
{
  TestBox box(t, pstatus);
  box = REGRESSION_TEST_PASSED;

  uint8_t buf[BUFSIZE_FOR_REGRESSION_TEST];

  for (const auto &i : indexed_test_case) {
    memset(buf, 0, BUFSIZE_FOR_REGRESSION_TEST);

    int len = encode_indexed_header_field(buf, buf + BUFSIZE_FOR_REGRESSION_TEST, i.index);

    box.check(len == i.encoded_field_len, "encoded length was %d, expecting %d", len, i.encoded_field_len);
    box.check(len > 0 && memcmp(buf, i.encoded_field, len) == 0, "encoded value was invalid");
  }
}

REGRESSION_TEST(HPACK_EncodeLiteralHeaderField)(RegressionTest *t, int, int *pstatus)
{
  TestBox box(t, pstatus);
  box = REGRESSION_TEST_PASSED;

  uint8_t buf[BUFSIZE_FOR_REGRESSION_TEST];
  int len;
  HpackIndexingTable indexing_table(4096);

  for (unsigned int i = 9; i < sizeof(literal_test_case) / sizeof(literal_test_case[0]); i++) {
    memset(buf, 0, BUFSIZE_FOR_REGRESSION_TEST);

    ats_scoped_obj<HTTPHdr> headers(new HTTPHdr);
    headers->create(HTTP_TYPE_RESPONSE);
    MIMEField *field = mime_field_create(headers->m_heap, headers->m_http->m_fields_impl);
    MIMEFieldWrapper header(field, headers->m_heap, headers->m_http->m_fields_impl);

    header.name_set(literal_test_case[i].raw_name, strlen(literal_test_case[i].raw_name));
    header.value_set(literal_test_case[i].raw_value, strlen(literal_test_case[i].raw_value));
    if (literal_test_case[i].index > 0) {
      len = encode_literal_header_field_with_indexed_name(buf, buf + BUFSIZE_FOR_REGRESSION_TEST, header,
                                                          literal_test_case[i].index, indexing_table, literal_test_case[i].type);
    } else {
      header.name_set(literal_test_case[i].raw_name, strlen(literal_test_case[i].raw_name));
      len = encode_literal_header_field_with_new_name(buf, buf + BUFSIZE_FOR_REGRESSION_TEST, header, indexing_table,
                                                      literal_test_case[i].type);
    }

    box.check(len == literal_test_case[i].encoded_field_len, "encoded length was %d, expecting %d", len,
              literal_test_case[i].encoded_field_len);
    box.check(len > 0 && memcmp(buf, literal_test_case[i].encoded_field, len) == 0, "encoded value was invalid");
  }
}

REGRESSION_TEST(HPACK_Encode)(RegressionTest *t, int, int *pstatus)
{
  TestBox box(t, pstatus);
  box = REGRESSION_TEST_PASSED;

  uint8_t buf[BUFSIZE_FOR_REGRESSION_TEST];
  HpackIndexingTable indexing_table(4096);
  indexing_table.update_maximum_size(DYNAMIC_TABLE_SIZE_FOR_REGRESSION_TEST);

  for (unsigned int i = 0; i < sizeof(encoded_field_response_test_case) / sizeof(encoded_field_response_test_case[0]); i++) {
    ats_scoped_obj<HTTPHdr> headers(new HTTPHdr);
    headers->create(HTTP_TYPE_RESPONSE);

    for (unsigned int j = 0; j < sizeof(raw_field_response_test_case[i]) / sizeof(raw_field_response_test_case[i][0]); j++) {
      const char *expected_name  = raw_field_response_test_case[i][j].raw_name;
      const char *expected_value = raw_field_response_test_case[i][j].raw_value;
      if (strlen(expected_name) == 0) {
        break;
      }

      MIMEField *field = mime_field_create(headers->m_heap, headers->m_http->m_fields_impl);
      field->name_set(headers->m_heap, headers->m_http->m_fields_impl, expected_name, strlen(expected_name));
      field->value_set(headers->m_heap, headers->m_http->m_fields_impl, expected_value, strlen(expected_value));
      mime_hdr_field_attach(headers->m_http->m_fields_impl, field, 1, nullptr);
    }

    memset(buf, 0, BUFSIZE_FOR_REGRESSION_TEST);
    uint64_t buf_len = BUFSIZE_FOR_REGRESSION_TEST;
    int64_t len      = hpack_encode_header_block(indexing_table, buf, buf_len, headers);

    if (len < 0) {
      box.check(false, "hpack_encode_header_blocks returned negative value: %" PRId64, len);
      break;
    }

    box.check(len == encoded_field_response_test_case[i].encoded_field_len, "encoded length was %" PRId64 ", expecting %d", len,
              encoded_field_response_test_case[i].encoded_field_len);
    box.check(len > 0 && memcmp(buf, encoded_field_response_test_case[i].encoded_field, len) == 0, "encoded value was invalid");

    // Check dynamic table
    uint32_t expected_dynamic_table_size = 0;
    for (unsigned int j = 0; j < sizeof(dynamic_table_response_test_case[i]) / sizeof(dynamic_table_response_test_case[i][0]);
         j++) {
      const char *expected_name  = dynamic_table_response_test_case[i][j].name;
      const char *expected_value = dynamic_table_response_test_case[i][j].value;
      int expected_name_len      = strlen(expected_name);
      int expected_value_len     = strlen(expected_value);

      if (expected_name_len == 0) {
        break;
      }

      HpackLookupResult lookupResult = indexing_table.lookup(expected_name, expected_name_len, expected_value, expected_value_len);
      box.check(lookupResult.match_type == HpackMatch::EXACT && lookupResult.index_type == HpackIndex::DYNAMIC,
                "the header field is not indexed");

      expected_dynamic_table_size += dynamic_table_response_test_case[i][j].size;
    }
    box.check(indexing_table.size() == expected_dynamic_table_size, "dynamic table is unexpected size: %d", indexing_table.size());
  }
}

REGRESSION_TEST(HPACK_DecodeIndexedHeaderField)(RegressionTest *t, int, int *pstatus)
{
  TestBox box(t, pstatus);
  box = REGRESSION_TEST_PASSED;

  HpackIndexingTable indexing_table(4096);

  for (const auto &i : indexed_test_case) {
    ats_scoped_obj<HTTPHdr> headers(new HTTPHdr);
    headers->create(HTTP_TYPE_REQUEST);
    MIMEField *field = mime_field_create(headers->m_heap, headers->m_http->m_fields_impl);
    MIMEFieldWrapper header(field, headers->m_heap, headers->m_http->m_fields_impl);

    int len = decode_indexed_header_field(header, i.encoded_field, i.encoded_field + i.encoded_field_len, indexing_table);

    box.check(len == i.encoded_field_len, "decoded length was %d, expecting %d", len, i.encoded_field_len);

    int name_len;
    const char *name = header.name_get(&name_len);
    box.check(len > 0 && memcmp(name, i.raw_name, name_len) == 0, "decoded header name was invalid");

    int actual_value_len;
    const char *actual_value = header.value_get(&actual_value_len);
    box.check(memcmp(actual_value, i.raw_value, actual_value_len) == 0, "decoded header value was invalid");
  }
}

REGRESSION_TEST(HPACK_DecodeLiteralHeaderField)(RegressionTest *t, int, int *pstatus)
{
  TestBox box(t, pstatus);
  box = REGRESSION_TEST_PASSED;

  HpackIndexingTable indexing_table(4096);

  for (const auto &i : literal_test_case) {
    ats_scoped_obj<HTTPHdr> headers(new HTTPHdr);
    headers->create(HTTP_TYPE_REQUEST);
    MIMEField *field = mime_field_create(headers->m_heap, headers->m_http->m_fields_impl);
    MIMEFieldWrapper header(field, headers->m_heap, headers->m_http->m_fields_impl);

    int len = decode_literal_header_field(header, i.encoded_field, i.encoded_field + i.encoded_field_len, indexing_table);

    box.check(len == i.encoded_field_len, "decoded length was %d, expecting %d", len, i.encoded_field_len);

    int name_len;
    const char *name = header.name_get(&name_len);
    box.check(name_len > 0 && memcmp(name, i.raw_name, name_len) == 0, "decoded header name was invalid");

    int actual_value_len;
    const char *actual_value = header.value_get(&actual_value_len);
    box.check(actual_value_len > 0 && memcmp(actual_value, i.raw_value, actual_value_len) == 0, "decoded header value was invalid");
  }
}

REGRESSION_TEST(HPACK_Decode)(RegressionTest *t, int, int *pstatus)
{
  TestBox box(t, pstatus);
  box = REGRESSION_TEST_PASSED;

  HpackIndexingTable indexing_table(4096);

  for (unsigned int i = 0; i < sizeof(encoded_field_request_test_case) / sizeof(encoded_field_request_test_case[0]); i++) {
    ats_scoped_obj<HTTPHdr> headers(new HTTPHdr);
    headers->create(HTTP_TYPE_REQUEST);

    hpack_decode_header_block(indexing_table, headers, encoded_field_request_test_case[i].encoded_field,
                              encoded_field_request_test_case[i].encoded_field_len, MAX_REQUEST_HEADER_SIZE, MAX_TABLE_SIZE);

    for (unsigned int j = 0; j < sizeof(raw_field_request_test_case[i]) / sizeof(raw_field_request_test_case[i][0]); j++) {
      const char *expected_name  = raw_field_request_test_case[i][j].raw_name;
      const char *expected_value = raw_field_request_test_case[i][j].raw_value;
      if (strlen(expected_name) == 0) {
        break;
      }

      MIMEField *field = headers->field_find(expected_name, strlen(expected_name));
      box.check(field != nullptr, "A MIMEField that has \"%s\" as name doesn't exist", expected_name);

      if (field) {
        int actual_value_len;
        const char *actual_value = field->value_get(&actual_value_len);
        box.check(strncmp(expected_value, actual_value, actual_value_len) == 0,
                  "A MIMEField that has \"%s\" as value doesn't exist", expected_value);
      }
    }
  }
}

void
forceLinkRegressionHPACK()
{
  // NOTE: Do Nothing
}
