| // 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 "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 "http2/platform/api/http2_macros.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. | |
| HTTP2_FALLTHROUGH; | |
| 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_ |