// 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_DECODER_TABLES_H_
#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_

// Static and dynamic tables for the HPACK decoder. See:
// http://httpwg.org/specs/rfc7541.html#indexing.tables

// Note that the Lookup methods return nullptr if the requested index was not
// found. This should be treated as a COMPRESSION error according to the HTTP/2
// spec, which is a connection level protocol error (i.e. the connection must
// be terminated). See these sections in the two RFCs:
// http://httpwg.org/specs/rfc7541.html#indexed.header.representation
// http://httpwg.org/specs/rfc7541.html#index.address.space
// http://httpwg.org/specs/rfc7540.html#HeaderBlock

#include <stddef.h>

#include <cstdint>
#include <vector>

#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_containers.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"

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

// HpackDecoderTablesDebugListener supports a QUIC experiment, enabling
// the gathering of information about the time-line of use of HPACK
// dynamic table entries.
class HTTP2_EXPORT_PRIVATE HpackDecoderTablesDebugListener {
 public:
  HpackDecoderTablesDebugListener();
  virtual ~HpackDecoderTablesDebugListener();

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

  // The entry has been inserted into the dynamic table. insert_count starts at
  // 62 because 61 is the last index in the static table; insert_count increases
  // by 1 with each insert into the dynamic table; it is not incremented when
  // when a entry is too large to fit into the dynamic table at all (which has
  // the effect of emptying the dynamic table).
  // Returns a value that can be used as time_added in OnUseEntry.
  virtual int64_t OnEntryInserted(const HpackStringPair& entry,
                                  size_t insert_count) = 0;

  // The entry has been used, either for the name or for the name and value.
  // insert_count is the same as passed to OnEntryInserted when entry was
  // inserted to the dynamic table, and time_added is the value that was
  // returned by OnEntryInserted.
  virtual void OnUseEntry(const HpackStringPair& entry,
                          size_t insert_count,
                          int64_t time_added) = 0;
};

// See http://httpwg.org/specs/rfc7541.html#static.table.definition for the
// contents, and http://httpwg.org/specs/rfc7541.html#index.address.space for
// info about accessing the static table.
class HTTP2_EXPORT_PRIVATE HpackDecoderStaticTable {
 public:
  explicit HpackDecoderStaticTable(const std::vector<HpackStringPair>* table);
  // Uses a global table shared by all threads.
  HpackDecoderStaticTable();

  // If index is valid, returns a pointer to the entry, otherwise returns
  // nullptr.
  const HpackStringPair* Lookup(size_t index) const;

 private:
  friend class test::HpackDecoderTablesPeer;
  const std::vector<HpackStringPair>* const table_;
};

// HpackDecoderDynamicTable implements HPACK compression feature "indexed
// headers"; previously sent headers may be referenced later by their index
// in the dynamic table. See these sections of the RFC:
//   http://httpwg.org/specs/rfc7541.html#dynamic.table
//   http://httpwg.org/specs/rfc7541.html#dynamic.table.management
class HTTP2_EXPORT_PRIVATE HpackDecoderDynamicTable {
 public:
  HpackDecoderDynamicTable();
  ~HpackDecoderDynamicTable();

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

  // Set the listener to be notified of insertions into this table, and later
  // uses of those entries. Added for evaluation of changes to QUIC's use
  // of HPACK.
  void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener) {
    debug_listener_ = debug_listener;
  }

  // Sets a new size limit, received from the peer; performs evictions if
  // necessary to ensure that the current size does not exceed the new limit.
  // The caller needs to have validated that size_limit does not
  // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
  void DynamicTableSizeUpdate(size_t size_limit);

  // Returns true if inserted, false if too large (at which point the
  // dynamic table will be empty.)
  bool Insert(const HpackString& name, const HpackString& value);

  // If index is valid, returns a pointer to the entry, otherwise returns
  // nullptr.
  const HpackStringPair* Lookup(size_t index) const;

  size_t size_limit() const { return size_limit_; }
  size_t current_size() const { return current_size_; }

 private:
  friend class test::HpackDecoderTablesPeer;
  struct HpackDecoderTableEntry : public HpackStringPair {
    HpackDecoderTableEntry(const HpackString& name, const HpackString& value);
    int64_t time_added;
  };

  // Drop older entries to ensure the size is not greater than limit.
  void EnsureSizeNoMoreThan(size_t limit);

  // Removes the oldest dynamic table entry.
  void RemoveLastEntry();

  Http2Deque<HpackDecoderTableEntry> table_;

  // The last received DynamicTableSizeUpdate value, initialized to
  // SETTINGS_HEADER_TABLE_SIZE.
  size_t size_limit_ = Http2SettingsInfo::DefaultHeaderTableSize();

  size_t current_size_ = 0;

  // insert_count_ and debug_listener_ are used by a QUIC experiment; remove
  // when the experiment is done.
  size_t insert_count_;
  HpackDecoderTablesDebugListener* debug_listener_;
};

class HTTP2_EXPORT_PRIVATE HpackDecoderTables {
 public:
  HpackDecoderTables();
  ~HpackDecoderTables();

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

  // Set the listener to be notified of insertions into the dynamic table, and
  // later uses of those entries. Added for evaluation of changes to QUIC's use
  // of HPACK.
  void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener);

  // Sets a new size limit, received from the peer; performs evictions if
  // necessary to ensure that the current size does not exceed the new limit.
  // The caller needs to have validated that size_limit does not
  // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
  void DynamicTableSizeUpdate(size_t size_limit) {
    dynamic_table_.DynamicTableSizeUpdate(size_limit);
  }

  // Returns true if inserted, false if too large (at which point the
  // dynamic table will be empty.)
  // TODO(jamessynge): Add methods for moving the string(s) into the table,
  // or for otherwise avoiding unnecessary copies.
  bool Insert(const HpackString& name, const HpackString& value) {
    return dynamic_table_.Insert(name, value);
  }

  // If index is valid, returns a pointer to the entry, otherwise returns
  // nullptr.
  const HpackStringPair* Lookup(size_t index) const;

  // The size limit that the peer (the HPACK encoder) has told the decoder it is
  // currently operating with. Defaults to SETTINGS_HEADER_TABLE_SIZE, 4096.
  size_t header_table_size_limit() const { return dynamic_table_.size_limit(); }

  // Sum of the sizes of the dynamic table entries.
  size_t current_header_table_size() const {
    return dynamic_table_.current_size();
  }

 private:
  friend class test::HpackDecoderTablesPeer;
  HpackDecoderStaticTable static_table_;
  HpackDecoderDynamicTable dynamic_table_;
};

}  // namespace http2

#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
