xref: /aosp_15_r20/external/pigweed/pw_grpc/hpack.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_grpc_private/hpack.h"
16 
17 #include <array>
18 
19 #include "pw_assert/check.h"
20 #include "pw_bytes/byte_builder.h"
21 #include "pw_log/log.h"
22 #include "pw_status/status.h"
23 #include "pw_status/try.h"
24 #include "pw_string/string_builder.h"
25 #include "pw_string/util.h"
26 
27 namespace pw::grpc {
28 
29 namespace {
30 #include "hpack.autogen.inc"
31 }
32 
33 // RFC 7541 §5.1
HpackIntegerDecode(ConstByteSpan & input,int bits_in_first_byte)34 Result<int> HpackIntegerDecode(ConstByteSpan& input, int bits_in_first_byte) {
35   if (input.empty()) {
36     return Status::InvalidArgument();
37   }
38 
39   const int n = bits_in_first_byte;
40   int i = static_cast<int>(input[0]) & ((1 << n) - 1);
41   input = input.subspan(1);
42 
43   if (i < ((1 << n) - 1)) {
44     return i;
45   }
46 
47   int m = 0;
48   while (true) {
49     if (input.empty()) {
50       return Status::InvalidArgument();
51     }
52     int b = static_cast<int>(input[0]);
53     input = input.subspan(1);
54     i += (b & 127) << m;
55     m += 7;
56     if ((b & 128) == 0) {
57       return i;
58     }
59     if (m >= 31) {
60       // Shift overflowed.
61       return Status::InvalidArgument();
62     }
63   }
64 }
65 
66 // RFC 7541 §5.2
HpackStringDecode(ConstByteSpan & input)67 Result<InlineString<kHpackMaxStringSize>> HpackStringDecode(
68     ConstByteSpan& input) {
69   if (input.empty()) {
70     return Status::InvalidArgument();
71   }
72 
73   int first = static_cast<int>(input[0]);
74   bool is_huffman = (first & 0x80) != 0;
75 
76   PW_TRY_ASSIGN(size_t length, HpackIntegerDecode(input, 7));
77   if (length > input.size()) {
78     return Status::InvalidArgument();
79   }
80   if (length > kHpackMaxStringSize) {
81     return Status::OutOfRange();
82   }
83 
84   auto value = input.subspan(0, length);
85   input = input.subspan(length);
86   if (is_huffman) {
87     return HpackHuffmanDecode(value);
88   }
89   return InlineString<kHpackMaxStringSize>(
90       reinterpret_cast<const char*>(value.data()), value.size());
91 }
92 
HpackHuffmanDecode(ConstByteSpan input)93 Result<InlineString<kHpackMaxStringSize>> HpackHuffmanDecode(
94     ConstByteSpan input) {
95   StringBuffer<kHpackMaxStringSize> buffer;
96   int table_index = 0;
97 
98   // See definition of kHuffmanDecoderTable in hpack.autogen.h.
99   for (std::byte byte : input) {
100     for (int k = 7; k >= 0; k--) {
101       auto bit = int(byte >> k) & 0x1;
102       auto cmd = kHuffmanDecoderTable[table_index][bit];
103       if ((cmd & 0b1000'0000) == 0) {
104         table_index = cmd;
105       } else if (cmd == 0b1111'1110 || cmd == 0b1111'1111) {
106         // Error: unprintable character or the decoder entered an invalid state.
107         return Status::InvalidArgument();
108       } else {
109         if (buffer.size() == buffer.max_size()) {
110           return Status::OutOfRange();
111         }
112         buffer.push_back(32 + (cmd & 0b0111'1111));
113         table_index = 0;
114       }
115     }
116   }
117 
118   return InlineString<kHpackMaxStringSize>(buffer.view());
119 }
120 
121 // RFC 7541 §6
HpackParseRequestHeaders(ConstByteSpan input)122 Result<InlineString<kHpackMaxStringSize>> HpackParseRequestHeaders(
123     ConstByteSpan input) {
124   while (!input.empty()) {
125     int first = static_cast<int>(input[0]);
126 
127     // RFC 7541 §6.1
128     if ((first & 0b1000'0000) != 0) {
129       PW_TRY_ASSIGN(int index, HpackIntegerDecode(input, 7));
130       // RFC 7541 Appendix A: these are the only static table entries for :path.
131       if (index == 4) {
132         return "/";
133       }
134       if (index == 5) {
135         return "/index.html";
136       }
137       continue;
138     }
139 
140     // RFC 7541 §6.3: dynamic table size update
141     if ((first & 0b1110'0000) == 0b0010'0000) {
142       // Ignore: we don't use the dynamic table.
143       PW_TRY(HpackIntegerDecode(input, 5));
144       continue;
145     }
146 
147     // RFC 7541 §6.2
148     int index;
149     if ((first & 0b1100'0000) == 0b0100'0000) {
150       PW_TRY_ASSIGN(index, HpackIntegerDecode(input, 6));
151     } else {
152       PW_CHECK((first & 0b1111'0000) == 0b0000'0000 ||
153                (first & 0b1111'0000) == 0b0001'0000);
154       PW_TRY_ASSIGN(index, HpackIntegerDecode(input, 4));
155     }
156 
157     // Check if the name is ":path".
158     bool is_path;
159     if (index == 0) {
160       PW_TRY_ASSIGN(auto name, HpackStringDecode(input));
161       is_path = (name == ":path");
162     } else {
163       // RFC 7541 Appendix A: these are the only static table entries for :path.
164       is_path = (index == 4 || index == 5);
165     }
166 
167     // Always extract the value to advance the `input` span.
168     PW_TRY_ASSIGN(auto value, HpackStringDecode(input));
169     if (is_path) {
170       return value;
171     }
172   }
173 
174   return Status::NotFound();
175 }
176 
ResponseHeadersPayload()177 ConstByteSpan ResponseHeadersPayload() {
178   return as_bytes(span{kResponseHeaderFields});
179 }
180 
ResponseTrailersPayload(Status response_code)181 ConstByteSpan ResponseTrailersPayload(Status response_code) {
182   PW_CHECK_UINT_LT(response_code.code(), kResponseTrailerFields.size());
183   auto* payload = &kResponseTrailerFields[response_code.code()];
184   return as_bytes(span{payload->bytes}.subspan(0, payload->size));
185 }
186 
187 }  // namespace pw::grpc
188