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