blob: 2482aa19c1ee3972514ef64069fd30d541bae32a [file] [log] [blame]
// 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 <array>
#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<int> 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<int>(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<int>(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<InlineString<kHpackMaxStringSize>> HpackStringDecode(
ConstByteSpan& input) {
if (input.empty()) {
return Status::InvalidArgument();
}
int first = static_cast<int>(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<kHpackMaxStringSize>(
reinterpret_cast<const char*>(value.data()), value.size());
}
Result<InlineString<kHpackMaxStringSize>> HpackHuffmanDecode(
ConstByteSpan input) {
StringBuffer<kHpackMaxStringSize> 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<kHpackMaxStringSize>(buffer.view());
}
// RFC 7541 §6
Result<InlineString<kHpackMaxStringSize>> HpackParseRequestHeaders(
ConstByteSpan input) {
while (!input.empty()) {
int first = static_cast<int>(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