1 // Copyright 2014 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
5 #include "quiche/quic/core/http/quic_spdy_client_session_base.h"
6
7 #include <string>
8
9 #include "quiche/quic/core/http/quic_client_promised_info.h"
10 #include "quiche/quic/core/http/spdy_server_push_utils.h"
11 #include "quiche/quic/core/quic_utils.h"
12 #include "quiche/quic/platform/api/quic_flags.h"
13 #include "quiche/quic/platform/api/quic_logging.h"
14
15 using spdy::Http2HeaderBlock;
16
17 namespace quic {
18
QuicSpdyClientSessionBase(QuicConnection * connection,QuicSession::Visitor * visitor,QuicClientPushPromiseIndex * push_promise_index,const QuicConfig & config,const ParsedQuicVersionVector & supported_versions)19 QuicSpdyClientSessionBase::QuicSpdyClientSessionBase(
20 QuicConnection* connection, QuicSession::Visitor* visitor,
21 QuicClientPushPromiseIndex* push_promise_index, const QuicConfig& config,
22 const ParsedQuicVersionVector& supported_versions)
23 : QuicSpdySession(connection, visitor, config, supported_versions),
24 push_promise_index_(push_promise_index),
25 largest_promised_stream_id_(
26 QuicUtils::GetInvalidStreamId(connection->transport_version())) {}
27
~QuicSpdyClientSessionBase()28 QuicSpdyClientSessionBase::~QuicSpdyClientSessionBase() {
29 // all promised streams for this session
30 for (auto& it : promised_by_id_) {
31 QUIC_DVLOG(1) << "erase stream " << it.first << " url " << it.second->url();
32 push_promise_index_->promised_by_url()->erase(it.second->url());
33 }
34 DeleteConnection();
35 }
36
OnConfigNegotiated()37 void QuicSpdyClientSessionBase::OnConfigNegotiated() {
38 QuicSpdySession::OnConfigNegotiated();
39 }
40
OnInitialHeadersComplete(QuicStreamId stream_id,const Http2HeaderBlock & response_headers)41 void QuicSpdyClientSessionBase::OnInitialHeadersComplete(
42 QuicStreamId stream_id, const Http2HeaderBlock& response_headers) {
43 // Note that the strong ordering of the headers stream means that
44 // QuicSpdyClientStream::OnPromiseHeadersComplete must have already
45 // been called (on the associated stream) if this is a promised
46 // stream. However, this stream may not have existed at this time,
47 // hence the need to query the session.
48 QuicClientPromisedInfo* promised = GetPromisedById(stream_id);
49 if (!promised) return;
50
51 promised->OnResponseHeaders(response_headers);
52 }
53
OnPromiseHeaderList(QuicStreamId stream_id,QuicStreamId promised_stream_id,size_t frame_len,const QuicHeaderList & header_list)54 void QuicSpdyClientSessionBase::OnPromiseHeaderList(
55 QuicStreamId stream_id, QuicStreamId promised_stream_id, size_t frame_len,
56 const QuicHeaderList& header_list) {
57 if (IsStaticStream(stream_id)) {
58 connection()->CloseConnection(
59 QUIC_INVALID_HEADERS_STREAM_DATA, "stream is static",
60 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
61 return;
62 }
63 // In HTTP3, push promises are received on individual streams, so they could
64 // be arrive out of order.
65 if (!VersionUsesHttp3(transport_version()) &&
66 promised_stream_id !=
67 QuicUtils::GetInvalidStreamId(transport_version()) &&
68 largest_promised_stream_id_ !=
69 QuicUtils::GetInvalidStreamId(transport_version()) &&
70 promised_stream_id <= largest_promised_stream_id_) {
71 connection()->CloseConnection(
72 QUIC_INVALID_STREAM_ID,
73 "Received push stream id lesser or equal to the"
74 " last accepted before",
75 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
76 return;
77 }
78 if (!IsIncomingStream(promised_stream_id)) {
79 connection()->CloseConnection(
80 QUIC_INVALID_STREAM_ID, "Received push stream id for outgoing stream.",
81 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
82 return;
83 }
84
85 if (VersionUsesHttp3(transport_version())) {
86 // Received push stream id is higher than MAX_PUSH_ID
87 // because no MAX_PUSH_ID frame is ever sent.
88 connection()->CloseConnection(
89 QUIC_INVALID_STREAM_ID,
90 "Received push stream id higher than MAX_PUSH_ID.",
91 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
92 return;
93 }
94 largest_promised_stream_id_ = promised_stream_id;
95
96 QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id);
97 if (!stream) {
98 // It's quite possible to receive headers after a stream has been reset.
99 return;
100 }
101 stream->OnPromiseHeaderList(promised_stream_id, frame_len, header_list);
102 }
103
HandlePromised(QuicStreamId,QuicStreamId promised_id,const Http2HeaderBlock & headers)104 bool QuicSpdyClientSessionBase::HandlePromised(
105 QuicStreamId /* associated_id */, QuicStreamId promised_id,
106 const Http2HeaderBlock& headers) {
107 // TODO(b/136295430): Do not treat |promised_id| as a stream ID when using
108 // IETF QUIC.
109 // Due to pathalogical packet re-ordering, it is possible that
110 // frames for the promised stream have already arrived, and the
111 // promised stream could be active or closed.
112 if (IsClosedStream(promised_id)) {
113 // There was a RST on the data stream already, perhaps
114 // QUIC_REFUSED_STREAM?
115 QUIC_DVLOG(1) << "Promise ignored for stream " << promised_id
116 << " that is already closed";
117 return false;
118 }
119
120 if (push_promise_index_->promised_by_url()->size() >= get_max_promises()) {
121 QUIC_DVLOG(1) << "Too many promises, rejecting promise for stream "
122 << promised_id;
123 ResetPromised(promised_id, QUIC_REFUSED_STREAM);
124 return false;
125 }
126
127 const std::string url =
128 SpdyServerPushUtils::GetPromisedUrlFromHeaders(headers);
129 QuicClientPromisedInfo* old_promised = GetPromisedByUrl(url);
130 if (old_promised) {
131 QUIC_DVLOG(1) << "Promise for stream " << promised_id
132 << " is duplicate URL " << url
133 << " of previous promise for stream " << old_promised->id();
134 ResetPromised(promised_id, QUIC_DUPLICATE_PROMISE_URL);
135 return false;
136 }
137
138 if (GetPromisedById(promised_id)) {
139 // OnPromiseHeadersComplete() would have closed the connection if
140 // promised id is a duplicate.
141 QUIC_BUG(quic_bug_10412_1) << "Duplicate promise for id " << promised_id;
142 return false;
143 }
144
145 QuicClientPromisedInfo* promised =
146 new QuicClientPromisedInfo(this, promised_id, url);
147 std::unique_ptr<QuicClientPromisedInfo> promised_owner(promised);
148 promised->Init();
149 QUIC_DVLOG(1) << "stream " << promised_id << " emplace url " << url;
150 (*push_promise_index_->promised_by_url())[url] = promised;
151 promised_by_id_[promised_id] = std::move(promised_owner);
152 bool result = promised->OnPromiseHeaders(headers);
153 if (result) {
154 QUICHE_DCHECK(promised_by_id_.find(promised_id) != promised_by_id_.end());
155 }
156 return result;
157 }
158
GetPromisedByUrl(const std::string & url)159 QuicClientPromisedInfo* QuicSpdyClientSessionBase::GetPromisedByUrl(
160 const std::string& url) {
161 auto it = push_promise_index_->promised_by_url()->find(url);
162 if (it != push_promise_index_->promised_by_url()->end()) {
163 return it->second;
164 }
165 return nullptr;
166 }
167
GetPromisedById(const QuicStreamId id)168 QuicClientPromisedInfo* QuicSpdyClientSessionBase::GetPromisedById(
169 const QuicStreamId id) {
170 auto it = promised_by_id_.find(id);
171 if (it != promised_by_id_.end()) {
172 return it->second.get();
173 }
174 return nullptr;
175 }
176
GetPromisedStream(const QuicStreamId id)177 QuicSpdyStream* QuicSpdyClientSessionBase::GetPromisedStream(
178 const QuicStreamId id) {
179 QuicStream* stream = GetActiveStream(id);
180 if (stream != nullptr) {
181 return static_cast<QuicSpdyStream*>(stream);
182 }
183 return nullptr;
184 }
185
DeletePromised(QuicClientPromisedInfo * promised)186 void QuicSpdyClientSessionBase::DeletePromised(
187 QuicClientPromisedInfo* promised) {
188 push_promise_index_->promised_by_url()->erase(promised->url());
189 // Since promised_by_id_ contains the unique_ptr, this will destroy
190 // promised.
191 // ToDo: Consider implementing logic to send a new MAX_PUSH_ID frame to allow
192 // another stream to be promised.
193 promised_by_id_.erase(promised->id());
194 if (!VersionUsesHttp3(transport_version())) {
195 headers_stream()->MaybeReleaseSequencerBuffer();
196 }
197 }
198
OnPushStreamTimedOut(QuicStreamId)199 void QuicSpdyClientSessionBase::OnPushStreamTimedOut(
200 QuicStreamId /*stream_id*/) {}
201
ResetPromised(QuicStreamId id,QuicRstStreamErrorCode error_code)202 void QuicSpdyClientSessionBase::ResetPromised(
203 QuicStreamId id, QuicRstStreamErrorCode error_code) {
204 QUICHE_DCHECK(QuicUtils::IsServerInitiatedStreamId(transport_version(), id));
205 ResetStream(id, error_code);
206 if (!IsOpenStream(id) && !IsClosedStream(id)) {
207 MaybeIncreaseLargestPeerStreamId(id);
208 }
209 }
210
OnStreamClosed(QuicStreamId stream_id)211 void QuicSpdyClientSessionBase::OnStreamClosed(QuicStreamId stream_id) {
212 QuicSpdySession::OnStreamClosed(stream_id);
213 if (!VersionUsesHttp3(transport_version())) {
214 headers_stream()->MaybeReleaseSequencerBuffer();
215 }
216 }
217
ShouldReleaseHeadersStreamSequencerBuffer()218 bool QuicSpdyClientSessionBase::ShouldReleaseHeadersStreamSequencerBuffer() {
219 return !HasActiveRequestStreams() && promised_by_id_.empty();
220 }
221
ShouldKeepConnectionAlive() const222 bool QuicSpdyClientSessionBase::ShouldKeepConnectionAlive() const {
223 return QuicSpdySession::ShouldKeepConnectionAlive() ||
224 num_outgoing_draining_streams() > 0;
225 }
226
OnSettingsFrame(const SettingsFrame & frame)227 bool QuicSpdyClientSessionBase::OnSettingsFrame(const SettingsFrame& frame) {
228 if (!was_zero_rtt_rejected()) {
229 if (max_outbound_header_list_size() != std::numeric_limits<size_t>::max() &&
230 frame.values.find(SETTINGS_MAX_FIELD_SECTION_SIZE) ==
231 frame.values.end()) {
232 CloseConnectionWithDetails(
233 QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
234 "Server accepted 0-RTT but omitted non-default "
235 "SETTINGS_MAX_FIELD_SECTION_SIZE");
236 return false;
237 }
238
239 if (qpack_encoder()->maximum_blocked_streams() != 0 &&
240 frame.values.find(SETTINGS_QPACK_BLOCKED_STREAMS) ==
241 frame.values.end()) {
242 CloseConnectionWithDetails(
243 QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
244 "Server accepted 0-RTT but omitted non-default "
245 "SETTINGS_QPACK_BLOCKED_STREAMS");
246 return false;
247 }
248
249 if (qpack_encoder()->MaximumDynamicTableCapacity() != 0 &&
250 frame.values.find(SETTINGS_QPACK_MAX_TABLE_CAPACITY) ==
251 frame.values.end()) {
252 CloseConnectionWithDetails(
253 QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
254 "Server accepted 0-RTT but omitted non-default "
255 "SETTINGS_QPACK_MAX_TABLE_CAPACITY");
256 return false;
257 }
258 }
259
260 if (!QuicSpdySession::OnSettingsFrame(frame)) {
261 return false;
262 }
263 std::string settings_frame = HttpEncoder::SerializeSettingsFrame(frame);
264 auto serialized_data = std::make_unique<ApplicationState>(
265 settings_frame.data(), settings_frame.data() + settings_frame.length());
266 GetMutableCryptoStream()->SetServerApplicationStateForResumption(
267 std::move(serialized_data));
268 return true;
269 }
270
271 } // namespace quic
272