OLD | NEW |
| (Empty) |
1 // Copyright 2016 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 "net/quic/quic_client_promised_info.h" | |
6 | |
7 #include <memory> | |
8 | |
9 #include "base/macros.h" | |
10 #include "base/scoped_ptr.h" | |
11 #include "net/gfe2/balsa_headers.h" | |
12 #include "net/quic/quic_client.h" | |
13 #include "net/quic/quic_client_session.h" | |
14 #include "net/quic/quic_spdy_client_stream.h" | |
15 #include "net/quic/quic_utils.h" | |
16 #include "net/quic/spdy_balsa_utils.h" | |
17 #include "net/quic/spdy_utils.h" | |
18 #include "net/quic/test_tools/crypto_test_utils.h" | |
19 #include "net/quic/test_tools/quic_test_utils.h" | |
20 #include "net/util/ipaddress.h" | |
21 #include "testing/base/public/gunit.h" | |
22 | |
23 using SpdyHeaderBlock; | |
24 using BalsaHeaders; | |
25 using testing::StrictMock; | |
26 | |
27 namespace net { | |
28 namespace test { | |
29 | |
30 class QuicClientPromisedInfoPeer { | |
31 public: | |
32 static QuicAlarm* GetAlarm(QuicClientPromisedInfo* promised_stream) { | |
33 return promised_stream->cleanup_alarm_.get(); | |
34 } | |
35 | |
36 private: | |
37 DISALLOW_COPY_AND_ASSIGN(QuicClientPromisedInfoPeer); | |
38 }; | |
39 | |
40 namespace { | |
41 | |
42 class MockQuicClientSession : public QuicClientSession { | |
43 public: | |
44 explicit MockQuicClientSession(QuicConnection* connection, | |
45 QuicClientPushPromiseIndex* push_promise_index) | |
46 : QuicClientSession( | |
47 DefaultQuicConfig(), | |
48 connection, | |
49 QuicServerId("example.com", 443, PRIVACY_MODE_DISABLED), | |
50 &crypto_config_, | |
51 push_promise_index), | |
52 crypto_config_(CryptoTestUtils::ProofVerifierForTesting()), | |
53 authorized_(true) {} | |
54 ~MockQuicClientSession() override {} | |
55 | |
56 bool IsAuthorized(const string& authority) override { return authorized_; } | |
57 | |
58 void set_authorized(bool authorized) { authorized_ = authorized; } | |
59 | |
60 MOCK_METHOD1(CloseStream, void(QuicStreamId stream_id)); | |
61 | |
62 private: | |
63 QuicCryptoClientConfig crypto_config_; | |
64 | |
65 bool authorized_; | |
66 | |
67 DISALLOW_COPY_AND_ASSIGN(MockQuicClientSession); | |
68 }; | |
69 | |
70 class QuicClientPromisedInfoTest : public ::testing::Test { | |
71 public: | |
72 class StreamVisitor; | |
73 | |
74 QuicClientPromisedInfoTest() | |
75 : connection_(new StrictMock<MockQuicConnection>(&helper_, | |
76 &alarm_factory_, | |
77 Perspective::IS_CLIENT)), | |
78 session_(connection_, &push_promise_index_), | |
79 body_("hello world"), | |
80 promise_id_(gfe_quic::test::kServerDataStreamId1) { | |
81 FLAGS_quic_supports_push_promise = true; | |
82 | |
83 session_.Initialize(); | |
84 | |
85 headers_.SetResponseFirstline("HTTP/1.1", 200, "Ok"); | |
86 headers_.ReplaceOrAppendHeader("content-length", "11"); | |
87 headers_string_ = SpdyBalsaUtils::SerializeResponseHeaders(headers_); | |
88 | |
89 stream_.reset(new QuicSpdyClientStream(gfe_quic::test::kClientDataStreamId1, | |
90 &session_)); | |
91 stream_visitor_.reset(new StreamVisitor()); | |
92 stream_->set_visitor(stream_visitor_.get()); | |
93 | |
94 push_promise_[":path"] = "/bar"; | |
95 push_promise_[":authority"] = "www.google.com"; | |
96 push_promise_[":version"] = "HTTP/1.1"; | |
97 push_promise_[":method"] = "GET"; | |
98 push_promise_[":scheme"] = "https"; | |
99 | |
100 promise_url_ = SpdyUtils::GetUrlFromHeaderBlock(push_promise_); | |
101 serialized_push_promise_ = | |
102 SpdyUtils::SerializeUncompressedHeaders(push_promise_); | |
103 | |
104 client_request_ = push_promise_.Clone(); | |
105 } | |
106 | |
107 class StreamVisitor : public QuicSpdyClientStream::Visitor { | |
108 void OnClose(QuicSpdyStream* stream) override { | |
109 DVLOG(1) << "stream " << stream->id(); | |
110 } | |
111 }; | |
112 | |
113 class PushPromiseDelegate : public QuicClientPushPromiseIndex::Delegate { | |
114 public: | |
115 explicit PushPromiseDelegate(bool match) | |
116 : match_(match), | |
117 rendezvous_fired_(false), | |
118 rendezvous_stream_(nullptr) {} | |
119 | |
120 bool CheckVary(const SpdyHeaderBlock& client_request, | |
121 const SpdyHeaderBlock& promise_request, | |
122 const SpdyHeaderBlock& promise_response) override { | |
123 DVLOG(1) << "match " << match_; | |
124 return match_; | |
125 } | |
126 | |
127 void OnRendezvousResult(QuicSpdyClientStream* stream) override { | |
128 rendezvous_fired_ = true; | |
129 rendezvous_stream_ = stream; | |
130 } | |
131 | |
132 QuicSpdyClientStream* rendezvous_stream() { return rendezvous_stream_; } | |
133 bool rendezvous_fired() { return rendezvous_fired_; } | |
134 | |
135 private: | |
136 bool match_; | |
137 bool rendezvous_fired_; | |
138 QuicSpdyClientStream* rendezvous_stream_; | |
139 }; | |
140 | |
141 void ReceivePromise(QuicStreamId id) { | |
142 stream_->OnStreamHeaders(serialized_push_promise_); | |
143 | |
144 stream_->OnPromiseHeadersComplete(id, serialized_push_promise_.size()); | |
145 } | |
146 | |
147 MockQuicConnectionHelper helper_; | |
148 MockAlarmFactory alarm_factory_; | |
149 StrictMock<MockQuicConnection>* connection_; | |
150 QuicClientPushPromiseIndex push_promise_index_; | |
151 | |
152 MockQuicClientSession session_; | |
153 std::unique_ptr<QuicSpdyClientStream> stream_; | |
154 std::unique_ptr<StreamVisitor> stream_visitor_; | |
155 std::unique_ptr<QuicSpdyClientStream> promised_stream_; | |
156 BalsaHeaders headers_; | |
157 string headers_string_; | |
158 string body_; | |
159 SpdyHeaderBlock push_promise_; | |
160 QuicStreamId promise_id_; | |
161 string promise_url_; | |
162 string serialized_push_promise_; | |
163 SpdyHeaderBlock client_request_; | |
164 }; | |
165 | |
166 TEST_F(QuicClientPromisedInfoTest, PushPromise) { | |
167 ReceivePromise(promise_id_); | |
168 | |
169 // Verify that the promise is in the unclaimed streams map. | |
170 EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr); | |
171 } | |
172 | |
173 TEST_F(QuicClientPromisedInfoTest, PushPromiseCleanupAlarm) { | |
174 ReceivePromise(promise_id_); | |
175 | |
176 // Verify that the promise is in the unclaimed streams map. | |
177 QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_); | |
178 ASSERT_NE(promised, nullptr); | |
179 | |
180 // Fire the alarm that will cancel the promised stream. | |
181 EXPECT_CALL(*connection_, | |
182 SendRstStream(promise_id_, QUIC_STREAM_CANCELLED, 0)); | |
183 alarm_factory_.FireAlarm(QuicClientPromisedInfoPeer::GetAlarm(promised)); | |
184 | |
185 // Verify that the promise is gone after the alarm fires. | |
186 EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr); | |
187 EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr); | |
188 } | |
189 | |
190 TEST_F(QuicClientPromisedInfoTest, PushPromiseInvalidMethod) { | |
191 // Promise with an unsafe method | |
192 push_promise_[":method"] = "PUT"; | |
193 serialized_push_promise_ = | |
194 SpdyUtils::SerializeUncompressedHeaders(push_promise_); | |
195 | |
196 EXPECT_CALL(*connection_, | |
197 SendRstStream(promise_id_, QUIC_INVALID_PROMISE_METHOD, 0)); | |
198 ReceivePromise(promise_id_); | |
199 | |
200 // Verify that the promise headers were ignored | |
201 EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr); | |
202 EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr); | |
203 } | |
204 | |
205 TEST_F(QuicClientPromisedInfoTest, PushPromiseInvalidUrl) { | |
206 // Remove required header field to make URL invalid | |
207 push_promise_.erase(":authority"); | |
208 serialized_push_promise_ = | |
209 SpdyUtils::SerializeUncompressedHeaders(push_promise_); | |
210 | |
211 EXPECT_CALL(*connection_, | |
212 SendRstStream(promise_id_, QUIC_INVALID_PROMISE_URL, 0)); | |
213 ReceivePromise(promise_id_); | |
214 | |
215 // Verify that the promise headers were ignored | |
216 EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr); | |
217 EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr); | |
218 } | |
219 | |
220 TEST_F(QuicClientPromisedInfoTest, PushPromiseInvalidUrl) { | |
221 // Promise with an unsafe method | |
222 push_promise_[":method"] = "PUT"; | |
223 serialized_push_promise_ = | |
224 SpdyUtils::SerializeUncompressedHeaders(push_promise_); | |
225 | |
226 EXPECT_CALL(*connection_, | |
227 SendRstStream(promise_id_, QUIC_INVALID_PROMISE_METHOD, 0)); | |
228 ReceivePromise(promise_id_); | |
229 | |
230 // Verify that the promise headers were ignored | |
231 EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr); | |
232 EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr); | |
233 } | |
234 | |
235 TEST_F(QuicClientPromisedInfoTest, PushPromiseUnauthorizedUrl) { | |
236 session_.set_authorized(false); | |
237 | |
238 EXPECT_CALL(*connection_, | |
239 SendRstStream(promise_id_, QUIC_UNAUTHORIZED_PROMISE_URL, 0)); | |
240 | |
241 ReceivePromise(promise_id_); | |
242 | |
243 QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_); | |
244 ASSERT_EQ(promised, nullptr); | |
245 } | |
246 | |
247 TEST_F(QuicClientPromisedInfoTest, PushPromiseMismatch) { | |
248 ReceivePromise(promise_id_); | |
249 | |
250 QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_); | |
251 ASSERT_NE(promised, nullptr); | |
252 | |
253 // Need to send the promised response headers and initiate the | |
254 // rendezvous for secondary validation to proceed. | |
255 QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>( | |
256 session_.GetOrCreateStream(promise_id_)); | |
257 promise_stream->OnStreamHeaders(headers_string_); | |
258 promise_stream->OnStreamHeadersComplete(false, headers_string_.size()); | |
259 | |
260 PushPromiseDelegate delegate(/*match=*/false); | |
261 EXPECT_CALL(*connection_, | |
262 SendRstStream(promise_id_, QUIC_PROMISE_VARY_MISMATCH, 0)); | |
263 EXPECT_CALL(session_, CloseStream(promise_id_)); | |
264 | |
265 promised->HandleClientRequest(client_request_, &delegate); | |
266 } | |
267 | |
268 TEST_F(QuicClientPromisedInfoTest, PushPromiseVaryWaits) { | |
269 ReceivePromise(promise_id_); | |
270 | |
271 QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_); | |
272 ASSERT_NE(promised, nullptr); | |
273 | |
274 // Now initiate rendezvous. | |
275 PushPromiseDelegate delegate(/*match=*/true); | |
276 promised->HandleClientRequest(std::move(client_request_), &delegate); | |
277 | |
278 // Promise is still there, waiting for response. | |
279 EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr); | |
280 | |
281 // Send Response, should trigger promise validation and complete rendezvous | |
282 QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>( | |
283 session_.GetOrCreateStream(promise_id_)); | |
284 ASSERT_NE(promise_stream, nullptr); | |
285 promise_stream->OnStreamHeaders(headers_string_); | |
286 promise_stream->OnStreamHeadersComplete(false, headers_string_.size()); | |
287 | |
288 // Promise is gone | |
289 EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr); | |
290 } | |
291 | |
292 TEST_F(QuicClientPromisedInfoTest, PushPromiseVaryNoWait) { | |
293 ReceivePromise(promise_id_); | |
294 | |
295 QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_); | |
296 ASSERT_NE(promised, nullptr); | |
297 | |
298 QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>( | |
299 session_.GetOrCreateStream(promise_id_)); | |
300 ASSERT_NE(promise_stream, nullptr); | |
301 | |
302 // Send Response, should trigger promise validation and complete rendezvous | |
303 promise_stream->OnStreamHeaders(headers_string_); | |
304 promise_stream->OnStreamHeadersComplete(false, headers_string_.size()); | |
305 | |
306 // Now initiate rendezvous. | |
307 PushPromiseDelegate delegate(/*match=*/true); | |
308 promised->HandleClientRequest(std::move(client_request_), &delegate); | |
309 | |
310 // Promise is gone | |
311 EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr); | |
312 // Have a push stream | |
313 EXPECT_TRUE(delegate.rendezvous_fired()); | |
314 | |
315 EXPECT_NE(delegate.rendezvous_stream(), nullptr); | |
316 } | |
317 | |
318 TEST_F(QuicClientPromisedInfoTest, PushPromiseWaitCancels) { | |
319 ReceivePromise(promise_id_); | |
320 | |
321 QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_); | |
322 ASSERT_NE(promised, nullptr); | |
323 | |
324 // Now initiate rendezvous. | |
325 PushPromiseDelegate delegate(/*match=*/true); | |
326 promised->HandleClientRequest(std::move(client_request_), &delegate); | |
327 | |
328 // Promise is still there, waiting for response. | |
329 EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr); | |
330 | |
331 // Create response stream, but no data yet. | |
332 session_.GetOrCreateStream(promise_id_); | |
333 | |
334 // Fire the alarm that will cancel the promised stream. | |
335 EXPECT_CALL(session_, CloseStream(promise_id_)); | |
336 EXPECT_CALL(*connection_, | |
337 SendRstStream(promise_id_, QUIC_STREAM_CANCELLED, 0)); | |
338 promised->Cancel(); | |
339 | |
340 // Promise is gone | |
341 EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr); | |
342 } | |
343 | |
344 TEST_F(QuicClientPromisedInfoTest, PushPromiseDataClosed) { | |
345 ReceivePromise(promise_id_); | |
346 | |
347 QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_); | |
348 ASSERT_NE(promised, nullptr); | |
349 | |
350 QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>( | |
351 session_.GetOrCreateStream(promise_id_)); | |
352 ASSERT_NE(promise_stream, nullptr); | |
353 | |
354 // Send response, rendezvous will be able to finish synchronously. | |
355 promise_stream->OnStreamHeaders(headers_string_); | |
356 promise_stream->OnStreamHeadersComplete(false, headers_string_.size()); | |
357 | |
358 EXPECT_CALL(session_, CloseStream(promise_id_)); | |
359 EXPECT_CALL(*connection_, | |
360 SendRstStream(promise_id_, QUIC_STREAM_PEER_GOING_AWAY, 0)); | |
361 session_.SendRstStream(promise_id_, QUIC_STREAM_PEER_GOING_AWAY, 0); | |
362 | |
363 // Now initiate rendezvous. | |
364 PushPromiseDelegate delegate(/*match=*/true); | |
365 EXPECT_EQ( | |
366 promised->HandleClientRequest(std::move(client_request_), &delegate), | |
367 QUIC_FAILURE); | |
368 | |
369 // Got an indication of the stream failure, client should retry | |
370 // request. | |
371 EXPECT_FALSE(delegate.rendezvous_fired()); | |
372 EXPECT_EQ(delegate.rendezvous_stream(), nullptr); | |
373 | |
374 // Promise is gone | |
375 EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr); | |
376 } | |
377 | |
378 } // namespace | |
379 } // namespace test | |
380 } // namespace net | |
OLD | NEW |