| // 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. |
| |
| #include "http2/hpack/decoder/hpack_decoder_string_buffer.h" |
| |
| #include <utility> |
| |
| #include "http2/platform/api/http2_bug_tracker.h" |
| #include "http2/platform/api/http2_logging.h" |
| |
| namespace http2 { |
| |
| std::ostream& operator<<(std::ostream& out, |
| const HpackDecoderStringBuffer::State v) { |
| switch (v) { |
| case HpackDecoderStringBuffer::State::RESET: |
| return out << "RESET"; |
| case HpackDecoderStringBuffer::State::COLLECTING: |
| return out << "COLLECTING"; |
| case HpackDecoderStringBuffer::State::COMPLETE: |
| return out << "COMPLETE"; |
| } |
| // Since the value doesn't come over the wire, only a programming bug should |
| // result in reaching this point. |
| int unknown = static_cast<int>(v); |
| HTTP2_BUG(http2_bug_50_1) |
| << "Invalid HpackDecoderStringBuffer::State: " << unknown; |
| return out << "HpackDecoderStringBuffer::State(" << unknown << ")"; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, |
| const HpackDecoderStringBuffer::Backing v) { |
| switch (v) { |
| case HpackDecoderStringBuffer::Backing::RESET: |
| return out << "RESET"; |
| case HpackDecoderStringBuffer::Backing::UNBUFFERED: |
| return out << "UNBUFFERED"; |
| case HpackDecoderStringBuffer::Backing::BUFFERED: |
| return out << "BUFFERED"; |
| case HpackDecoderStringBuffer::Backing::STATIC: |
| return out << "STATIC"; |
| } |
| // Since the value doesn't come over the wire, only a programming bug should |
| // result in reaching this point. |
| auto v2 = static_cast<int>(v); |
| HTTP2_BUG(http2_bug_50_2) |
| << "Invalid HpackDecoderStringBuffer::Backing: " << v2; |
| return out << "HpackDecoderStringBuffer::Backing(" << v2 << ")"; |
| } |
| |
| HpackDecoderStringBuffer::HpackDecoderStringBuffer() |
| : remaining_len_(0), |
| is_huffman_encoded_(false), |
| state_(State::RESET), |
| backing_(Backing::RESET) {} |
| HpackDecoderStringBuffer::~HpackDecoderStringBuffer() = default; |
| |
| void HpackDecoderStringBuffer::Reset() { |
| HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::Reset"; |
| state_ = State::RESET; |
| } |
| |
| void HpackDecoderStringBuffer::Set(absl::string_view value, bool is_static) { |
| HTTP2_DVLOG(2) << "HpackDecoderStringBuffer::Set"; |
| QUICHE_DCHECK_EQ(state_, State::RESET); |
| value_ = value; |
| state_ = State::COMPLETE; |
| backing_ = is_static ? Backing::STATIC : Backing::UNBUFFERED; |
| // TODO(jamessynge): Determine which of these two fields must be set. |
| remaining_len_ = 0; |
| is_huffman_encoded_ = false; |
| } |
| |
| void HpackDecoderStringBuffer::OnStart(bool huffman_encoded, size_t len) { |
| HTTP2_DVLOG(2) << "HpackDecoderStringBuffer::OnStart"; |
| QUICHE_DCHECK_EQ(state_, State::RESET); |
| |
| remaining_len_ = len; |
| is_huffman_encoded_ = huffman_encoded; |
| state_ = State::COLLECTING; |
| |
| if (huffman_encoded) { |
| // We don't set, clear or use value_ for buffered strings until OnEnd. |
| decoder_.Reset(); |
| buffer_.clear(); |
| backing_ = Backing::BUFFERED; |
| |
| // Reserve space in buffer_ for the uncompressed string, assuming the |
| // maximum expansion. The shortest Huffman codes in the RFC are 5 bits long, |
| // which then expand to 8 bits during decoding (i.e. each code is for one |
| // plain text octet, aka byte), so the maximum size is 60% longer than the |
| // encoded size. |
| len = len * 8 / 5; |
| if (buffer_.capacity() < len) { |
| buffer_.reserve(len); |
| } |
| } else { |
| // Assume for now that we won't need to use buffer_, so don't reserve space |
| // in it. |
| backing_ = Backing::RESET; |
| // OnData is not called for empty (zero length) strings, so make sure that |
| // value_ is cleared. |
| value_ = absl::string_view(); |
| } |
| } |
| |
| bool HpackDecoderStringBuffer::OnData(const char* data, size_t len) { |
| HTTP2_DVLOG(2) << "HpackDecoderStringBuffer::OnData state=" << state_ |
| << ", backing=" << backing_; |
| QUICHE_DCHECK_EQ(state_, State::COLLECTING); |
| QUICHE_DCHECK_LE(len, remaining_len_); |
| remaining_len_ -= len; |
| |
| if (is_huffman_encoded_) { |
| QUICHE_DCHECK_EQ(backing_, Backing::BUFFERED); |
| return decoder_.Decode(absl::string_view(data, len), &buffer_); |
| } |
| |
| if (backing_ == Backing::RESET) { |
| // This is the first call to OnData. If data contains the entire string, |
| // don't copy the string. If we later find that the HPACK entry is split |
| // across input buffers, then we'll copy the string into buffer_. |
| if (remaining_len_ == 0) { |
| value_ = absl::string_view(data, len); |
| backing_ = Backing::UNBUFFERED; |
| return true; |
| } |
| |
| // We need to buffer the string because it is split across input buffers. |
| // Reserve space in buffer_ for the entire string. |
| backing_ = Backing::BUFFERED; |
| buffer_.reserve(remaining_len_ + len); |
| buffer_.assign(data, len); |
| return true; |
| } |
| |
| // This is not the first call to OnData for this string, so it should be |
| // buffered. |
| QUICHE_DCHECK_EQ(backing_, Backing::BUFFERED); |
| |
| // Append to the current contents of the buffer. |
| buffer_.append(data, len); |
| return true; |
| } |
| |
| bool HpackDecoderStringBuffer::OnEnd() { |
| HTTP2_DVLOG(2) << "HpackDecoderStringBuffer::OnEnd"; |
| QUICHE_DCHECK_EQ(state_, State::COLLECTING); |
| QUICHE_DCHECK_EQ(0u, remaining_len_); |
| |
| if (is_huffman_encoded_) { |
| QUICHE_DCHECK_EQ(backing_, Backing::BUFFERED); |
| // Did the Huffman encoding of the string end properly? |
| if (!decoder_.InputProperlyTerminated()) { |
| return false; // No, it didn't. |
| } |
| value_ = buffer_; |
| } else if (backing_ == Backing::BUFFERED) { |
| value_ = buffer_; |
| } |
| state_ = State::COMPLETE; |
| return true; |
| } |
| |
| void HpackDecoderStringBuffer::BufferStringIfUnbuffered() { |
| HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::BufferStringIfUnbuffered state=" |
| << state_ << ", backing=" << backing_; |
| if (state_ != State::RESET && backing_ == Backing::UNBUFFERED) { |
| HTTP2_DVLOG(2) |
| << "HpackDecoderStringBuffer buffering std::string of length " |
| << value_.size(); |
| buffer_.assign(value_.data(), value_.size()); |
| if (state_ == State::COMPLETE) { |
| value_ = buffer_; |
| } |
| backing_ = Backing::BUFFERED; |
| } |
| } |
| |
| bool HpackDecoderStringBuffer::IsBuffered() const { |
| HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::IsBuffered"; |
| return state_ != State::RESET && backing_ == Backing::BUFFERED; |
| } |
| |
| size_t HpackDecoderStringBuffer::BufferedLength() const { |
| HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::BufferedLength"; |
| return IsBuffered() ? buffer_.size() : 0; |
| } |
| |
| absl::string_view HpackDecoderStringBuffer::str() const { |
| HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::str"; |
| QUICHE_DCHECK_EQ(state_, State::COMPLETE); |
| return value_; |
| } |
| |
| absl::string_view HpackDecoderStringBuffer::GetStringIfComplete() const { |
| if (state_ != State::COMPLETE) { |
| return {}; |
| } |
| return str(); |
| } |
| |
| std::string HpackDecoderStringBuffer::ReleaseString() { |
| HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::ReleaseString"; |
| QUICHE_DCHECK_EQ(state_, State::COMPLETE); |
| QUICHE_DCHECK_EQ(backing_, Backing::BUFFERED); |
| if (state_ == State::COMPLETE) { |
| state_ = State::RESET; |
| if (backing_ == Backing::BUFFERED) { |
| return std::move(buffer_); |
| } else { |
| return std::string(value_); |
| } |
| } |
| return ""; |
| } |
| |
| void HpackDecoderStringBuffer::OutputDebugStringTo(std::ostream& out) const { |
| out << "{state=" << state_; |
| if (state_ != State::RESET) { |
| out << ", backing=" << backing_; |
| out << ", remaining_len=" << remaining_len_; |
| out << ", is_huffman_encoded=" << is_huffman_encoded_; |
| if (backing_ == Backing::BUFFERED) { |
| out << ", buffer: " << buffer_; |
| } else { |
| out << ", value: " << value_; |
| } |
| } |
| out << "}"; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) { |
| v.OutputDebugStringTo(out); |
| return out; |
| } |
| |
| } // namespace http2 |