| 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 |