Project import generated by Copybara.

PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/core/quic_stream_id_manager.cc b/quic/core/quic_stream_id_manager.cc
new file mode 100644
index 0000000..ce08e59
--- /dev/null
+++ b/quic/core/quic_stream_id_manager.cc
@@ -0,0 +1,350 @@
+// 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/quiche/src/quic/core/quic_stream_id_manager.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_constants.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+#define ENDPOINT                                                   \
+  (session_->perspective() == Perspective::IS_SERVER ? " Server: " \
+                                                     : " Client: ")
+
+QuicStreamIdManager::QuicStreamIdManager(
+    QuicSession* session,
+    QuicStreamId next_outgoing_stream_id,
+    QuicStreamId largest_peer_created_stream_id,
+    QuicStreamId first_incoming_dynamic_stream_id,
+    size_t max_allowed_outgoing_streams,
+    size_t max_allowed_incoming_streams)
+    : session_(session),
+      next_outgoing_stream_id_(next_outgoing_stream_id),
+      largest_peer_created_stream_id_(largest_peer_created_stream_id),
+      max_allowed_outgoing_stream_id_(0),
+      actual_max_allowed_incoming_stream_id_(0),
+      advertised_max_allowed_incoming_stream_id_(0),
+      max_stream_id_window_(max_allowed_incoming_streams /
+                            kMaxStreamIdWindowDivisor),
+      max_allowed_incoming_streams_(max_allowed_incoming_streams),
+      first_incoming_dynamic_stream_id_(first_incoming_dynamic_stream_id),
+      first_outgoing_dynamic_stream_id_(next_outgoing_stream_id) {
+  available_incoming_streams_ = max_allowed_incoming_streams_;
+  SetMaxOpenOutgoingStreams(max_allowed_outgoing_streams);
+  SetMaxOpenIncomingStreams(max_allowed_incoming_streams);
+}
+
+QuicStreamIdManager::~QuicStreamIdManager() {
+  QUIC_LOG_IF(WARNING,
+              session_->num_locally_closed_incoming_streams_highest_offset() >
+                  max_allowed_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_allowed_outgoing_streams_)
+      << "Surprisingly high number of locally closed self initiated streams"
+         "still waiting for final byte offset: "
+      << session_->GetNumLocallyClosedOutgoingStreamsHighestOffset();
+}
+
+bool QuicStreamIdManager::OnMaxStreamIdFrame(
+    const QuicMaxStreamIdFrame& frame) {
+  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(frame.max_stream_id),
+            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  // Need to determine whether the stream id matches our client/server
+  // perspective or not. If not, it's an error. If so, update appropriate
+  // maxima.
+  QUIC_CODE_COUNT_N(max_stream_id_received, 2, 2);
+  // TODO(fkastenholz): this test needs to be broader to handle uni- and bi-
+  // directional stream ids when that functionality is supported.
+  if (IsIncomingStream(frame.max_stream_id)) {
+    // TODO(fkastenholz): This, and following, closeConnection may
+    // need modification when proper support for IETF CONNECTION
+    // CLOSE is done.
+    QUIC_CODE_COUNT(max_stream_id_bad_direction);
+    session_->connection()->CloseConnection(
+        QUIC_MAX_STREAM_ID_ERROR,
+        "Recevied max stream ID with wrong initiator bit setting",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
+
+  // If a MAX_STREAM_ID advertises a stream ID that is smaller than previously
+  // advertised, it is to be ignored.
+  if (frame.max_stream_id < max_allowed_outgoing_stream_id_) {
+    QUIC_CODE_COUNT(max_stream_id_ignored);
+    return true;
+  }
+  max_allowed_outgoing_stream_id_ = frame.max_stream_id;
+
+  // Outgoing stream limit has increased, tell the applications
+  session_->OnCanCreateNewOutgoingStream();
+
+  return true;
+}
+
+bool QuicStreamIdManager::OnStreamIdBlockedFrame(
+    const QuicStreamIdBlockedFrame& frame) {
+  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(frame.stream_id),
+            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  QUIC_CODE_COUNT_N(stream_id_blocked_received, 2, 2);
+  QuicStreamId id = frame.stream_id;
+  if (!IsIncomingStream(frame.stream_id)) {
+    // Client/server mismatch, close the connection
+    // TODO(fkastenholz): revise when proper IETF Connection Close support is
+    // done.
+    QUIC_CODE_COUNT(stream_id_blocked_bad_direction);
+    session_->connection()->CloseConnection(
+        QUIC_STREAM_ID_BLOCKED_ERROR,
+        "Invalid stream ID directionality specified",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
+
+  if (id > advertised_max_allowed_incoming_stream_id_) {
+    // Peer thinks it can send more streams that we've told it.
+    // This is a protocol error.
+    // TODO(fkastenholz): revise when proper IETF Connection Close support is
+    // done.
+    QUIC_CODE_COUNT(stream_id_blocked_id_too_big);
+    session_->connection()->CloseConnection(
+        QUIC_STREAM_ID_BLOCKED_ERROR, "Invalid stream ID specified",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
+  if (id < actual_max_allowed_incoming_stream_id_) {
+    // Peer thinks it's blocked on an ID that is less than our current
+    // max. Inform the peer of the correct stream ID.
+    SendMaxStreamIdFrame();
+    return true;
+  }
+  // The peer's notion of the maximum ID is correct,
+  // there is nothing to do.
+  QUIC_CODE_COUNT(stream_id_blocked_id_correct);
+  return true;
+}
+
+// TODO(fkastenholz): Many changes will be needed here:
+//  -- Use IETF QUIC server/client-initiation sense
+//  -- Support both BIDI and UNI streams.
+//  -- can not change the max number of streams after config negotiation has
+//     been done.
+void QuicStreamIdManager::SetMaxOpenOutgoingStreams(size_t max_streams) {
+  max_allowed_outgoing_streams_ = max_streams;
+  max_allowed_outgoing_stream_id_ =
+      next_outgoing_stream_id_ + (max_streams - 1) * kV99StreamIdIncrement;
+}
+
+// TODO(fkastenholz): Many changes will be needed here:
+//  -- can not change the max number of streams after config negotiation has
+//     been done.
+//  -- Currently uses the Google Client/server-initiation sense, needs to
+//     be IETF.
+//  -- Support both BIDI and UNI streams.
+//  -- Convert calculation of the maximum ID from Google-QUIC semantics to IETF
+//     QUIC semantics.
+void QuicStreamIdManager::SetMaxOpenIncomingStreams(size_t max_streams) {
+  max_allowed_incoming_streams_ = max_streams;
+  // The peer should always believe that it has the negotiated
+  // number of stream ids available for use.
+  available_incoming_streams_ = max_allowed_incoming_streams_;
+
+  // the window is a fraction of the peer's notion of its stream-id space.
+  max_stream_id_window_ =
+      available_incoming_streams_ / kMaxStreamIdWindowDivisor;
+  if (max_stream_id_window_ == 0) {
+    max_stream_id_window_ = 1;
+  }
+
+  actual_max_allowed_incoming_stream_id_ =
+      first_incoming_dynamic_stream_id_ +
+      (max_allowed_incoming_streams_ - 1) * kV99StreamIdIncrement;
+  // To start, we can assume advertised and actual are the same.
+  advertised_max_allowed_incoming_stream_id_ =
+      actual_max_allowed_incoming_stream_id_;
+}
+
+void QuicStreamIdManager::MaybeSendMaxStreamIdFrame() {
+  if (available_incoming_streams_ > max_stream_id_window_) {
+    // window too large, no advertisement
+    return;
+  }
+  // Calculate the number of streams that the peer will believe
+  // it has. The "/kV99StreamIdIncrement" converts from stream-id-
+  // values to number-of-stream-ids.
+  available_incoming_streams_ += (actual_max_allowed_incoming_stream_id_ -
+                                  advertised_max_allowed_incoming_stream_id_) /
+                                 kV99StreamIdIncrement;
+  SendMaxStreamIdFrame();
+}
+
+void QuicStreamIdManager::SendMaxStreamIdFrame() {
+  advertised_max_allowed_incoming_stream_id_ =
+      actual_max_allowed_incoming_stream_id_;
+  // And Advertise it.
+  session_->SendMaxStreamId(advertised_max_allowed_incoming_stream_id_);
+}
+
+void QuicStreamIdManager::OnStreamClosed(QuicStreamId stream_id) {
+  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
+            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  if (!IsIncomingStream(stream_id)) {
+    // Nothing to do for outbound streams with respect to the
+    // stream ID space management.
+    return;
+  }
+  // If the stream is inbound, we can increase the stream ID limit and maybe
+  // advertise the new limit to the peer.
+  if (actual_max_allowed_incoming_stream_id_ >=
+      (kMaxQuicStreamId - kV99StreamIdIncrement)) {
+    // Reached the maximum stream id value that the implementation
+    // supports. Nothing can be done here.
+    return;
+  }
+  actual_max_allowed_incoming_stream_id_ += kV99StreamIdIncrement;
+  MaybeSendMaxStreamIdFrame();
+}
+
+QuicStreamId QuicStreamIdManager::GetNextOutgoingStreamId() {
+  QUIC_BUG_IF(next_outgoing_stream_id_ > max_allowed_outgoing_stream_id_)
+      << "Attempt allocate a new outgoing stream ID would exceed the limit";
+  QuicStreamId id = next_outgoing_stream_id_;
+  next_outgoing_stream_id_ += kV99StreamIdIncrement;
+  return id;
+}
+
+bool QuicStreamIdManager::CanOpenNextOutgoingStream() {
+  DCHECK_EQ(QUIC_VERSION_99, session_->connection()->transport_version());
+  if (next_outgoing_stream_id_ > max_allowed_outgoing_stream_id_) {
+    // Next stream ID would exceed the limit, need to inform the peer.
+    session_->SendStreamIdBlocked(max_allowed_outgoing_stream_id_);
+    QUIC_CODE_COUNT(reached_outgoing_stream_id_limit);
+    return false;
+  }
+  return true;
+}
+
+void QuicStreamIdManager::RegisterStaticStream(QuicStreamId stream_id) {
+  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
+            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  QuicStreamId first_dynamic_stream_id = stream_id + kV99StreamIdIncrement;
+
+  if (IsIncomingStream(first_dynamic_stream_id)) {
+    // This code is predicated on static stream ids being allocated densely, in
+    // order, and starting with the first stream allowed. QUIC_BUG if this is
+    // not so.
+    QUIC_BUG_IF(stream_id > first_incoming_dynamic_stream_id_)
+        << "Error in incoming static stream allocation, expected to allocate "
+        << first_incoming_dynamic_stream_id_ << " got " << stream_id;
+
+    // This is a stream id for a stream that is started by the peer, deal with
+    // the incoming stream ids. Increase the floor and adjust everything
+    // accordingly.
+    if (stream_id == first_incoming_dynamic_stream_id_) {
+      actual_max_allowed_incoming_stream_id_ += kV99StreamIdIncrement;
+      first_incoming_dynamic_stream_id_ = first_dynamic_stream_id;
+    }
+    return;
+  }
+
+  // This code is predicated on static stream ids being allocated densely, in
+  // order, and starting with the first stream allowed. QUIC_BUG if this is
+  // not so.
+  QUIC_BUG_IF(stream_id > first_outgoing_dynamic_stream_id_)
+      << "Error in outgoing static stream allocation, expected to allocate "
+      << first_outgoing_dynamic_stream_id_ << " got " << stream_id;
+  // This is a stream id for a stream that is started by this node; deal with
+  // the outgoing stream ids. Increase the floor and adjust everything
+  // accordingly.
+  if (stream_id == first_outgoing_dynamic_stream_id_) {
+    max_allowed_outgoing_stream_id_ += kV99StreamIdIncrement;
+    first_outgoing_dynamic_stream_id_ = first_dynamic_stream_id;
+  }
+}
+
+bool QuicStreamIdManager::MaybeIncreaseLargestPeerStreamId(
+    const QuicStreamId stream_id) {
+  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
+            QuicUtils::IsBidirectionalStreamId(next_outgoing_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;
+  }
+
+  if (stream_id > actual_max_allowed_incoming_stream_id_) {
+    // Desired stream ID is larger than the limit, do not increase.
+    QUIC_DLOG(INFO) << ENDPOINT
+                    << "Failed to create a new incoming stream with id:"
+                    << stream_id << ".  Maximum allowed stream id is "
+                    << actual_max_allowed_incoming_stream_id_ << ".";
+    session_->connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID,
+        QuicStrCat("Stream id ", stream_id, " above ",
+                   actual_max_allowed_incoming_stream_id_),
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
+
+  available_incoming_streams_--;
+
+  QuicStreamId id = largest_peer_created_stream_id_ + kV99StreamIdIncrement;
+  if (largest_peer_created_stream_id_ ==
+      QuicUtils::GetInvalidStreamId(
+          session_->connection()->transport_version())) {
+    // Adjust id based on perspective and whether stream_id is bidirectional or
+    // unidirectional.
+    if (QuicUtils::IsBidirectionalStreamId(stream_id)) {
+      // This should only happen on client side because server bidirectional
+      // stream ID manager's largest_peer_created_stream_id_ is initialized to
+      // the crypto stream ID.
+      DCHECK_EQ(Perspective::IS_CLIENT, session_->perspective());
+      id = 1;
+    } else {
+      id = session_->perspective() == Perspective::IS_SERVER ? 2 : 3;
+    }
+  }
+  for (; id < stream_id; id += kV99StreamIdIncrement) {
+    available_streams_.insert(id);
+  }
+  largest_peer_created_stream_id_ = stream_id;
+  return true;
+}
+
+bool QuicStreamIdManager::IsAvailableStream(QuicStreamId id) const {
+  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(id),
+            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  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 QuicStreamIdManager::IsIncomingStream(QuicStreamId id) const {
+  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(id),
+            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  return id % kV99StreamIdIncrement !=
+         next_outgoing_stream_id_ % kV99StreamIdIncrement;
+}
+
+}  // namespace quic