// Copyright (c) 2018 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/quic/core/legacy_quic_stream_id_manager.h"

#include "net/third_party/quic/core/quic_session.h"
#include "net/third_party/quic/core/quic_utils.h"
#include "net/third_party/quic/platform/api/quic_map_util.h"

namespace quic {

#define ENDPOINT \
  (session_->perspective() == Perspective::IS_SERVER ? "Server: " : "Client: ")

LegacyQuicStreamIdManager::LegacyQuicStreamIdManager(
    QuicSession* session,
    size_t max_open_outgoing_streams,
    size_t max_open_incoming_streams)
    : session_(session),
      max_open_outgoing_streams_(max_open_outgoing_streams),
      max_open_incoming_streams_(max_open_incoming_streams),
      next_outgoing_stream_id_(
          QuicUtils::GetCryptoStreamId(
              session->connection()->transport_version()) +
          (session->perspective() == Perspective::IS_SERVER ? 1 : 2)),
      largest_peer_created_stream_id_(
          session->perspective() == Perspective::IS_SERVER
              ? QuicUtils::GetCryptoStreamId(
                    session->connection()->transport_version())
              : QuicUtils::GetInvalidStreamId(
                    session->connection()->transport_version())) {}

LegacyQuicStreamIdManager::~LegacyQuicStreamIdManager() {
  QUIC_LOG_IF(WARNING,
              session_->num_locally_closed_incoming_streams_highest_offset() >
                  max_open_incoming_streams_)
      << "Surprisingly high number of locally closed peer initiated streams"
         "still waiting for final byte offset: "
      << session_->num_locally_closed_incoming_streams_highest_offset();
  QUIC_LOG_IF(WARNING,
              session_->GetNumLocallyClosedOutgoingStreamsHighestOffset() >
                  max_open_outgoing_streams_)
      << "Surprisingly high number of locally closed self initiated streams"
         "still waiting for final byte offset: "
      << session_->GetNumLocallyClosedOutgoingStreamsHighestOffset();
}

bool LegacyQuicStreamIdManager::CanOpenNextOutgoingStream(
    size_t current_num_open_outgoing_streams) const {
  if (current_num_open_outgoing_streams >= max_open_outgoing_streams_) {
    QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
                    << "Already " << current_num_open_outgoing_streams
                    << " open.";
    return false;
  }
  return true;
}

bool LegacyQuicStreamIdManager::CanOpenIncomingStream(
    size_t current_num_open_incoming_streams) const {
  // Check if the new number of open streams would cause the number of
  // open streams to exceed the limit.
  return current_num_open_incoming_streams < max_open_incoming_streams_;
}

bool LegacyQuicStreamIdManager::MaybeIncreaseLargestPeerStreamId(
    const QuicStreamId stream_id) {
  available_streams_.erase(stream_id);

  if (largest_peer_created_stream_id_ !=
          QuicUtils::GetInvalidStreamId(
              session_->connection()->transport_version()) &&
      stream_id <= largest_peer_created_stream_id_) {
    return true;
  }

  // Check if the new number of available streams would cause the number of
  // available streams to exceed the limit.  Note that the peer can create
  // only alternately-numbered streams.
  size_t additional_available_streams =
      (stream_id - largest_peer_created_stream_id_) / 2 - 1;
  size_t new_num_available_streams =
      GetNumAvailableStreams() + additional_available_streams;
  if (new_num_available_streams > MaxAvailableStreams()) {
    QUIC_DLOG(INFO) << ENDPOINT
                    << "Failed to create a new incoming stream with id:"
                    << stream_id << ".  There are already "
                    << GetNumAvailableStreams()
                    << " streams available, which would become "
                    << new_num_available_streams << ", which exceeds the limit "
                    << MaxAvailableStreams() << ".";
    session_->connection()->CloseConnection(
        QUIC_TOO_MANY_AVAILABLE_STREAMS,
        QuicStrCat(new_num_available_streams, " above ", MaxAvailableStreams()),
        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
    return false;
  }
  for (QuicStreamId id = largest_peer_created_stream_id_ + 2; id < stream_id;
       id += 2) {
    available_streams_.insert(id);
  }
  largest_peer_created_stream_id_ = stream_id;

  return true;
}

QuicStreamId LegacyQuicStreamIdManager::GetNextOutgoingStreamId() {
  QuicStreamId id = next_outgoing_stream_id_;
  next_outgoing_stream_id_ += 2;
  return id;
}

bool LegacyQuicStreamIdManager::IsAvailableStream(QuicStreamId id) const {
  if (!IsIncomingStream(id)) {
    // Stream IDs under next_ougoing_stream_id_ are either open or previously
    // open but now closed.
    return id >= next_outgoing_stream_id_;
  }
  // For peer created streams, we also need to consider available streams.
  return largest_peer_created_stream_id_ ==
             QuicUtils::GetInvalidStreamId(
                 session_->connection()->transport_version()) ||
         id > largest_peer_created_stream_id_ ||
         QuicContainsKey(available_streams_, id);
}

bool LegacyQuicStreamIdManager::IsIncomingStream(QuicStreamId id) const {
  return id % 2 != next_outgoing_stream_id_ % 2;
}

size_t LegacyQuicStreamIdManager::GetNumAvailableStreams() const {
  return available_streams_.size();
}

size_t LegacyQuicStreamIdManager::MaxAvailableStreams() const {
  return max_open_incoming_streams_ * kMaxAvailableStreamsMultiplier;
}

}  // namespace quic
