blob: b861d23e63ff25539072e82346b36e08831c56b3 [file] [log] [blame]
QUICHE teamfd50a402018-12-07 22:54:05 -05001// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
6#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
7
8// HpackStringDecoder decodes strings encoded per the HPACK spec; this does
9// not mean decompressing Huffman encoded strings, just identifying the length,
10// encoding and contents for a listener.
11
12#include <stddef.h>
13
14#include <algorithm>
15#include <cstdint>
bnc47904002019-08-16 11:49:48 -070016#include <string>
QUICHE teamfd50a402018-12-07 22:54:05 -050017
QUICHE team5be974e2020-12-29 18:35:24 -050018#include "http2/decoder/decode_buffer.h"
19#include "http2/decoder/decode_status.h"
20#include "http2/hpack/varint/hpack_varint_decoder.h"
21#include "http2/platform/api/http2_logging.h"
22#include "http2/platform/api/http2_macros.h"
23#include "common/platform/api/quiche_export.h"
QUICHE teamfd50a402018-12-07 22:54:05 -050024
25namespace http2 {
26
27// Decodes a single string in an HPACK header entry. The high order bit of
28// the first byte of the length is the H (Huffman) bit indicating whether
29// the value is Huffman encoded, and the remainder of the byte is the first
30// 7 bits of an HPACK varint.
31//
32// Call Start() to begin decoding; if it returns kDecodeInProgress, then call
33// Resume() when more input is available, repeating until kDecodeInProgress is
34// not returned. If kDecodeDone or kDecodeError is returned, then Resume() must
35// not be called until Start() has been called to start decoding a new string.
bnc641ace72020-01-21 12:24:57 -080036class QUICHE_EXPORT_PRIVATE HpackStringDecoder {
QUICHE teamfd50a402018-12-07 22:54:05 -050037 public:
38 enum StringDecoderState {
39 kStartDecodingLength,
40 kDecodingString,
41 kResumeDecodingLength,
42 };
43
44 template <class Listener>
45 DecodeStatus Start(DecodeBuffer* db, Listener* cb) {
46 // Fast decode path is used if the string is under 127 bytes and the
47 // entire length of the string is in the decode buffer. More than 83% of
48 // string lengths are encoded in just one byte.
49 if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) {
50 // The string is short.
51 uint8_t h_and_prefix = db->DecodeUInt8();
52 uint8_t length = h_and_prefix & 0x7f;
53 bool huffman_encoded = (h_and_prefix & 0x80) == 0x80;
54 cb->OnStringStart(huffman_encoded, length);
55 if (length <= db->Remaining()) {
56 // Yeah, we've got the whole thing in the decode buffer.
57 // Ideally this will be the common case. Note that we don't
58 // update any of the member variables in this path.
59 cb->OnStringData(db->cursor(), length);
60 db->AdvanceCursor(length);
61 cb->OnStringEnd();
62 return DecodeStatus::kDecodeDone;
63 }
64 // Not all in the buffer.
65 huffman_encoded_ = huffman_encoded;
66 remaining_ = length;
67 // Call Resume to decode the string body, which is only partially
68 // in the decode buffer (or not at all).
69 state_ = kDecodingString;
70 return Resume(db, cb);
71 }
72 // Call Resume to decode the string length, which is either not in
73 // the decode buffer, or spans multiple bytes.
74 state_ = kStartDecodingLength;
75 return Resume(db, cb);
76 }
77
78 template <class Listener>
79 DecodeStatus Resume(DecodeBuffer* db, Listener* cb) {
80 DecodeStatus status;
81 while (true) {
82 switch (state_) {
83 case kStartDecodingLength:
QUICHE team61940b42019-03-07 23:32:27 -050084 HTTP2_DVLOG(2) << "kStartDecodingLength: db->Remaining="
85 << db->Remaining();
QUICHE teamfd50a402018-12-07 22:54:05 -050086 if (!StartDecodingLength(db, cb, &status)) {
87 // The length is split across decode buffers.
88 return status;
89 }
90 // We've finished decoding the length, which spanned one or more
91 // bytes. Approximately 17% of strings have a length that is greater
92 // than 126 bytes, and thus the length is encoded in more than one
93 // byte, and so doesn't get the benefit of the optimization in
94 // Start() for single byte lengths. But, we still expect that most
95 // of such strings will be contained entirely in a single decode
96 // buffer, and hence this fall through skips another trip through the
97 // switch above and more importantly skips setting the state_ variable
98 // again in those cases where we don't need it.
99 HTTP2_FALLTHROUGH;
100
101 case kDecodingString:
QUICHE team61940b42019-03-07 23:32:27 -0500102 HTTP2_DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining()
103 << " remaining_=" << remaining_;
QUICHE teamfd50a402018-12-07 22:54:05 -0500104 return DecodeString(db, cb);
105
106 case kResumeDecodingLength:
QUICHE team61940b42019-03-07 23:32:27 -0500107 HTTP2_DVLOG(2) << "kResumeDecodingLength: db->Remaining="
108 << db->Remaining();
QUICHE teamfd50a402018-12-07 22:54:05 -0500109 if (!ResumeDecodingLength(db, cb, &status)) {
110 return status;
111 }
112 }
113 }
114 }
115
bnc47904002019-08-16 11:49:48 -0700116 std::string DebugString() const;
QUICHE teamfd50a402018-12-07 22:54:05 -0500117
118 private:
bnc47904002019-08-16 11:49:48 -0700119 static std::string StateToString(StringDecoderState v);
QUICHE teamfd50a402018-12-07 22:54:05 -0500120
121 // Returns true if the length is fully decoded and the listener wants the
122 // decoding to continue, false otherwise; status is set to the status from
123 // the varint decoder.
124 // If the length is not fully decoded, case state_ is set appropriately
125 // for the next call to Resume.
126 template <class Listener>
127 bool StartDecodingLength(DecodeBuffer* db,
128 Listener* cb,
129 DecodeStatus* status) {
130 if (db->Empty()) {
131 *status = DecodeStatus::kDecodeInProgress;
132 state_ = kStartDecodingLength;
133 return false;
134 }
135 uint8_t h_and_prefix = db->DecodeUInt8();
136 huffman_encoded_ = (h_and_prefix & 0x80) == 0x80;
137 *status = length_decoder_.Start(h_and_prefix, 7, db);
138 if (*status == DecodeStatus::kDecodeDone) {
139 OnStringStart(cb, status);
140 return true;
141 }
142 // Set the state to cover the DecodeStatus::kDecodeInProgress case.
143 // Won't be needed if the status is kDecodeError.
144 state_ = kResumeDecodingLength;
145 return false;
146 }
147
148 // Returns true if the length is fully decoded and the listener wants the
149 // decoding to continue, false otherwise; status is set to the status from
150 // the varint decoder; state_ is updated when fully decoded.
151 // If the length is not fully decoded, case state_ is set appropriately
152 // for the next call to Resume.
153 template <class Listener>
154 bool ResumeDecodingLength(DecodeBuffer* db,
155 Listener* cb,
156 DecodeStatus* status) {
vasilvvafcc3172021-02-02 12:01:07 -0800157 QUICHE_DCHECK_EQ(state_, kResumeDecodingLength);
QUICHE teamfd50a402018-12-07 22:54:05 -0500158 *status = length_decoder_.Resume(db);
159 if (*status == DecodeStatus::kDecodeDone) {
160 state_ = kDecodingString;
161 OnStringStart(cb, status);
162 return true;
163 }
164 return false;
165 }
166
167 // Returns true if the listener wants the decoding to continue, and
168 // false otherwise, in which case status set.
169 template <class Listener>
wub1bcd0c32020-03-26 17:53:37 -0700170 void OnStringStart(Listener* cb, DecodeStatus* /*status*/) {
vasilvvbbe900a2019-01-15 14:22:33 -0500171 // TODO(vasilvv): fail explicitly in case of truncation.
172 remaining_ = static_cast<size_t>(length_decoder_.value());
QUICHE teamfd50a402018-12-07 22:54:05 -0500173 // Make callback so consumer knows what is coming.
174 cb->OnStringStart(huffman_encoded_, remaining_);
175 }
176
177 // Passes the available portion of the string to the listener, and signals
178 // the end of the string when it is reached. Returns kDecodeDone or
179 // kDecodeInProgress as appropriate.
180 template <class Listener>
181 DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) {
182 size_t len = std::min(remaining_, db->Remaining());
183 if (len > 0) {
184 cb->OnStringData(db->cursor(), len);
185 db->AdvanceCursor(len);
186 remaining_ -= len;
187 }
188 if (remaining_ == 0) {
189 cb->OnStringEnd();
190 return DecodeStatus::kDecodeDone;
191 }
192 state_ = kDecodingString;
193 return DecodeStatus::kDecodeInProgress;
194 }
195
196 HpackVarintDecoder length_decoder_;
197
198 // These fields are initialized just to keep ASAN happy about reading
199 // them from DebugString().
200 size_t remaining_ = 0;
201 StringDecoderState state_ = kStartDecodingLength;
202 bool huffman_encoded_ = false;
203};
204
bnc641ace72020-01-21 12:24:57 -0800205QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
206 const HpackStringDecoder& v);
QUICHE teamfd50a402018-12-07 22:54:05 -0500207
208} // namespace http2
209#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_