| // 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 <string> | |
| #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" | |
| 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, | |
| "Received 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 |