// 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.

// HpackDecoderState maintains the HPACK decompressor state; i.e. updates the
// HPACK dynamic table according to RFC 7541 as the entries in an HPACK block
// are decoded, and reads from the static and dynamic tables in order to build
// complete header entries. Calls an HpackDecoderListener with the completely
// decoded headers (i.e. after resolving table indices into names or values),
// thus translating the decoded HPACK entries into HTTP/2 headers.

#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_

#include <stddef.h>

#include <cstdint>

#include "absl/strings/string_view.h"
#include "quiche/http2/hpack/decoder/hpack_decoder_listener.h"
#include "quiche/http2/hpack/decoder/hpack_decoder_string_buffer.h"
#include "quiche/http2/hpack/decoder/hpack_decoder_tables.h"
#include "quiche/http2/hpack/decoder/hpack_decoding_error.h"
#include "quiche/http2/hpack/decoder/hpack_whole_entry_listener.h"
#include "quiche/http2/hpack/http2_hpack_constants.h"
#include "quiche/common/platform/api/quiche_export.h"

namespace http2 {
namespace test {
class HpackDecoderStatePeer;
}  // namespace test

class QUICHE_EXPORT HpackDecoderState : public HpackWholeEntryListener {
 public:
  explicit HpackDecoderState(HpackDecoderListener* listener);
  ~HpackDecoderState() override;

  HpackDecoderState(const HpackDecoderState&) = delete;
  HpackDecoderState& operator=(const HpackDecoderState&) = delete;

  // Set the listener to be notified when a whole entry has been decoded,
  // including resolving name or name and value references.
  // The listener may be changed at any time.
  HpackDecoderListener* listener() const { return listener_; }

  // ApplyHeaderTableSizeSetting notifies this object that this endpoint has
  // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from
  // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the
  // maximum size of the dynamic table that this endpoint will use to decode
  // HPACK blocks).
  // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values,
  // the caller must keep track of those multiple changes, and make
  // corresponding calls to this method. In particular, a call must be made
  // with the lowest value acknowledged by the peer, and a call must be made
  // with the final value acknowledged, in that order; additional calls may
  // be made if additional values were sent. These calls must be made between
  // decoding the SETTINGS ACK, and before the next HPACK block is decoded.
  void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size);

  // Returns the most recently applied value of SETTINGS_HEADER_TABLE_SIZE.
  size_t GetCurrentHeaderTableSizeSetting() const {
    return final_header_table_size_;
  }

  // OnHeaderBlockStart notifies this object that we're starting to decode the
  // HPACK payload of a HEADERS or PUSH_PROMISE frame.
  void OnHeaderBlockStart();

  // Implement the HpackWholeEntryListener methods, each of which notifies this
  // object when an entire entry has been decoded.
  void OnIndexedHeader(size_t index) override;
  void OnNameIndexAndLiteralValue(
      HpackEntryType entry_type, size_t name_index,
      HpackDecoderStringBuffer* value_buffer) override;
  void OnLiteralNameAndValue(HpackEntryType entry_type,
                             HpackDecoderStringBuffer* name_buffer,
                             HpackDecoderStringBuffer* value_buffer) override;
  void OnDynamicTableSizeUpdate(size_t size) override;
  void OnHpackDecodeError(HpackDecodingError error,
                          std::string detailed_error) override;

  // OnHeaderBlockEnd notifies this object that an entire HPACK block has been
  // decoded, which might have extended into CONTINUATION blocks.
  void OnHeaderBlockEnd();

  // Returns error code after an error has been detected and reported.
  // No further callbacks will be made to the listener.
  HpackDecodingError error() const { return error_; }

  size_t GetDynamicTableSize() const {
    return decoder_tables_.current_header_table_size();
  }

  const HpackDecoderTables& decoder_tables_for_test() const {
    return decoder_tables_;
  }

  std::string detailed_error() const { return detailed_error_; }

 private:
  friend class test::HpackDecoderStatePeer;

  // Reports an error to the listener IF this is the first error detected.
  void ReportError(HpackDecodingError error, std::string detailed_error);

  // The static and dynamic HPACK tables.
  HpackDecoderTables decoder_tables_;

  // The listener to be notified of headers, the start and end of header
  // lists, and of errors.
  HpackDecoderListener* listener_;

  // The most recent HEADER_TABLE_SIZE setting acknowledged by the peer.
  uint32_t final_header_table_size_;

  // The lowest HEADER_TABLE_SIZE setting acknowledged by the peer; valid until
  // the next HPACK block is decoded.
  // TODO(jamessynge): Test raising the HEADER_TABLE_SIZE.
  uint32_t lowest_header_table_size_;

  // Must the next (first) HPACK entry be a dynamic table size update?
  bool require_dynamic_table_size_update_;

  // May the next (first or second) HPACK entry be a dynamic table size update?
  bool allow_dynamic_table_size_update_;

  // Have we already seen a dynamic table size update in this HPACK block?
  bool saw_dynamic_table_size_update_;

  // Has an error already been detected and reported to the listener?
  HpackDecodingError error_;
  std::string detailed_error_;
};

}  // namespace http2

#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
