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