text: // 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
  
  #include "base/logging.h"
  #include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
  #include "net/third_party/quiche/src/http2/http2_constants.h"
  #include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
  
  namespace http2 {
  namespace {
  
  HpackString ExtractHpackString(HpackDecoderStringBuffer* string_buffer) {
    if (string_buffer->IsBuffered()) {
      return HpackString(string_buffer->ReleaseString());
    } else {
      auto result = HpackString(string_buffer->str());
      string_buffer->Reset();
      return result;
    }
  }
  
  }  // namespace
  
  HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener)
      : listener_(HTTP2_DIE_IF_NULL(listener)),
        final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()),
        lowest_header_table_size_(final_header_table_size_),
        require_dynamic_table_size_update_(false),
        allow_dynamic_table_size_update_(true),
        saw_dynamic_table_size_update_(false),
        error_detected_(false) {}
  HpackDecoderState::~HpackDecoderState() = default;
  
  void HpackDecoderState::set_tables_debug_listener(
      HpackDecoderTablesDebugListener* debug_listener) {
    decoder_tables_.set_debug_listener(debug_listener);
  }
  
  void HpackDecoderState::ApplyHeaderTableSizeSetting(
      uint32_t header_table_size) {
    DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting("
             << header_table_size << ")";
    DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
    if (header_table_size < lowest_header_table_size_) {
      lowest_header_table_size_ = header_table_size;
    }
    final_header_table_size_ = header_table_size;
    DVLOG(2) << "low water mark: " << lowest_header_table_size_;
    DVLOG(2) << "final limit: " << final_header_table_size_;
  }
  
  // Called to notify this object that we're starting to decode an HPACK block
  // (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded).
  void HpackDecoderState::OnHeaderBlockStart() {
    DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart";
    // This instance can't be reused after an error has been detected, as we must
    // assume that the encoder and decoder compression states are no longer
    // synchronized.
    DCHECK(!error_detected_);
    DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
    allow_dynamic_table_size_update_ = true;
    saw_dynamic_table_size_update_ = false;
    // If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which
    // its HPACK encoder has been using, then the next HPACK block it sends MUST
    // start with a Dynamic Table Size Update entry that is at least as low as
    // lowest_header_table_size_. That may be followed by another as great as
    // final_header_table_size_, if those are different.
    require_dynamic_table_size_update_ =
        (lowest_header_table_size_ <
             decoder_tables_.current_header_table_size() ||
         final_header_table_size_ < decoder_tables_.header_table_size_limit());
    DVLOG(2) << "HpackDecoderState::OnHeaderListStart "
             << "require_dynamic_table_size_update_="
             << require_dynamic_table_size_update_;
    listener_->OnHeaderListStart();
  }
  
  void HpackDecoderState::OnIndexedHeader(size_t index) {
    DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index;
    if (error_detected_) {
      return;
    }
    if (require_dynamic_table_size_update_) {
      ReportError("Missing dynamic table size update.");
      return;
    }
    allow_dynamic_table_size_update_ = false;
    const HpackStringPair* entry = decoder_tables_.Lookup(index);
    if (entry != nullptr) {
      listener_->OnHeader(HpackEntryType::kIndexedHeader, entry->name,
                          entry->value);
    } else {
      ReportError("Invalid index.");
    }
  }
  
  void HpackDecoderState::OnNameIndexAndLiteralValue(
      HpackEntryType entry_type,
      size_t name_index,
      HpackDecoderStringBuffer* value_buffer) {
    DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue " << entry_type
             << ", " << name_index << ", " << value_buffer->str();
    if (error_detected_) {
      return;
    }
    if (require_dynamic_table_size_update_) {
      ReportError("Missing dynamic table size update.");
      return;
    }
    allow_dynamic_table_size_update_ = false;
    const HpackStringPair* entry = decoder_tables_.Lookup(name_index);
    if (entry != nullptr) {
      HpackString value(ExtractHpackString(value_buffer));
      listener_->OnHeader(entry_type, entry->name, value);
      if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
        decoder_tables_.Insert(entry->name, value);
      }
    } else {
      ReportError("Invalid name index.");
    }
  }
  
  void HpackDecoderState::OnLiteralNameAndValue(
      HpackEntryType entry_type,
      HpackDecoderStringBuffer* name_buffer,
      HpackDecoderStringBuffer* value_buffer) {
    DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type << ", "
             << name_buffer->str() << ", " << value_buffer->str();
    if (error_detected_) {
      return;
    }
    if (require_dynamic_table_size_update_) {
      ReportError("Missing dynamic table size update.");
      return;
    }
    allow_dynamic_table_size_update_ = false;
    HpackString name(ExtractHpackString(name_buffer));
    HpackString value(ExtractHpackString(value_buffer));
    listener_->OnHeader(entry_type, name, value);
    if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
      decoder_tables_.Insert(name, value);
    }
  }
  
  void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) {
    DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate " << size_limit
             << ", required="
             << (require_dynamic_table_size_update_ ? "true" : "false")
             << ", allowed="
             << (allow_dynamic_table_size_update_ ? "true" : "false");
    if (error_detected_) {
      return;
    }
    DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
    if (!allow_dynamic_table_size_update_) {
      // At most two dynamic table size updates allowed at the start, and not
      // after a header.
      ReportError("Dynamic table size update not allowed.");
      return;
    }
    if (require_dynamic_table_size_update_) {
      // The new size must not be greater than the low water mark.
      if (size_limit > lowest_header_table_size_) {
        ReportError("Initial dynamic table size update is above low water mark.");
        return;
      }
      require_dynamic_table_size_update_ = false;
    } else if (size_limit > final_header_table_size_) {
      // The new size must not be greater than the final max header table size
      // that the peer acknowledged.
      ReportError("Dynamic table size update is above acknowledged setting.");
      return;
    }
    decoder_tables_.DynamicTableSizeUpdate(size_limit);
    if (saw_dynamic_table_size_update_) {
      allow_dynamic_table_size_update_ = false;
    } else {
      saw_dynamic_table_size_update_ = true;
    }
    // We no longer need to keep an eye out for a lower header table size.
    lowest_header_table_size_ = final_header_table_size_;
  }
  
  void HpackDecoderState::OnHpackDecodeError(Http2StringPiece error_message) {
    DVLOG(2) << "HpackDecoderState::OnHpackDecodeError " << error_message;
    if (!error_detected_) {
      ReportError(error_message);
    }
  }
  
  void HpackDecoderState::OnHeaderBlockEnd() {
    DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd";
    if (error_detected_) {
      return;
    }
    if (require_dynamic_table_size_update_) {
      // Apparently the HPACK block was empty, but we needed it to contain at
      // least 1 dynamic table size update.
      ReportError("Missing dynamic table size update.");
    } else {
      listener_->OnHeaderListEnd();
    }
  }
  
  void HpackDecoderState::ReportError(Http2StringPiece error_message) {
    DVLOG(2) << "HpackDecoderState::ReportError is new="
             << (!error_detected_ ? "true" : "false")
             << ", error_message: " << error_message;
    if (!error_detected_) {
      listener_->OnHeaderErrorDetected(error_message);
      error_detected_ = true;
    }
  }
  
  }  // namespace http2
  