// 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 #include "pw_assert/check.h" #include "pw_bytes/byte_builder.h" #include "pw_log/log.h" #include "pw_status/status.h" #include "pw_status/try.h" #include "pw_string/string_builder.h" #include "pw_string/util.h" namespace pw::grpc { namespace { #include "hpack.autogen.inc" } // RFC 7541 §5.1 Result HpackIntegerDecode(ConstByteSpan& input, int bits_in_first_byte) { if (input.empty()) { return Status::InvalidArgument(); } const int n = bits_in_first_byte; int i = static_cast(input[0]) & ((1 << n) - 1); input = input.subspan(1); if (i < ((1 << n) - 1)) { return i; } int m = 0; while (true) { if (input.empty()) { return Status::InvalidArgument(); } int b = static_cast(input[0]); input = input.subspan(1); i += (b & 127) << m; m += 7; if ((b & 128) == 0) { return i; } if (m >= 31) { // Shift overflowed. return Status::InvalidArgument(); } } } // RFC 7541 §5.2 Result> HpackStringDecode( ConstByteSpan& input) { if (input.empty()) { return Status::InvalidArgument(); } int first = static_cast(input[0]); bool is_huffman = (first & 0x80) != 0; PW_TRY_ASSIGN(size_t length, HpackIntegerDecode(input, 7)); if (length > input.size()) { return Status::InvalidArgument(); } if (length > kHpackMaxStringSize) { return Status::OutOfRange(); } auto value = input.subspan(0, length); input = input.subspan(length); if (is_huffman) { return HpackHuffmanDecode(value); } return InlineString( reinterpret_cast(value.data()), value.size()); } Result> HpackHuffmanDecode( ConstByteSpan input) { StringBuffer buffer; int table_index = 0; // See definition of kHuffmanDecoderTable in hpack.autogen.h. for (std::byte byte : input) { for (int k = 7; k >= 0; k--) { auto bit = int(byte >> k) & 0x1; auto cmd = kHuffmanDecoderTable[table_index][bit]; if ((cmd & 0b1000'0000) == 0) { table_index = cmd; } else if (cmd == 0b1111'1110 || cmd == 0b1111'1111) { // Error: unprintable character or the decoder entered an invalid state. return Status::InvalidArgument(); } else { if (buffer.size() == buffer.max_size()) { return Status::OutOfRange(); } buffer.push_back(32 + (cmd & 0b0111'1111)); table_index = 0; } } } return InlineString(buffer.view()); } // RFC 7541 §6 Result> HpackParseRequestHeaders( ConstByteSpan input) { while (!input.empty()) { int first = static_cast(input[0]); // RFC 7541 §6.1 if ((first & 0b1000'0000) != 0) { PW_TRY_ASSIGN(int index, HpackIntegerDecode(input, 7)); // RFC 7541 Appendix A: these are the only static table entries for :path. if (index == 4) { return "/"; } if (index == 5) { return "/index.html"; } continue; } // RFC 7541 §6.3: dynamic table size update if ((first & 0b1110'0000) == 0b0010'0000) { // Ignore: we don't use the dynamic table. PW_TRY(HpackIntegerDecode(input, 5)); continue; } // RFC 7541 §6.2 int index; if ((first & 0b1100'0000) == 0b0100'0000) { PW_TRY_ASSIGN(index, HpackIntegerDecode(input, 6)); } else { PW_CHECK((first & 0b1111'0000) == 0b0000'0000 || (first & 0b1111'0000) == 0b0001'0000); PW_TRY_ASSIGN(index, HpackIntegerDecode(input, 4)); } // Check if the name is ":path". bool is_path; if (index == 0) { PW_TRY_ASSIGN(auto name, HpackStringDecode(input)); is_path = (name == ":path"); } else { // RFC 7541 Appendix A: these are the only static table entries for :path. is_path = (index == 4 || index == 5); } // Always extract the value to advance the `input` span. PW_TRY_ASSIGN(auto value, HpackStringDecode(input)); if (is_path) { return value; } } return Status::NotFound(); } ConstByteSpan ResponseHeadersPayload() { return as_bytes(span{kResponseHeaderFields}); } ConstByteSpan ResponseTrailersPayload(Status response_code) { PW_CHECK_UINT_LT(response_code.code(), kResponseTrailerFields.size()); auto* payload = &kResponseTrailerFields[response_code.code()]; return as_bytes(span{payload->bytes}.subspan(0, payload->size)); } } // namespace pw::grpc