// Copyright 2024 The Pigweed Authors // // 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 // // https://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 "pw_grpc_private/hpack.h" #include "gtest/gtest.h" #include "pw_bytes/array.h" namespace pw::grpc { namespace { void TestIntegerDecode(ConstByteSpan input, int bits, int expected) { auto result = HpackIntegerDecode(input, bits); ASSERT_TRUE(result.ok()); EXPECT_EQ(*result, expected); EXPECT_TRUE(input.empty()); // input has advanced past the integer } void TestHuffmanDecode(ConstByteSpan input, std::string_view expected) { auto result = HpackHuffmanDecode(input); ASSERT_TRUE(result.ok()); EXPECT_EQ(*result, expected); } // Integer test cases from RFC 7541 Appendix C.1. TEST(HpackTest, HpackIntegerDecodeC11) { const auto kInput = bytes::Array<0b11101010>(); TestIntegerDecode(kInput, /*bits_in_first_byte=*/5, /*expected=*/10); } TEST(HpackTest, HpackIntegerDecodeC12) { const auto kInput = bytes::Array<0b11111111, 0b10011010, 0b00001010>(); TestIntegerDecode(kInput, /*bits_in_first_byte=*/5, /*expected=*/1337); } TEST(HpackTest, HpackIntegerDecodeC13) { const auto kInput = bytes::Array<0b00101010>(); TestIntegerDecode(kInput, /*bits_in_first_byte=*/8, /*expected=*/42); } // Huffman test cases from RFC 7541 Appendix C.4. // clang-format off const auto kHuffmanC41 = bytes::Array<0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff>(); const auto kHuffmanC42 = bytes::Array<0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf>(); const auto kHuffmanC43a = bytes::Array<0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f>(); const auto kHuffmanC43b = bytes::Array<0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf>(); // clang-format on TEST(HpackTest, HpackHuffmanDecodeC41) { TestHuffmanDecode(kHuffmanC41, "www.example.com"); } TEST(HpackTest, HpackHuffmanDecodeC42) { TestHuffmanDecode(kHuffmanC42, "no-cache"); } TEST(HpackTest, HpackHuffmanDecodeC43a) { TestHuffmanDecode(kHuffmanC43a, "custom-key"); } TEST(HpackTest, HpackHuffmanDecodeC43b) { TestHuffmanDecode(kHuffmanC43b, "custom-value"); } // Header field test cases from RFC 7541 Appendix C. TEST(HpackTest, HpackParseRequestHeadersFoundIndexedSlash) { // Appendix C.3.1. const auto kInput = bytes::Array<0x84>(); auto result = HpackParseRequestHeaders(kInput); ASSERT_TRUE(result.ok()); EXPECT_EQ(*result, "/"); } TEST(HpackTest, HpackParseRequestHeadersFoundIndexedHtml) { // Appendix C.3.3. const auto kInput = bytes::Array<0x85>(); auto result = HpackParseRequestHeaders(kInput); ASSERT_TRUE(result.ok()); EXPECT_EQ(*result, "/index.html"); } TEST(HpackTest, HpackParseRequestHeadersFoundNotIndexed) { // clang-format off const auto kInput = bytes::Array< // Appendix C.2.1. 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, // Appendix C.2.3. 0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // Appendix C.2.2. 0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68 >(); // clang-format on auto result = HpackParseRequestHeaders(kInput); ASSERT_TRUE(result.ok()); EXPECT_EQ(*result, "/sample/path"); } TEST(HpackTest, HpackParseRequestHeadersNotFound) { // clang-format off const auto kInput = bytes::Array< // Appendix C.2.1. 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, // Appendix C.2.3. 0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74 >(); // clang-format on auto result = HpackParseRequestHeaders(kInput); ASSERT_FALSE(result.ok()); EXPECT_EQ(result.status().code(), PW_STATUS_NOT_FOUND); } } // namespace } // namespace pw::grpc