diff options
Diffstat (limited to 'chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc')
-rw-r--r-- | chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc b/chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc new file mode 100644 index 00000000000..fb6e14b8957 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc @@ -0,0 +1,351 @@ +// 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 |