blob: 8beb73d508c7cc2974209b02f7782668a5d8f707 [file] [log] [blame]
/** @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
}