blob: 17368093c00206cab83498cdda1c6860ea7959a1 [file] [log] [blame]
QUICHE teama6ef0a62019-03-07 20:34:33 -05001// Copyright (c) 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#include "net/third_party/quiche/src/quic/core/quic_stream_id_manager.h"
5
vasilvv872e7a32019-03-12 16:42:44 -07006#include <string>
7
QUICHE teama6ef0a62019-03-07 20:34:33 -05008#include "net/third_party/quiche/src/quic/core/quic_connection.h"
9#include "net/third_party/quiche/src/quic/core/quic_constants.h"
10#include "net/third_party/quiche/src/quic/core/quic_session.h"
11#include "net/third_party/quiche/src/quic/core/quic_utils.h"
12#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
13#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
14#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
15#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
QUICHE teama6ef0a62019-03-07 20:34:33 -050016
17namespace quic {
18
19#define ENDPOINT \
20 (session_->perspective() == Perspective::IS_SERVER ? " Server: " \
21 : " Client: ")
22
23QuicStreamIdManager::QuicStreamIdManager(
24 QuicSession* session,
25 QuicStreamId next_outgoing_stream_id,
26 QuicStreamId largest_peer_created_stream_id,
27 QuicStreamId first_incoming_dynamic_stream_id,
28 size_t max_allowed_outgoing_streams,
29 size_t max_allowed_incoming_streams)
30 : session_(session),
31 next_outgoing_stream_id_(next_outgoing_stream_id),
32 largest_peer_created_stream_id_(largest_peer_created_stream_id),
33 max_allowed_outgoing_stream_id_(0),
34 actual_max_allowed_incoming_stream_id_(0),
35 advertised_max_allowed_incoming_stream_id_(0),
36 max_stream_id_window_(max_allowed_incoming_streams /
37 kMaxStreamIdWindowDivisor),
38 max_allowed_incoming_streams_(max_allowed_incoming_streams),
39 first_incoming_dynamic_stream_id_(first_incoming_dynamic_stream_id),
40 first_outgoing_dynamic_stream_id_(next_outgoing_stream_id) {
41 available_incoming_streams_ = max_allowed_incoming_streams_;
42 SetMaxOpenOutgoingStreams(max_allowed_outgoing_streams);
43 SetMaxOpenIncomingStreams(max_allowed_incoming_streams);
44}
45
46QuicStreamIdManager::~QuicStreamIdManager() {
47 QUIC_LOG_IF(WARNING,
48 session_->num_locally_closed_incoming_streams_highest_offset() >
49 max_allowed_incoming_streams_)
50 << "Surprisingly high number of locally closed peer initiated streams"
51 "still waiting for final byte offset: "
52 << session_->num_locally_closed_incoming_streams_highest_offset();
53 QUIC_LOG_IF(WARNING,
54 session_->GetNumLocallyClosedOutgoingStreamsHighestOffset() >
55 max_allowed_outgoing_streams_)
56 << "Surprisingly high number of locally closed self initiated streams"
57 "still waiting for final byte offset: "
58 << session_->GetNumLocallyClosedOutgoingStreamsHighestOffset();
59}
60
61bool QuicStreamIdManager::OnMaxStreamIdFrame(
62 const QuicMaxStreamIdFrame& frame) {
63 DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(frame.max_stream_id),
64 QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
65 // Need to determine whether the stream id matches our client/server
66 // perspective or not. If not, it's an error. If so, update appropriate
67 // maxima.
68 QUIC_CODE_COUNT_N(max_stream_id_received, 2, 2);
69 // TODO(fkastenholz): this test needs to be broader to handle uni- and bi-
70 // directional stream ids when that functionality is supported.
71 if (IsIncomingStream(frame.max_stream_id)) {
72 // TODO(fkastenholz): This, and following, closeConnection may
73 // need modification when proper support for IETF CONNECTION
74 // CLOSE is done.
75 QUIC_CODE_COUNT(max_stream_id_bad_direction);
76 session_->connection()->CloseConnection(
77 QUIC_MAX_STREAM_ID_ERROR,
78 "Recevied max stream ID with wrong initiator bit setting",
79 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
80 return false;
81 }
82
83 // If a MAX_STREAM_ID advertises a stream ID that is smaller than previously
84 // advertised, it is to be ignored.
85 if (frame.max_stream_id < max_allowed_outgoing_stream_id_) {
86 QUIC_CODE_COUNT(max_stream_id_ignored);
87 return true;
88 }
89 max_allowed_outgoing_stream_id_ = frame.max_stream_id;
90
91 // Outgoing stream limit has increased, tell the applications
92 session_->OnCanCreateNewOutgoingStream();
93
94 return true;
95}
96
97bool QuicStreamIdManager::OnStreamIdBlockedFrame(
98 const QuicStreamIdBlockedFrame& frame) {
99 DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(frame.stream_id),
100 QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
101 QUIC_CODE_COUNT_N(stream_id_blocked_received, 2, 2);
102 QuicStreamId id = frame.stream_id;
103 if (!IsIncomingStream(frame.stream_id)) {
104 // Client/server mismatch, close the connection
105 // TODO(fkastenholz): revise when proper IETF Connection Close support is
106 // done.
107 QUIC_CODE_COUNT(stream_id_blocked_bad_direction);
108 session_->connection()->CloseConnection(
109 QUIC_STREAM_ID_BLOCKED_ERROR,
110 "Invalid stream ID directionality specified",
111 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
112 return false;
113 }
114
115 if (id > advertised_max_allowed_incoming_stream_id_) {
116 // Peer thinks it can send more streams that we've told it.
117 // This is a protocol error.
118 // TODO(fkastenholz): revise when proper IETF Connection Close support is
119 // done.
120 QUIC_CODE_COUNT(stream_id_blocked_id_too_big);
121 session_->connection()->CloseConnection(
122 QUIC_STREAM_ID_BLOCKED_ERROR, "Invalid stream ID specified",
123 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
124 return false;
125 }
126 if (id < actual_max_allowed_incoming_stream_id_) {
127 // Peer thinks it's blocked on an ID that is less than our current
128 // max. Inform the peer of the correct stream ID.
129 SendMaxStreamIdFrame();
130 return true;
131 }
132 // The peer's notion of the maximum ID is correct,
133 // there is nothing to do.
134 QUIC_CODE_COUNT(stream_id_blocked_id_correct);
135 return true;
136}
137
138// TODO(fkastenholz): Many changes will be needed here:
139// -- Use IETF QUIC server/client-initiation sense
140// -- Support both BIDI and UNI streams.
141// -- can not change the max number of streams after config negotiation has
142// been done.
143void QuicStreamIdManager::SetMaxOpenOutgoingStreams(size_t max_streams) {
144 max_allowed_outgoing_streams_ = max_streams;
145 max_allowed_outgoing_stream_id_ =
146 next_outgoing_stream_id_ + (max_streams - 1) * kV99StreamIdIncrement;
147}
148
149// TODO(fkastenholz): Many changes will be needed here:
150// -- can not change the max number of streams after config negotiation has
151// been done.
152// -- Currently uses the Google Client/server-initiation sense, needs to
153// be IETF.
154// -- Support both BIDI and UNI streams.
155// -- Convert calculation of the maximum ID from Google-QUIC semantics to IETF
156// QUIC semantics.
157void QuicStreamIdManager::SetMaxOpenIncomingStreams(size_t max_streams) {
158 max_allowed_incoming_streams_ = max_streams;
159 // The peer should always believe that it has the negotiated
160 // number of stream ids available for use.
161 available_incoming_streams_ = max_allowed_incoming_streams_;
162
163 // the window is a fraction of the peer's notion of its stream-id space.
164 max_stream_id_window_ =
165 available_incoming_streams_ / kMaxStreamIdWindowDivisor;
166 if (max_stream_id_window_ == 0) {
167 max_stream_id_window_ = 1;
168 }
169
170 actual_max_allowed_incoming_stream_id_ =
171 first_incoming_dynamic_stream_id_ +
172 (max_allowed_incoming_streams_ - 1) * kV99StreamIdIncrement;
173 // To start, we can assume advertised and actual are the same.
174 advertised_max_allowed_incoming_stream_id_ =
175 actual_max_allowed_incoming_stream_id_;
176}
177
178void QuicStreamIdManager::MaybeSendMaxStreamIdFrame() {
179 if (available_incoming_streams_ > max_stream_id_window_) {
180 // window too large, no advertisement
181 return;
182 }
183 // Calculate the number of streams that the peer will believe
184 // it has. The "/kV99StreamIdIncrement" converts from stream-id-
185 // values to number-of-stream-ids.
186 available_incoming_streams_ += (actual_max_allowed_incoming_stream_id_ -
187 advertised_max_allowed_incoming_stream_id_) /
188 kV99StreamIdIncrement;
189 SendMaxStreamIdFrame();
190}
191
192void QuicStreamIdManager::SendMaxStreamIdFrame() {
193 advertised_max_allowed_incoming_stream_id_ =
194 actual_max_allowed_incoming_stream_id_;
195 // And Advertise it.
196 session_->SendMaxStreamId(advertised_max_allowed_incoming_stream_id_);
197}
198
199void QuicStreamIdManager::OnStreamClosed(QuicStreamId stream_id) {
200 DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
201 QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
202 if (!IsIncomingStream(stream_id)) {
203 // Nothing to do for outbound streams with respect to the
204 // stream ID space management.
205 return;
206 }
207 // If the stream is inbound, we can increase the stream ID limit and maybe
208 // advertise the new limit to the peer.
209 if (actual_max_allowed_incoming_stream_id_ >=
210 (kMaxQuicStreamId - kV99StreamIdIncrement)) {
211 // Reached the maximum stream id value that the implementation
212 // supports. Nothing can be done here.
213 return;
214 }
215 actual_max_allowed_incoming_stream_id_ += kV99StreamIdIncrement;
216 MaybeSendMaxStreamIdFrame();
217}
218
219QuicStreamId QuicStreamIdManager::GetNextOutgoingStreamId() {
220 QUIC_BUG_IF(next_outgoing_stream_id_ > max_allowed_outgoing_stream_id_)
221 << "Attempt allocate a new outgoing stream ID would exceed the limit";
222 QuicStreamId id = next_outgoing_stream_id_;
223 next_outgoing_stream_id_ += kV99StreamIdIncrement;
224 return id;
225}
226
227bool QuicStreamIdManager::CanOpenNextOutgoingStream() {
228 DCHECK_EQ(QUIC_VERSION_99, session_->connection()->transport_version());
229 if (next_outgoing_stream_id_ > max_allowed_outgoing_stream_id_) {
230 // Next stream ID would exceed the limit, need to inform the peer.
231 session_->SendStreamIdBlocked(max_allowed_outgoing_stream_id_);
232 QUIC_CODE_COUNT(reached_outgoing_stream_id_limit);
233 return false;
234 }
235 return true;
236}
237
238void QuicStreamIdManager::RegisterStaticStream(QuicStreamId stream_id) {
239 DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
240 QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
241 QuicStreamId first_dynamic_stream_id = stream_id + kV99StreamIdIncrement;
242
243 if (IsIncomingStream(first_dynamic_stream_id)) {
244 // This code is predicated on static stream ids being allocated densely, in
245 // order, and starting with the first stream allowed. QUIC_BUG if this is
246 // not so.
247 QUIC_BUG_IF(stream_id > first_incoming_dynamic_stream_id_)
248 << "Error in incoming static stream allocation, expected to allocate "
249 << first_incoming_dynamic_stream_id_ << " got " << stream_id;
250
251 // This is a stream id for a stream that is started by the peer, deal with
252 // the incoming stream ids. Increase the floor and adjust everything
253 // accordingly.
254 if (stream_id == first_incoming_dynamic_stream_id_) {
255 actual_max_allowed_incoming_stream_id_ += kV99StreamIdIncrement;
256 first_incoming_dynamic_stream_id_ = first_dynamic_stream_id;
257 }
258 return;
259 }
260
261 // This code is predicated on static stream ids being allocated densely, in
262 // order, and starting with the first stream allowed. QUIC_BUG if this is
263 // not so.
264 QUIC_BUG_IF(stream_id > first_outgoing_dynamic_stream_id_)
265 << "Error in outgoing static stream allocation, expected to allocate "
266 << first_outgoing_dynamic_stream_id_ << " got " << stream_id;
267 // This is a stream id for a stream that is started by this node; deal with
268 // the outgoing stream ids. Increase the floor and adjust everything
269 // accordingly.
270 if (stream_id == first_outgoing_dynamic_stream_id_) {
271 max_allowed_outgoing_stream_id_ += kV99StreamIdIncrement;
272 first_outgoing_dynamic_stream_id_ = first_dynamic_stream_id;
273 }
274}
275
276bool QuicStreamIdManager::MaybeIncreaseLargestPeerStreamId(
277 const QuicStreamId stream_id) {
278 DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
279 QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
280 available_streams_.erase(stream_id);
281
282 if (largest_peer_created_stream_id_ !=
283 QuicUtils::GetInvalidStreamId(
284 session_->connection()->transport_version()) &&
285 stream_id <= largest_peer_created_stream_id_) {
286 return true;
287 }
288
289 if (stream_id > actual_max_allowed_incoming_stream_id_) {
290 // Desired stream ID is larger than the limit, do not increase.
291 QUIC_DLOG(INFO) << ENDPOINT
292 << "Failed to create a new incoming stream with id:"
293 << stream_id << ". Maximum allowed stream id is "
294 << actual_max_allowed_incoming_stream_id_ << ".";
295 session_->connection()->CloseConnection(
296 QUIC_INVALID_STREAM_ID,
297 QuicStrCat("Stream id ", stream_id, " above ",
298 actual_max_allowed_incoming_stream_id_),
299 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
300 return false;
301 }
302
303 available_incoming_streams_--;
304
305 QuicStreamId id = largest_peer_created_stream_id_ + kV99StreamIdIncrement;
306 if (largest_peer_created_stream_id_ ==
307 QuicUtils::GetInvalidStreamId(
308 session_->connection()->transport_version())) {
309 // Adjust id based on perspective and whether stream_id is bidirectional or
310 // unidirectional.
311 if (QuicUtils::IsBidirectionalStreamId(stream_id)) {
312 // This should only happen on client side because server bidirectional
313 // stream ID manager's largest_peer_created_stream_id_ is initialized to
314 // the crypto stream ID.
315 DCHECK_EQ(Perspective::IS_CLIENT, session_->perspective());
316 id = 1;
317 } else {
318 id = session_->perspective() == Perspective::IS_SERVER ? 2 : 3;
319 }
320 }
321 for (; id < stream_id; id += kV99StreamIdIncrement) {
322 available_streams_.insert(id);
323 }
324 largest_peer_created_stream_id_ = stream_id;
325 return true;
326}
327
328bool QuicStreamIdManager::IsAvailableStream(QuicStreamId id) const {
329 DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(id),
330 QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
331 if (!IsIncomingStream(id)) {
332 // Stream IDs under next_ougoing_stream_id_ are either open or previously
333 // open but now closed.
334 return id >= next_outgoing_stream_id_;
335 }
336 // For peer created streams, we also need to consider available streams.
337 return largest_peer_created_stream_id_ ==
338 QuicUtils::GetInvalidStreamId(
339 session_->connection()->transport_version()) ||
340 id > largest_peer_created_stream_id_ ||
341 QuicContainsKey(available_streams_, id);
342}
343
344bool QuicStreamIdManager::IsIncomingStream(QuicStreamId id) const {
345 DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(id),
346 QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
347 return id % kV99StreamIdIncrement !=
348 next_outgoing_stream_id_ % kV99StreamIdIncrement;
349}
350
351} // namespace quic