• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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