| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ |
| #define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ |
| |
| // HpackStringDecoder decodes strings encoded per the HPACK spec; this does |
| // not mean decompressing Huffman encoded strings, just identifying the length, |
| // encoding and contents for a listener. |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <string> |
| |
| #include "absl/base/macros.h" |
| #include "http2/decoder/decode_buffer.h" |
| #include "http2/decoder/decode_status.h" |
| #include "http2/hpack/varint/hpack_varint_decoder.h" |
| #include "http2/platform/api/http2_logging.h" |
| #include "common/platform/api/quiche_export.h" |
| |
| namespace http2 { |
| |
| // Decodes a single string in an HPACK header entry. The high order bit of |
| // the first byte of the length is the H (Huffman) bit indicating whether |
| // the value is Huffman encoded, and the remainder of the byte is the first |
| // 7 bits of an HPACK varint. |
| // |
| // Call Start() to begin decoding; if it returns kDecodeInProgress, then call |
| // Resume() when more input is available, repeating until kDecodeInProgress is |
| // not returned. If kDecodeDone or kDecodeError is returned, then Resume() must |
| // not be called until Start() has been called to start decoding a new string. |
| class QUICHE_EXPORT_PRIVATE HpackStringDecoder { |
| public: |
| enum StringDecoderState { |
| kStartDecodingLength, |
| kDecodingString, |
| kResumeDecodingLength, |
| }; |
| |
| template <class Listener> |
| DecodeStatus Start(DecodeBuffer* db, Listener* cb) { |
| // Fast decode path is used if the string is under 127 bytes and the |
| // entire length of the string is in the decode buffer. More than 83% of |
| // string lengths are encoded in just one byte. |
| if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) { |
| // The string is short. |
| uint8_t h_and_prefix = db->DecodeUInt8(); |
| uint8_t length = h_and_prefix & 0x7f; |
| bool huffman_encoded = (h_and_prefix & 0x80) == 0x80; |
| cb->OnStringStart(huffman_encoded, length); |
| if (length <= db->Remaining()) { |
| // Yeah, we've got the whole thing in the decode buffer. |
| // Ideally this will be the common case. Note that we don't |
| // update any of the member variables in this path. |
| cb->OnStringData(db->cursor(), length); |
| db->AdvanceCursor(length); |
| cb->OnStringEnd(); |
| return DecodeStatus::kDecodeDone; |
| } |
| // Not all in the buffer. |
| huffman_encoded_ = huffman_encoded; |
| remaining_ = length; |
| // Call Resume to decode the string body, which is only partially |
| // in the decode buffer (or not at all). |
| state_ = kDecodingString; |
| return Resume(db, cb); |
| } |
| // Call Resume to decode the string length, which is either not in |
| // the decode buffer, or spans multiple bytes. |
| state_ = kStartDecodingLength; |
| return Resume(db, cb); |
| } |
| |
| template <class Listener> |
| DecodeStatus Resume(DecodeBuffer* db, Listener* cb) { |
| DecodeStatus status; |
| while (true) { |
| switch (state_) { |
| case kStartDecodingLength: |
| HTTP2_DVLOG(2) << "kStartDecodingLength: db->Remaining=" |
| << db->Remaining(); |
| if (!StartDecodingLength(db, cb, &status)) { |
| // The length is split across decode buffers. |
| return status; |
| } |
| // We've finished decoding the length, which spanned one or more |
| // bytes. Approximately 17% of strings have a length that is greater |
| // than 126 bytes, and thus the length is encoded in more than one |
| // byte, and so doesn't get the benefit of the optimization in |
| // Start() for single byte lengths. But, we still expect that most |
| // of such strings will be contained entirely in a single decode |
| // buffer, and hence this fall through skips another trip through the |
| // switch above and more importantly skips setting the state_ variable |
| // again in those cases where we don't need it. |
| ABSL_FALLTHROUGH_INTENDED; |
| |
| case kDecodingString: |
| HTTP2_DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining() |
| << " remaining_=" << remaining_; |
| return DecodeString(db, cb); |
| |
| case kResumeDecodingLength: |
| HTTP2_DVLOG(2) << "kResumeDecodingLength: db->Remaining=" |
| << db->Remaining(); |
| if (!ResumeDecodingLength(db, cb, &status)) { |
| return status; |
| } |
| } |
| } |
| } |
| |
| std::string DebugString() const; |
| |
| private: |
| static std::string StateToString(StringDecoderState v); |
| |
| // Returns true if the length is fully decoded and the listener wants the |
| // decoding to continue, false otherwise; status is set to the status from |
| // the varint decoder. |
| // If the length is not fully decoded, case state_ is set appropriately |
| // for the next call to Resume. |
| template <class Listener> |
| bool StartDecodingLength(DecodeBuffer* db, |
| Listener* cb, |
| DecodeStatus* status) { |
| if (db->Empty()) { |
| *status = DecodeStatus::kDecodeInProgress; |
| state_ = kStartDecodingLength; |
| return false; |
| } |
| uint8_t h_and_prefix = db->DecodeUInt8(); |
| huffman_encoded_ = (h_and_prefix & 0x80) == 0x80; |
| *status = length_decoder_.Start(h_and_prefix, 7, db); |
| if (*status == DecodeStatus::kDecodeDone) { |
| OnStringStart(cb, status); |
| return true; |
| } |
| // Set the state to cover the DecodeStatus::kDecodeInProgress case. |
| // Won't be needed if the status is kDecodeError. |
| state_ = kResumeDecodingLength; |
| return false; |
| } |
| |
| // Returns true if the length is fully decoded and the listener wants the |
| // decoding to continue, false otherwise; status is set to the status from |
| // the varint decoder; state_ is updated when fully decoded. |
| // If the length is not fully decoded, case state_ is set appropriately |
| // for the next call to Resume. |
| template <class Listener> |
| bool ResumeDecodingLength(DecodeBuffer* db, |
| Listener* cb, |
| DecodeStatus* status) { |
| QUICHE_DCHECK_EQ(state_, kResumeDecodingLength); |
| *status = length_decoder_.Resume(db); |
| if (*status == DecodeStatus::kDecodeDone) { |
| state_ = kDecodingString; |
| OnStringStart(cb, status); |
| return true; |
| } |
| return false; |
| } |
| |
| // Returns true if the listener wants the decoding to continue, and |
| // false otherwise, in which case status set. |
| template <class Listener> |
| void OnStringStart(Listener* cb, DecodeStatus* /*status*/) { |
| // TODO(vasilvv): fail explicitly in case of truncation. |
| remaining_ = static_cast<size_t>(length_decoder_.value()); |
| // Make callback so consumer knows what is coming. |
| cb->OnStringStart(huffman_encoded_, remaining_); |
| } |
| |
| // Passes the available portion of the string to the listener, and signals |
| // the end of the string when it is reached. Returns kDecodeDone or |
| // kDecodeInProgress as appropriate. |
| template <class Listener> |
| DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) { |
| size_t len = std::min(remaining_, db->Remaining()); |
| if (len > 0) { |
| cb->OnStringData(db->cursor(), len); |
| db->AdvanceCursor(len); |
| remaining_ -= len; |
| } |
| if (remaining_ == 0) { |
| cb->OnStringEnd(); |
| return DecodeStatus::kDecodeDone; |
| } |
| state_ = kDecodingString; |
| return DecodeStatus::kDecodeInProgress; |
| } |
| |
| HpackVarintDecoder length_decoder_; |
| |
| // These fields are initialized just to keep ASAN happy about reading |
| // them from DebugString(). |
| size_t remaining_ = 0; |
| StringDecoderState state_ = kStartDecodingLength; |
| bool huffman_encoded_ = false; |
| }; |
| |
| QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, |
| const HpackStringDecoder& v); |
| |
| } // namespace http2 |
| #endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ |