#ifndef QUICHE_HTTP2_ADAPTER_OGHTTP2_SESSION_H_
#define QUICHE_HTTP2_ADAPTER_OGHTTP2_SESSION_H_

#include <cstdint>
#include <limits>
#include <list>
#include <memory>
#include <vector>

#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "quiche/http2/adapter/data_source.h"
#include "quiche/http2/adapter/event_forwarder.h"
#include "quiche/http2/adapter/header_validator.h"
#include "quiche/http2/adapter/header_validator_base.h"
#include "quiche/http2/adapter/http2_protocol.h"
#include "quiche/http2/adapter/http2_session.h"
#include "quiche/http2/adapter/http2_util.h"
#include "quiche/http2/adapter/http2_visitor_interface.h"
#include "quiche/http2/adapter/window_manager.h"
#include "quiche/http2/core/http2_trace_logging.h"
#include "quiche/http2/core/priority_write_scheduler.h"
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/platform/api/quiche_flags.h"
#include "quiche/common/quiche_callbacks.h"
#include "quiche/common/quiche_linked_hash_map.h"
#include "quiche/spdy/core/http2_frame_decoder_adapter.h"
#include "quiche/spdy/core/http2_header_block.h"
#include "quiche/spdy/core/no_op_headers_handler.h"
#include "quiche/spdy/core/spdy_framer.h"
#include "quiche/spdy/core/spdy_protocol.h"

namespace http2 {
namespace adapter {

// This class manages state associated with a single multiplexed HTTP/2 session.
class QUICHE_EXPORT OgHttp2Session : public Http2Session,
                                     public spdy::SpdyFramerVisitorInterface {
 public:
  struct QUICHE_EXPORT Options {
    // Returns whether to send a WINDOW_UPDATE based on the window limit, window
    // size, and delta that would be sent in the WINDOW_UPDATE.
    WindowManager::ShouldWindowUpdateFn should_window_update_fn =
        DeltaAtLeastHalfLimit;
    // The perspective of this session.
    Perspective perspective = Perspective::kClient;
    // The maximum HPACK table size to use.
    absl::optional<size_t> max_hpack_encoding_table_capacity = absl::nullopt;
    // The maximum number of decoded header bytes that a stream can receive.
    absl::optional<uint32_t> max_header_list_bytes = absl::nullopt;
    // The maximum size of an individual header field, including name and value.
    absl::optional<uint32_t> max_header_field_size = absl::nullopt;
    // Whether to automatically send PING acks when receiving a PING.
    bool auto_ping_ack = true;
    // Whether (as server) to send a RST_STREAM NO_ERROR when sending a fin on
    // an incomplete stream.
    bool rst_stream_no_error_when_incomplete = false;
    // Whether (as server) to queue trailers until after a stream's data source
    // has indicated the end of data. If false, the server will assume that
    // submitting trailers indicates the end of data.
    bool trailers_require_end_data = false;
    // Whether to mark all input data as consumed upon encountering a connection
    // error while processing bytes. If true, subsequent processing will also
    // mark all input data as consumed.
    bool blackhole_data_on_connection_error = true;
    // Whether to advertise support for the extended CONNECT semantics described
    // in RFC 8441. If true, this endpoint will send the appropriate setting in
    // initial SETTINGS.
    bool allow_extended_connect = true;
    // Whether to allow `obs-text` (characters from hexadecimal 0x80 to 0xff) in
    // header field values.
    bool allow_obs_text = true;
    // If true, validates header field names and values according to RFC 7230
    // and RFC 7540.
    bool validate_http_headers = true;
    // If true, allows the '#' character in request paths, even though this
    // contradicts RFC 3986 Section 3.3.
    // TODO(birenroy): Flip the default value to false.
    bool allow_fragment_in_path = true;
  };

  OgHttp2Session(Http2VisitorInterface& visitor, Options options);
  ~OgHttp2Session() override;

  // Enqueues a frame for transmission to the peer.
  void EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame);

  // Starts a graceful shutdown sequence. No-op if a GOAWAY has already been
  // sent.
  void StartGracefulShutdown();

  // Invokes the visitor's OnReadyToSend() method for serialized frames and
  // DataFrameSource::Send() for data frames.
  int Send();

  int32_t SubmitRequest(absl::Span<const Header> headers,
                        std::unique_ptr<DataFrameSource> data_source,
                        void* user_data);
  int SubmitResponse(Http2StreamId stream_id, absl::Span<const Header> headers,
                     std::unique_ptr<DataFrameSource> data_source);
  int SubmitTrailer(Http2StreamId stream_id, absl::Span<const Header> trailers);
  void SubmitMetadata(Http2StreamId stream_id,
                      std::unique_ptr<MetadataSource> source);
  void SubmitSettings(absl::Span<const Http2Setting> settings);

  bool IsServerSession() const {
    return options_.perspective == Perspective::kServer;
  }
  Http2StreamId GetHighestReceivedStreamId() const {
    return highest_received_stream_id_;
  }
  void SetStreamUserData(Http2StreamId stream_id, void* user_data);
  void* GetStreamUserData(Http2StreamId stream_id);

  // Resumes a stream that was previously blocked. Returns true on success.
  bool ResumeStream(Http2StreamId stream_id);

  // Returns the peer's outstanding stream receive window for the given stream.
  int GetStreamSendWindowSize(Http2StreamId stream_id) const;

  // Returns the current upper bound on the flow control receive window for this
  // stream.
  int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const;

  // Returns the outstanding stream receive window, or -1 if the stream does not
  // exist.
  int GetStreamReceiveWindowSize(Http2StreamId stream_id) const;

  // Returns the outstanding connection receive window.
  int GetReceiveWindowSize() const;

  // Returns the size of the HPACK encoder's dynamic table, including the
  // per-entry overhead from the specification.
  int GetHpackEncoderDynamicTableSize() const;

  // Returns the maximum capacity of the HPACK encoder's dynamic table.
  int GetHpackEncoderDynamicTableCapacity() const;

  // Returns the size of the HPACK decoder's dynamic table, including the
  // per-entry overhead from the specification.
  int GetHpackDecoderDynamicTableSize() const;

  // Returns the size of the HPACK decoder's most recently applied size limit.
  int GetHpackDecoderSizeLimit() const;

  // From Http2Session.
  int64_t ProcessBytes(absl::string_view bytes) override;
  int Consume(Http2StreamId stream_id, size_t num_bytes) override;
  bool want_read() const override {
    return !received_goaway_ && !decoder_.HasError();
  }
  bool want_write() const override {
    return !fatal_send_error_ &&
           (!frames_.empty() || !buffered_data_.empty() || HasReadyStream() ||
            !goaway_rejected_streams_.empty());
  }
  int GetRemoteWindowSize() const override { return connection_send_window_; }
  bool peer_enables_connect_protocol() {
    return peer_enables_connect_protocol_;
  }

  // From SpdyFramerVisitorInterface
  void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
               std::string detailed_error) override;
  void OnCommonHeader(spdy::SpdyStreamId /*stream_id*/, size_t /*length*/,
                      uint8_t /*type*/, uint8_t /*flags*/) override;
  void OnDataFrameHeader(spdy::SpdyStreamId stream_id, size_t length,
                         bool fin) override;
  void OnStreamFrameData(spdy::SpdyStreamId stream_id, const char* data,
                         size_t len) override;
  void OnStreamEnd(spdy::SpdyStreamId stream_id) override;
  void OnStreamPadLength(spdy::SpdyStreamId /*stream_id*/,
                         size_t /*value*/) override;
  void OnStreamPadding(spdy::SpdyStreamId stream_id, size_t len) override;
  spdy::SpdyHeadersHandlerInterface* OnHeaderFrameStart(
      spdy::SpdyStreamId stream_id) override;
  void OnHeaderFrameEnd(spdy::SpdyStreamId stream_id) override;
  void OnRstStream(spdy::SpdyStreamId stream_id,
                   spdy::SpdyErrorCode error_code) override;
  void OnSettings() override;
  void OnSetting(spdy::SpdySettingsId id, uint32_t value) override;
  void OnSettingsEnd() override;
  void OnSettingsAck() override;
  void OnPing(spdy::SpdyPingId unique_id, bool is_ack) override;
  void OnGoAway(spdy::SpdyStreamId last_accepted_stream_id,
                spdy::SpdyErrorCode error_code) override;
  bool OnGoAwayFrameData(const char* goaway_data, size_t len) override;
  void OnHeaders(spdy::SpdyStreamId stream_id, size_t payload_length,
                 bool has_priority, int weight,
                 spdy::SpdyStreamId parent_stream_id, bool exclusive, bool fin,
                 bool end) override;
  void OnWindowUpdate(spdy::SpdyStreamId stream_id,
                      int delta_window_size) override;
  void OnPushPromise(spdy::SpdyStreamId stream_id,
                     spdy::SpdyStreamId promised_stream_id, bool end) override;
  void OnContinuation(spdy::SpdyStreamId stream_id, size_t payload_length,
                      bool end) override;
  void OnAltSvc(spdy::SpdyStreamId /*stream_id*/, absl::string_view /*origin*/,
                const spdy::SpdyAltSvcWireFormat::
                    AlternativeServiceVector& /*altsvc_vector*/) override;
  void OnPriority(spdy::SpdyStreamId stream_id,
                  spdy::SpdyStreamId parent_stream_id, int weight,
                  bool exclusive) override;
  void OnPriorityUpdate(spdy::SpdyStreamId prioritized_stream_id,
                        absl::string_view priority_field_value) override;
  bool OnUnknownFrame(spdy::SpdyStreamId stream_id,
                      uint8_t frame_type) override;
  void OnUnknownFrameStart(spdy::SpdyStreamId stream_id, size_t length,
                           uint8_t type, uint8_t flags) override;
  void OnUnknownFramePayload(spdy::SpdyStreamId stream_id,
                             absl::string_view payload) override;

  // Invoked when header processing encounters an invalid or otherwise
  // problematic header.
  void OnHeaderStatus(Http2StreamId stream_id,
                      Http2VisitorInterface::OnHeaderResult result);

 private:
  struct QUICHE_EXPORT StreamState {
    StreamState(int32_t stream_receive_window, int32_t stream_send_window,
                WindowManager::WindowUpdateListener listener,
                WindowManager::ShouldWindowUpdateFn should_window_update_fn)
        : window_manager(stream_receive_window, std::move(listener),
                         std::move(should_window_update_fn),
                         /*update_window_on_notify=*/false),
          send_window(stream_send_window) {}

    WindowManager window_manager;
    std::unique_ptr<DataFrameSource> outbound_body;
    std::unique_ptr<spdy::Http2HeaderBlock> trailers;
    void* user_data = nullptr;
    int32_t send_window;
    absl::optional<HeaderType> received_header_type;
    absl::optional<size_t> remaining_content_length;
    bool half_closed_local = false;
    bool half_closed_remote = false;
    // Indicates that `outbound_body` temporarily cannot produce data.
    bool data_deferred = false;
    bool sent_head_method = false;
    bool can_receive_body = true;
  };
  using StreamStateMap = absl::flat_hash_map<Http2StreamId, StreamState>;

  struct QUICHE_EXPORT PendingStreamState {
    spdy::Http2HeaderBlock headers;
    std::unique_ptr<DataFrameSource> data_source;
    void* user_data = nullptr;
  };

  class QUICHE_EXPORT PassthroughHeadersHandler
      : public spdy::SpdyHeadersHandlerInterface {
   public:
    PassthroughHeadersHandler(OgHttp2Session& session,
                              Http2VisitorInterface& visitor);

    void set_stream_id(Http2StreamId stream_id) {
      stream_id_ = stream_id;
      result_ = Http2VisitorInterface::HEADER_OK;
    }

    void set_frame_contains_fin() { frame_contains_fin_ = true; }
    void set_header_type(HeaderType type) { type_ = type; }
    HeaderType header_type() const { return type_; }

    void OnHeaderBlockStart() override;
    void OnHeader(absl::string_view key, absl::string_view value) override;
    void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */,
                          size_t /* compressed_header_bytes */) override;
    absl::string_view status_header() const {
      QUICHE_DCHECK(type_ == HeaderType::RESPONSE ||
                    type_ == HeaderType::RESPONSE_100);
      return validator_->status_header();
    }
    absl::optional<size_t> content_length() const {
      return validator_->content_length();
    }
    void SetAllowExtendedConnect() { validator_->SetAllowExtendedConnect(); }
    void SetMaxFieldSize(uint32_t field_size) {
      validator_->SetMaxFieldSize(field_size);
    }
    void SetAllowObsText(bool allow) {
      validator_->SetObsTextOption(allow ? ObsTextOption::kAllow
                                         : ObsTextOption::kDisallow);
    }
    bool CanReceiveBody() const;

   private:
    OgHttp2Session& session_;
    Http2VisitorInterface& visitor_;
    Http2StreamId stream_id_ = 0;
    Http2VisitorInterface::OnHeaderResult result_ =
        Http2VisitorInterface::HEADER_OK;
    // Validates header blocks according to the HTTP/2 specification.
    std::unique_ptr<HeaderValidatorBase> validator_;
    HeaderType type_ = HeaderType::RESPONSE;
    bool frame_contains_fin_ = false;
  };

  struct QUICHE_EXPORT ProcessBytesResultVisitor;

  // Queues the connection preface, if not already done. If not
  // `sending_outbound_settings` and the preface has not yet been queued, this
  // method will generate and enqueue initial SETTINGS.
  void MaybeSetupPreface(bool sending_outbound_settings);

  // Gets the settings to be sent in the initial SETTINGS frame sent as part of
  // the connection preface.
  std::vector<Http2Setting> GetInitialSettings() const;

  // Prepares and returns a SETTINGS frame with the given `settings`.
  std::unique_ptr<spdy::SpdySettingsIR> PrepareSettingsFrame(
      absl::Span<const Http2Setting> settings);

  // Updates internal state to match the SETTINGS advertised to the peer.
  void HandleOutboundSettings(const spdy::SpdySettingsIR& settings_frame);

  void SendWindowUpdate(Http2StreamId stream_id, size_t update_delta);

  enum class SendResult {
    // All data was flushed.
    SEND_OK,
    // Not all data was flushed (due to flow control or TCP back pressure).
    SEND_BLOCKED,
    // An error occurred while sending data.
    SEND_ERROR,
  };

  // Returns the int corresponding to the `result`, updating state as needed.
  int InterpretSendResult(SendResult result);

  enum class ProcessBytesError {
    // A general, unspecified error.
    kUnspecified,
    // The (server-side) session received an invalid client connection preface.
    kInvalidConnectionPreface,
    // A user/visitor callback failed with a fatal error.
    kVisitorCallbackFailed,
  };
  using ProcessBytesResult = absl::variant<int64_t, ProcessBytesError>;

  // Attempts to process `bytes` and returns the number of bytes proccessed on
  // success or the processing error on failure.
  ProcessBytesResult ProcessBytesImpl(absl::string_view bytes);

  // Returns true if at least one stream has data or control frames to write.
  bool HasReadyStream() const;

  // Returns the next stream that has something to write. If there are no such
  // streams, returns zero.
  Http2StreamId GetNextReadyStream();

  // Sends the buffered connection preface or serialized frame data, if any.
  SendResult MaybeSendBufferedData();

  // Serializes and sends queued frames.
  SendResult SendQueuedFrames();

  // Returns false if a fatal connection error occurred.
  bool AfterFrameSent(uint8_t frame_type_int, uint32_t stream_id,
                      size_t payload_length, uint8_t flags,
                      uint32_t error_code);

  // Writes DATA frames for stream `stream_id`.
  SendResult WriteForStream(Http2StreamId stream_id);

  void SerializeMetadata(Http2StreamId stream_id,
                         std::unique_ptr<MetadataSource> source);

  void SendHeaders(Http2StreamId stream_id, spdy::Http2HeaderBlock headers,
                   bool end_stream);

  void SendTrailers(Http2StreamId stream_id, spdy::Http2HeaderBlock trailers);

  // Encapsulates the RST_STREAM NO_ERROR behavior described in RFC 7540
  // Section 8.1.
  void MaybeFinWithRstStream(StreamStateMap::iterator iter);

  // Performs flow control accounting for data sent by the peer.
  void MarkDataBuffered(Http2StreamId stream_id, size_t bytes);

  // Creates a stream for `stream_id` if not already present and returns an
  // iterator pointing to it.
  StreamStateMap::iterator CreateStream(Http2StreamId stream_id);

  // Creates a stream for `stream_id`, stores the `data_source` and `user_data`
  // in the stream state, and sends the `headers`.
  void StartRequest(Http2StreamId stream_id, spdy::Http2HeaderBlock headers,
                    std::unique_ptr<DataFrameSource> data_source,
                    void* user_data);

  // Sends headers for pending streams as long as the stream limit allows.
  void StartPendingStreams();

  // Closes the given `stream_id` with the given `error_code`.
  void CloseStream(Http2StreamId stream_id, Http2ErrorCode error_code);

  // Calculates the next expected header type for a stream in a given state.
  HeaderType NextHeaderType(absl::optional<HeaderType> current_type);

  // Returns true if the session can create a new stream.
  bool CanCreateStream() const;

  // Informs the visitor of the connection `error` and stops processing on the
  // connection. If server-side, also sends a GOAWAY with `error_code`.
  void LatchErrorAndNotify(Http2ErrorCode error_code,
                           Http2VisitorInterface::ConnectionError error);

  void CloseStreamIfReady(uint8_t frame_type, uint32_t stream_id);

  // Informs the visitor of rejected, non-active streams due to GOAWAY receipt.
  void CloseGoAwayRejectedStreams();

  // Updates internal state to prepare for sending an immediate GOAWAY.
  void PrepareForImmediateGoAway();

  // Handles the potential end of received metadata for the given `stream_id`.
  void MaybeHandleMetadataEndForStream(Http2StreamId stream_id);

  void DecrementQueuedFrameCount(uint32_t stream_id, uint8_t frame_type);

  void HandleContentLengthError(Http2StreamId stream_id);

  // Invoked when sending a flow control window update to the peer.
  void UpdateReceiveWindow(Http2StreamId stream_id, int32_t delta);

  // Updates stream send window accounting to respect the peer's advertised
  // initial window setting.
  void UpdateStreamSendWindowSizes(uint32_t new_value);

  // Updates stream receive window managers to use the newly advertised stream
  // initial window.
  void UpdateStreamReceiveWindowSizes(uint32_t new_value);

  // Receives events when inbound frames are parsed.
  Http2VisitorInterface& visitor_;

  const Options options_;

  // Forwards received events to the session if it can accept them.
  EventForwarder event_forwarder_;

  // Logs received frames when enabled.
  Http2TraceLogger receive_logger_;
  // Logs sent frames when enabled.
  Http2FrameLogger send_logger_;

  // Encodes outbound frames.
  spdy::SpdyFramer framer_{spdy::SpdyFramer::ENABLE_COMPRESSION};

  // Decodes inbound frames.
  http2::Http2DecoderAdapter decoder_;

  // Maintains the state of active streams known to this session.
  StreamStateMap stream_map_;

  // Maintains the state of pending streams known to this session. A pending
  // stream is kept in this list until it can be created while complying with
  // `max_outbound_concurrent_streams_`.
  quiche::QuicheLinkedHashMap<Http2StreamId, PendingStreamState>
      pending_streams_;

  // The queue of outbound frames.
  std::list<std::unique_ptr<spdy::SpdyFrameIR>> frames_;
  // Buffered data (connection preface, serialized frames) that has not yet been
  // sent.
  std::string buffered_data_;

  // Maintains the set of streams ready to write data to the peer.
  using WriteScheduler = PriorityWriteScheduler<Http2StreamId>;
  WriteScheduler write_scheduler_;

  // Stores the queue of callbacks to invoke upon receiving SETTINGS acks. At
  // most one callback is invoked for each SETTINGS ack.
  using SettingsAckCallback = quiche::SingleUseCallback<void()>;
  std::list<SettingsAckCallback> settings_ack_callbacks_;

  // Delivers header name-value pairs to the visitor.
  PassthroughHeadersHandler headers_handler_;

  // Ignores header data, e.g., for an unknown or rejected stream.
  spdy::NoOpHeadersHandler noop_headers_handler_;

  // Tracks the remaining client connection preface, in the case of a server
  // session.
  absl::string_view remaining_preface_;

  WindowManager connection_window_manager_;

  // Tracks the streams that have been marked for reset. A stream is removed
  // from this set once it is closed.
  absl::flat_hash_set<Http2StreamId> streams_reset_;

  // The number of frames currently queued per stream.
  absl::flat_hash_map<Http2StreamId, int> queued_frames_;
  // Includes streams that are currently ready to write trailers.
  absl::flat_hash_set<Http2StreamId> trailers_ready_;
  // Includes streams that will not be written due to receipt of GOAWAY.
  absl::flat_hash_set<Http2StreamId> goaway_rejected_streams_;

  Http2StreamId next_stream_id_ = 1;
  // The highest received stream ID is the highest stream ID in any frame read
  // from the peer. The highest processed stream ID is the highest stream ID for
  // which this endpoint created a stream in the stream map.
  Http2StreamId highest_received_stream_id_ = 0;
  Http2StreamId highest_processed_stream_id_ = 0;
  Http2StreamId received_goaway_stream_id_ = 0;
  size_t metadata_length_ = 0;
  int32_t connection_send_window_ = kInitialFlowControlWindowSize;
  // The initial flow control receive window size for any newly created streams.
  int32_t initial_stream_receive_window_ = kInitialFlowControlWindowSize;
  // The initial flow control send window size for any newly created streams.
  int32_t initial_stream_send_window_ = kInitialFlowControlWindowSize;
  uint32_t max_frame_payload_ = kDefaultFramePayloadSizeLimit;
  // The maximum number of concurrent streams that this connection can open to
  // its peer and allow from its peer, respectively. Although the initial value
  // is unlimited, the spec encourages a value of at least 100. We limit
  // ourselves to opening 100 until told otherwise by the peer and allow an
  // unlimited number from the peer until updated from SETTINGS we send.
  uint32_t max_outbound_concurrent_streams_ = 100u;
  uint32_t pending_max_inbound_concurrent_streams_ =
      std::numeric_limits<uint32_t>::max();
  uint32_t max_inbound_concurrent_streams_ =
      std::numeric_limits<uint32_t>::max();

  // The HPACK encoder header table capacity that will be applied when
  // acking SETTINGS from the peer. Only contains a value if the peer advertises
  // a larger table capacity than currently used; a smaller value can safely be
  // applied immediately upon receipt.
  absl::optional<uint32_t> encoder_header_table_capacity_when_acking_;

  bool received_goaway_ = false;
  bool queued_preface_ = false;
  bool peer_supports_metadata_ = false;
  bool end_metadata_ = false;
  bool process_metadata_ = false;
  bool sent_non_ack_settings_ = false;

  // Recursion guard for ProcessBytes().
  bool processing_bytes_ = false;
  // Recursion guard for Send().
  bool sending_ = false;

  bool peer_enables_connect_protocol_ = false;

  // Replace this with a stream ID, for multiple GOAWAY support.
  bool queued_goaway_ = false;
  bool queued_immediate_goaway_ = false;
  bool latched_error_ = false;

  // True if a fatal sending error has occurred.
  bool fatal_send_error_ = false;

  // True if a fatal processing visitor callback failed.
  bool fatal_visitor_callback_failure_ = false;
};

}  // namespace adapter
}  // namespace http2

#endif  // QUICHE_HTTP2_ADAPTER_OGHTTP2_SESSION_H_
