blob: ce08e5998210d313f0d092ea43208bbb833e58b8 [file] [log] [blame]
// 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