1 // Copyright 2019 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 "third_party/blink/renderer/modules/webtransport/quic_transport.h"
6
7 #include <array>
8 #include <memory>
9 #include <utility>
10
11 #include "base/containers/span.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/test/mock_callback.h"
14 #include "mojo/public/cpp/bindings/receiver_set.h"
15 #include "services/network/public/mojom/quic_transport.mojom-blink.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
19 #include "third_party/blink/public/mojom/webtransport/quic_transport_connector.mojom-blink.h"
20 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
21 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
22 #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
23 #include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer.h"
24 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
25 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
26 #include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
27 #include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
28 #include "third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h"
29 #include "third_party/blink/renderer/bindings/modules/v8/v8_bidirectional_stream.h"
30 #include "third_party/blink/renderer/bindings/modules/v8/v8_quic_transport_options.h"
31 #include "third_party/blink/renderer/bindings/modules/v8/v8_receive_stream.h"
32 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_dtls_fingerprint.h"
33 #include "third_party/blink/renderer/bindings/modules/v8/v8_send_stream.h"
34 #include "third_party/blink/renderer/bindings/modules/v8/v8_web_transport_close_info.h"
35 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
36 #include "third_party/blink/renderer/core/streams/readable_stream.h"
37 #include "third_party/blink/renderer/core/streams/readable_stream_default_reader.h"
38 #include "third_party/blink/renderer/core/streams/writable_stream.h"
39 #include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
40 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
41 #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
42 #include "third_party/blink/renderer/modules/webtransport/receive_stream.h"
43 #include "third_party/blink/renderer/modules/webtransport/send_stream.h"
44 #include "third_party/blink/renderer/modules/webtransport/test_utils.h"
45 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
46 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
47 #include "third_party/blink/renderer/platform/heap/persistent.h"
48 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
49 #include "third_party/blink/renderer/platform/wtf/deque.h"
50 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
51 #include "v8/include/v8.h"
52
53 namespace blink {
54
55 namespace {
56
57 using ::testing::_;
58 using ::testing::ElementsAre;
59 using ::testing::Invoke;
60 using ::testing::Mock;
61 using ::testing::StrictMock;
62 using ::testing::Truly;
63 using ::testing::Unused;
64
65 class QuicTransportConnector final
66 : public mojom::blink::QuicTransportConnector {
67 public:
68 struct ConnectArgs {
ConnectArgsblink::__anonba0457960111::QuicTransportConnector::ConnectArgs69 ConnectArgs(
70 const KURL& url,
71 Vector<network::mojom::blink::QuicTransportCertificateFingerprintPtr>
72 fingerprints,
73 mojo::PendingRemote<network::mojom::blink::QuicTransportHandshakeClient>
74 handshake_client)
75 : url(url),
76 fingerprints(std::move(fingerprints)),
77 handshake_client(std::move(handshake_client)) {}
78
79 KURL url;
80 Vector<network::mojom::blink::QuicTransportCertificateFingerprintPtr>
81 fingerprints;
82 mojo::PendingRemote<network::mojom::blink::QuicTransportHandshakeClient>
83 handshake_client;
84 };
85
Connect(const KURL & url,Vector<network::mojom::blink::QuicTransportCertificateFingerprintPtr> fingerprints,mojo::PendingRemote<network::mojom::blink::QuicTransportHandshakeClient> handshake_client)86 void Connect(
87 const KURL& url,
88 Vector<network::mojom::blink::QuicTransportCertificateFingerprintPtr>
89 fingerprints,
90 mojo::PendingRemote<network::mojom::blink::QuicTransportHandshakeClient>
91 handshake_client) override {
92 connect_args_.push_back(
93 ConnectArgs(url, std::move(fingerprints), std::move(handshake_client)));
94 }
95
TakeConnectArgs()96 Vector<ConnectArgs> TakeConnectArgs() { return std::move(connect_args_); }
97
Bind(mojo::PendingReceiver<mojom::blink::QuicTransportConnector> receiver)98 void Bind(
99 mojo::PendingReceiver<mojom::blink::QuicTransportConnector> receiver) {
100 receiver_set_.Add(this, std::move(receiver));
101 }
102
103 private:
104 mojo::ReceiverSet<mojom::blink::QuicTransportConnector> receiver_set_;
105 Vector<ConnectArgs> connect_args_;
106 };
107
108 class MockQuicTransport : public network::mojom::blink::QuicTransport {
109 public:
MockQuicTransport(mojo::PendingReceiver<network::mojom::blink::QuicTransport> pending_receiver)110 MockQuicTransport(mojo::PendingReceiver<network::mojom::blink::QuicTransport>
111 pending_receiver)
112 : receiver_(this, std::move(pending_receiver)) {}
113
114 MOCK_METHOD2(SendDatagram,
115 void(base::span<const uint8_t> data,
116 base::OnceCallback<void(bool)> callback));
117
118 MOCK_METHOD3(CreateStream,
119 void(mojo::ScopedDataPipeConsumerHandle readable,
120 mojo::ScopedDataPipeProducerHandle writable,
121 base::OnceCallback<void(bool, uint32_t)> callback));
122
123 MOCK_METHOD1(
124 AcceptBidirectionalStream,
125 void(base::OnceCallback<void(uint32_t,
126 mojo::ScopedDataPipeConsumerHandle,
127 mojo::ScopedDataPipeProducerHandle)>));
128
129 MOCK_METHOD1(AcceptUnidirectionalStream,
130 void(base::OnceCallback<
131 void(uint32_t, mojo::ScopedDataPipeConsumerHandle)>));
132
SendFin(uint32_t stream_id)133 void SendFin(uint32_t stream_id) override {}
AbortStream(uint32_t stream_id,uint64_t code)134 void AbortStream(uint32_t stream_id, uint64_t code) override {}
135
136 private:
137 mojo::Receiver<network::mojom::blink::QuicTransport> receiver_;
138 };
139
140 class QuicTransportTest : public ::testing::Test {
141 public:
142 using AcceptUnidirectionalStreamCallback =
143 base::OnceCallback<void(uint32_t, mojo::ScopedDataPipeConsumerHandle)>;
144 using AcceptBidirectionalStreamCallback =
145 base::OnceCallback<void(uint32_t,
146 mojo::ScopedDataPipeConsumerHandle,
147 mojo::ScopedDataPipeProducerHandle)>;
148
AddBinder(const V8TestingScope & scope)149 void AddBinder(const V8TestingScope& scope) {
150 interface_broker_ =
151 &scope.GetExecutionContext()->GetBrowserInterfaceBroker();
152 interface_broker_->SetBinderForTesting(
153 mojom::blink::QuicTransportConnector::Name_,
154 base::BindRepeating(&QuicTransportTest::BindConnector,
155 weak_ptr_factory_.GetWeakPtr()));
156 }
157
EmptyOptions()158 static QuicTransportOptions* EmptyOptions() {
159 return MakeGarbageCollected<QuicTransportOptions>();
160 }
161
162 // Creates a QuicTransport object with the given |url|.
Create(const V8TestingScope & scope,const String & url)163 QuicTransport* Create(const V8TestingScope& scope, const String& url) {
164 AddBinder(scope);
165 return QuicTransport::Create(scope.GetScriptState(), url, EmptyOptions(),
166 ASSERT_NO_EXCEPTION);
167 }
168
169 // Connects a QuicTransport object. Runs the event loop.
ConnectSuccessfully(QuicTransport * quic_transport)170 void ConnectSuccessfully(QuicTransport* quic_transport) {
171 DCHECK(!mock_quic_transport_) << "Only one connection supported, sorry";
172
173 test::RunPendingTasks();
174
175 auto args = connector_.TakeConnectArgs();
176 if (args.size() != 1u) {
177 ADD_FAILURE() << "args.size() should be 1, but is " << args.size();
178 return;
179 }
180
181 mojo::Remote<network::mojom::blink::QuicTransportHandshakeClient>
182 handshake_client(std::move(args[0].handshake_client));
183
184 mojo::PendingRemote<network::mojom::blink::QuicTransport>
185 quic_transport_to_pass;
186 mojo::PendingRemote<network::mojom::blink::QuicTransportClient>
187 client_remote;
188
189 mock_quic_transport_ = std::make_unique<StrictMock<MockQuicTransport>>(
190 quic_transport_to_pass.InitWithNewPipeAndPassReceiver());
191
192 // These are called on every connection, so expect them in every test.
193 EXPECT_CALL(*mock_quic_transport_, AcceptUnidirectionalStream(_))
194 .WillRepeatedly([this](AcceptUnidirectionalStreamCallback callback) {
195 pending_unidirectional_accept_callbacks_.push_back(
196 std::move(callback));
197 });
198
199 EXPECT_CALL(*mock_quic_transport_, AcceptBidirectionalStream(_))
200 .WillRepeatedly([this](AcceptBidirectionalStreamCallback callback) {
201 pending_bidirectional_accept_callbacks_.push_back(
202 std::move(callback));
203 });
204
205 handshake_client->OnConnectionEstablished(
206 std::move(quic_transport_to_pass),
207 client_remote.InitWithNewPipeAndPassReceiver());
208 client_remote_.Bind(std::move(client_remote));
209
210 test::RunPendingTasks();
211 }
212
213 // Creates, connects and returns a QuicTransport object with the given |url|.
214 // Runs the event loop.
CreateAndConnectSuccessfully(const V8TestingScope & scope,const String & url)215 QuicTransport* CreateAndConnectSuccessfully(const V8TestingScope& scope,
216 const String& url) {
217 auto* quic_transport = Create(scope, url);
218 ConnectSuccessfully(quic_transport);
219 return quic_transport;
220 }
221
CreateSendStreamSuccessfully(const V8TestingScope & scope,QuicTransport * quic_transport)222 SendStream* CreateSendStreamSuccessfully(const V8TestingScope& scope,
223 QuicTransport* quic_transport) {
224 EXPECT_CALL(*mock_quic_transport_, CreateStream(_, _, _))
225 .WillOnce([this](mojo::ScopedDataPipeConsumerHandle handle, Unused,
226 base::OnceCallback<void(bool, uint32_t)> callback) {
227 send_stream_consumer_handle_ = std::move(handle);
228 std::move(callback).Run(true, next_stream_id_++);
229 });
230
231 auto* script_state = scope.GetScriptState();
232 ScriptPromise send_stream_promise =
233 quic_transport->createSendStream(script_state, ASSERT_NO_EXCEPTION);
234 ScriptPromiseTester tester(script_state, send_stream_promise);
235
236 tester.WaitUntilSettled();
237
238 EXPECT_TRUE(tester.IsFulfilled());
239 auto* send_stream = V8SendStream::ToImplWithTypeCheck(
240 scope.GetIsolate(), tester.Value().V8Value());
241 EXPECT_TRUE(send_stream);
242 return send_stream;
243 }
244
DoAcceptUnidirectionalStream()245 mojo::ScopedDataPipeProducerHandle DoAcceptUnidirectionalStream() {
246 mojo::ScopedDataPipeProducerHandle producer;
247 mojo::ScopedDataPipeConsumerHandle consumer;
248
249 // There's no good way to handle failure to create the pipe, so just
250 // continue.
251 CreateDataPipeForWebTransportTests(&producer, &consumer);
252
253 std::move(pending_unidirectional_accept_callbacks_.front())
254 .Run(next_stream_id_++, std::move(consumer));
255 pending_unidirectional_accept_callbacks_.pop_front();
256
257 return producer;
258 }
259
ReadReceiveStream(const V8TestingScope & scope,QuicTransport * quic_transport)260 ReceiveStream* ReadReceiveStream(const V8TestingScope& scope,
261 QuicTransport* quic_transport) {
262 ReadableStream* streams = quic_transport->receiveStreams();
263
264 v8::Local<v8::Value> v8value = ReadValueFromStream(scope, streams);
265
266 ReceiveStream* receive_stream =
267 V8ReceiveStream::ToImplWithTypeCheck(scope.GetIsolate(), v8value);
268 EXPECT_TRUE(receive_stream);
269
270 return receive_stream;
271 }
272
BindConnector(mojo::ScopedMessagePipeHandle handle)273 void BindConnector(mojo::ScopedMessagePipeHandle handle) {
274 connector_.Bind(mojo::PendingReceiver<mojom::blink::QuicTransportConnector>(
275 std::move(handle)));
276 }
277
TearDown()278 void TearDown() override {
279 if (!interface_broker_)
280 return;
281 interface_broker_->SetBinderForTesting(
282 mojom::blink::QuicTransportConnector::Name_, {});
283 }
284
285 BrowserInterfaceBrokerProxy* interface_broker_ = nullptr;
286 WTF::Deque<AcceptUnidirectionalStreamCallback>
287 pending_unidirectional_accept_callbacks_;
288 WTF::Deque<AcceptBidirectionalStreamCallback>
289 pending_bidirectional_accept_callbacks_;
290 QuicTransportConnector connector_;
291 std::unique_ptr<MockQuicTransport> mock_quic_transport_;
292 mojo::Remote<network::mojom::blink::QuicTransportClient> client_remote_;
293 uint32_t next_stream_id_ = 0;
294 mojo::ScopedDataPipeConsumerHandle send_stream_consumer_handle_;
295
296 base::WeakPtrFactory<QuicTransportTest> weak_ptr_factory_{this};
297 };
298
TEST_F(QuicTransportTest,FailWithNullURL)299 TEST_F(QuicTransportTest, FailWithNullURL) {
300 V8TestingScope scope;
301 auto& exception_state = scope.GetExceptionState();
302 QuicTransport::Create(scope.GetScriptState(), String(), EmptyOptions(),
303 exception_state);
304 EXPECT_TRUE(exception_state.HadException());
305 EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
306 exception_state.Code());
307 }
308
TEST_F(QuicTransportTest,FailWithEmptyURL)309 TEST_F(QuicTransportTest, FailWithEmptyURL) {
310 V8TestingScope scope;
311 auto& exception_state = scope.GetExceptionState();
312 QuicTransport::Create(scope.GetScriptState(), String(""), EmptyOptions(),
313 exception_state);
314 EXPECT_TRUE(exception_state.HadException());
315 EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
316 exception_state.Code());
317 EXPECT_EQ("The URL '' is invalid.", exception_state.Message());
318 }
319
TEST_F(QuicTransportTest,FailWithNoScheme)320 TEST_F(QuicTransportTest, FailWithNoScheme) {
321 V8TestingScope scope;
322 auto& exception_state = scope.GetExceptionState();
323 QuicTransport::Create(scope.GetScriptState(), String("no-scheme"),
324 EmptyOptions(), exception_state);
325 EXPECT_TRUE(exception_state.HadException());
326 EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
327 exception_state.Code());
328 EXPECT_EQ("The URL 'no-scheme' is invalid.", exception_state.Message());
329 }
330
TEST_F(QuicTransportTest,FailWithHttpsURL)331 TEST_F(QuicTransportTest, FailWithHttpsURL) {
332 V8TestingScope scope;
333 auto& exception_state = scope.GetExceptionState();
334 QuicTransport::Create(scope.GetScriptState(), String("https://example.com/"),
335 EmptyOptions(), exception_state);
336 EXPECT_TRUE(exception_state.HadException());
337 EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
338 exception_state.Code());
339 EXPECT_EQ(
340 "The URL's scheme must be 'quic-transport'. 'https' is not allowed.",
341 exception_state.Message());
342 }
343
TEST_F(QuicTransportTest,FailWithNoHost)344 TEST_F(QuicTransportTest, FailWithNoHost) {
345 V8TestingScope scope;
346 auto& exception_state = scope.GetExceptionState();
347 QuicTransport::Create(scope.GetScriptState(), String("quic-transport:///"),
348 EmptyOptions(), exception_state);
349 EXPECT_TRUE(exception_state.HadException());
350 EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
351 exception_state.Code());
352 EXPECT_EQ("The URL 'quic-transport:///' is invalid.",
353 exception_state.Message());
354 }
355
TEST_F(QuicTransportTest,FailWithURLFragment)356 TEST_F(QuicTransportTest, FailWithURLFragment) {
357 V8TestingScope scope;
358 auto& exception_state = scope.GetExceptionState();
359 QuicTransport::Create(scope.GetScriptState(),
360 String("quic-transport://example.com/#failing"),
361 EmptyOptions(), exception_state);
362 EXPECT_TRUE(exception_state.HadException());
363 EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
364 exception_state.Code());
365 EXPECT_EQ(
366 "The URL contains a fragment identifier ('#failing'). Fragment "
367 "identifiers are not allowed in QuicTransport URLs.",
368 exception_state.Message());
369 }
370
TEST_F(QuicTransportTest,FailByCSP)371 TEST_F(QuicTransportTest, FailByCSP) {
372 V8TestingScope scope;
373 auto& exception_state = scope.GetExceptionState();
374 scope.GetExecutionContext()
375 ->GetContentSecurityPolicyForCurrentWorld()
376 ->DidReceiveHeader("connect-src 'none'",
377 network::mojom::ContentSecurityPolicyType::kEnforce,
378 network::mojom::ContentSecurityPolicySource::kHTTP);
379 QuicTransport::Create(scope.GetScriptState(),
380 String("quic-transport://example.com/"), EmptyOptions(),
381 exception_state);
382 EXPECT_TRUE(exception_state.HadException());
383 EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSecurityError),
384 exception_state.Code());
385 EXPECT_EQ("Failed to connect to 'quic-transport://example.com/'",
386 exception_state.Message());
387 }
388
TEST_F(QuicTransportTest,PassCSP)389 TEST_F(QuicTransportTest, PassCSP) {
390 V8TestingScope scope;
391 // This doesn't work without the https:// prefix, even thought it should
392 // according to
393 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src.
394 auto& exception_state = scope.GetExceptionState();
395 scope.GetExecutionContext()
396 ->GetContentSecurityPolicyForCurrentWorld()
397 ->DidReceiveHeader("connect-src quic-transport://example.com",
398 network::mojom::ContentSecurityPolicyType::kEnforce,
399 network::mojom::ContentSecurityPolicySource::kHTTP);
400 QuicTransport::Create(scope.GetScriptState(),
401 String("quic-transport://example.com/"), EmptyOptions(),
402 exception_state);
403 EXPECT_FALSE(exception_state.HadException());
404 }
405
TEST_F(QuicTransportTest,SendConnect)406 TEST_F(QuicTransportTest, SendConnect) {
407 V8TestingScope scope;
408 AddBinder(scope);
409 auto* quic_transport = QuicTransport::Create(
410 scope.GetScriptState(), String("quic-transport://example.com/"),
411 EmptyOptions(), ASSERT_NO_EXCEPTION);
412
413 test::RunPendingTasks();
414
415 auto args = connector_.TakeConnectArgs();
416 ASSERT_EQ(1u, args.size());
417 EXPECT_EQ(KURL("quic-transport://example.com/"), args[0].url);
418 EXPECT_TRUE(args[0].fingerprints.IsEmpty());
419 EXPECT_TRUE(quic_transport->HasPendingActivity());
420 }
421
TEST_F(QuicTransportTest,SuccessfulConnect)422 TEST_F(QuicTransportTest, SuccessfulConnect) {
423 V8TestingScope scope;
424 auto* quic_transport =
425 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
426 ScriptPromiseTester ready_tester(scope.GetScriptState(),
427 quic_transport->ready());
428
429 EXPECT_TRUE(quic_transport->HasPendingActivity());
430
431 ready_tester.WaitUntilSettled();
432 EXPECT_TRUE(ready_tester.IsFulfilled());
433 }
434
TEST_F(QuicTransportTest,FailedConnect)435 TEST_F(QuicTransportTest, FailedConnect) {
436 V8TestingScope scope;
437 AddBinder(scope);
438 auto* quic_transport = QuicTransport::Create(
439 scope.GetScriptState(), String("quic-transport://example.com/"),
440 EmptyOptions(), ASSERT_NO_EXCEPTION);
441 ScriptPromiseTester ready_tester(scope.GetScriptState(),
442 quic_transport->ready());
443 ScriptPromiseTester closed_tester(scope.GetScriptState(),
444 quic_transport->closed());
445
446 test::RunPendingTasks();
447
448 auto args = connector_.TakeConnectArgs();
449 ASSERT_EQ(1u, args.size());
450
451 mojo::Remote<network::mojom::blink::QuicTransportHandshakeClient>
452 handshake_client(std::move(args[0].handshake_client));
453
454 handshake_client->OnHandshakeFailed(nullptr);
455
456 test::RunPendingTasks();
457 EXPECT_FALSE(quic_transport->HasPendingActivity());
458 EXPECT_TRUE(ready_tester.IsRejected());
459 EXPECT_TRUE(closed_tester.IsRejected());
460 }
461
TEST_F(QuicTransportTest,SendConnectWithFingerprint)462 TEST_F(QuicTransportTest, SendConnectWithFingerprint) {
463 V8TestingScope scope;
464 AddBinder(scope);
465 auto* fingerprints = MakeGarbageCollected<RTCDtlsFingerprint>();
466 fingerprints->setAlgorithm("sha-256");
467 fingerprints->setValue(
468 "ED:3D:D7:C3:67:10:94:68:D1:DC:D1:26:5C:B2:74:D7:1C:A2:63:3E:94:94:C0:84:"
469 "39:D6:64:FA:08:B9:77:37");
470 auto* options = MakeGarbageCollected<QuicTransportOptions>();
471 options->setServerCertificateFingerprints({fingerprints});
472 QuicTransport::Create(scope.GetScriptState(),
473 String("quic-transport://example.com/"), options,
474 ASSERT_NO_EXCEPTION);
475
476 test::RunPendingTasks();
477
478 auto args = connector_.TakeConnectArgs();
479 ASSERT_EQ(1u, args.size());
480 ASSERT_EQ(1u, args[0].fingerprints.size());
481 EXPECT_EQ(args[0].fingerprints[0]->algorithm, "sha-256");
482 EXPECT_EQ(args[0].fingerprints[0]->fingerprint,
483 "ED:3D:D7:C3:67:10:94:68:D1:DC:D1:26:5C:B2:74:D7:1C:A2:63:3E:94:94:"
484 "C0:84:39:D6:64:FA:08:B9:77:37");
485 }
486
TEST_F(QuicTransportTest,CloseDuringConnect)487 TEST_F(QuicTransportTest, CloseDuringConnect) {
488 V8TestingScope scope;
489 AddBinder(scope);
490 auto* quic_transport = QuicTransport::Create(
491 scope.GetScriptState(), String("quic-transport://example.com/"),
492 EmptyOptions(), ASSERT_NO_EXCEPTION);
493 ScriptPromiseTester ready_tester(scope.GetScriptState(),
494 quic_transport->ready());
495 ScriptPromiseTester closed_tester(scope.GetScriptState(),
496 quic_transport->closed());
497
498 test::RunPendingTasks();
499
500 auto args = connector_.TakeConnectArgs();
501 ASSERT_EQ(1u, args.size());
502
503 quic_transport->close(nullptr);
504
505 test::RunPendingTasks();
506
507 EXPECT_FALSE(quic_transport->HasPendingActivity());
508 EXPECT_TRUE(ready_tester.IsRejected());
509 EXPECT_TRUE(closed_tester.IsFulfilled());
510 }
511
TEST_F(QuicTransportTest,CloseAfterConnection)512 TEST_F(QuicTransportTest, CloseAfterConnection) {
513 V8TestingScope scope;
514 auto* quic_transport =
515 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
516 ScriptPromiseTester ready_tester(scope.GetScriptState(),
517 quic_transport->ready());
518 ScriptPromiseTester closed_tester(scope.GetScriptState(),
519 quic_transport->closed());
520
521 WebTransportCloseInfo close_info;
522 close_info.setErrorCode(42);
523 close_info.setReason("because");
524 quic_transport->close(&close_info);
525
526 test::RunPendingTasks();
527
528 // TODO(ricea): Check that the close info is sent through correctly, once we
529 // start sending it.
530
531 EXPECT_FALSE(quic_transport->HasPendingActivity());
532 EXPECT_TRUE(ready_tester.IsFulfilled());
533 EXPECT_TRUE(closed_tester.IsFulfilled());
534
535 // Calling close again does nothing.
536 quic_transport->close(nullptr);
537 }
538
539 // A live connection will be kept alive even if there is no explicit reference.
540 // When the underlying connection is shut down, the connection will be swept.
TEST_F(QuicTransportTest,GarbageCollection)541 TEST_F(QuicTransportTest, GarbageCollection) {
542 V8TestingScope scope;
543
544 WeakPersistent<QuicTransport> quic_transport;
545
546 {
547 // The streams created when creating a QuicTransport create some v8 handles.
548 // To ensure these are collected, we need to create a handle scope. This is
549 // not a problem for garbage collection in normal operation.
550 v8::HandleScope handle_scope(scope.GetIsolate());
551 quic_transport =
552 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
553 }
554
555 // Pretend the stack is empty. This will avoid accidentally treating any
556 // copies of the |quic_transport| pointer as references.
557 ThreadState::Current()->CollectAllGarbageForTesting();
558
559 EXPECT_TRUE(quic_transport);
560
561 quic_transport->close(nullptr);
562
563 test::RunPendingTasks();
564
565 ThreadState::Current()->CollectAllGarbageForTesting();
566
567 EXPECT_FALSE(quic_transport);
568 }
569
TEST_F(QuicTransportTest,GarbageCollectMojoConnectionError)570 TEST_F(QuicTransportTest, GarbageCollectMojoConnectionError) {
571 V8TestingScope scope;
572
573 WeakPersistent<QuicTransport> quic_transport;
574
575 {
576 v8::HandleScope handle_scope(scope.GetIsolate());
577 quic_transport =
578 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
579 }
580
581 ScriptPromiseTester closed_tester(scope.GetScriptState(),
582 quic_transport->closed());
583
584 // Closing the server-side of the pipe causes a mojo connection error.
585 client_remote_.reset();
586
587 test::RunPendingTasks();
588
589 ThreadState::Current()->CollectAllGarbageForTesting();
590
591 EXPECT_FALSE(quic_transport);
592 EXPECT_TRUE(closed_tester.IsRejected());
593 }
594
TEST_F(QuicTransportTest,SendDatagram)595 TEST_F(QuicTransportTest, SendDatagram) {
596 V8TestingScope scope;
597 auto* quic_transport =
598 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
599
600 EXPECT_CALL(*mock_quic_transport_, SendDatagram(ElementsAre('A'), _))
601 .WillOnce(Invoke([](base::span<const uint8_t>,
602 MockQuicTransport::SendDatagramCallback callback) {
603 std::move(callback).Run(true);
604 }));
605
606 auto* writable = quic_transport->sendDatagrams();
607 auto* script_state = scope.GetScriptState();
608 auto* writer = writable->getWriter(script_state, ASSERT_NO_EXCEPTION);
609 auto* chunk = DOMUint8Array::Create(1);
610 *chunk->Data() = 'A';
611 ScriptPromise result =
612 writer->write(script_state, ScriptValue::From(script_state, chunk),
613 ASSERT_NO_EXCEPTION);
614 ScriptPromiseTester tester(script_state, result);
615 tester.WaitUntilSettled();
616 EXPECT_TRUE(tester.IsFulfilled());
617 EXPECT_TRUE(tester.Value().IsUndefined());
618 }
619
TEST_F(QuicTransportTest,SendDatagramBeforeConnect)620 TEST_F(QuicTransportTest, SendDatagramBeforeConnect) {
621 V8TestingScope scope;
622 auto* quic_transport = Create(scope, "quic-transport://example.com");
623
624 auto* writable = quic_transport->sendDatagrams();
625 auto* script_state = scope.GetScriptState();
626 auto* writer = writable->getWriter(script_state, ASSERT_NO_EXCEPTION);
627 auto* chunk = DOMUint8Array::Create(1);
628 *chunk->Data() = 'A';
629 ScriptPromise result =
630 writer->write(script_state, ScriptValue::From(script_state, chunk),
631 ASSERT_NO_EXCEPTION);
632
633 ConnectSuccessfully(quic_transport);
634
635 // No datagram is sent.
636
637 ScriptPromiseTester tester(script_state, result);
638 tester.WaitUntilSettled();
639 EXPECT_TRUE(tester.IsFulfilled());
640 EXPECT_TRUE(tester.Value().IsUndefined());
641 }
642
TEST_F(QuicTransportTest,SendDatagramAfterClose)643 TEST_F(QuicTransportTest, SendDatagramAfterClose) {
644 V8TestingScope scope;
645 auto* quic_transport =
646 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
647
648 quic_transport->close(nullptr);
649 test::RunPendingTasks();
650
651 auto* writable = quic_transport->sendDatagrams();
652 auto* script_state = scope.GetScriptState();
653 auto* writer = writable->getWriter(script_state, ASSERT_NO_EXCEPTION);
654
655 auto* chunk = DOMUint8Array::Create(1);
656 *chunk->Data() = 'A';
657 ScriptPromise result =
658 writer->write(script_state, ScriptValue::From(script_state, chunk),
659 ASSERT_NO_EXCEPTION);
660
661 // No datagram is sent.
662
663 ScriptPromiseTester tester(script_state, result);
664 tester.WaitUntilSettled();
665 EXPECT_TRUE(tester.IsRejected());
666 }
667
GetValueAsVector(ScriptState * script_state,ScriptValue iterator_result)668 Vector<uint8_t> GetValueAsVector(ScriptState* script_state,
669 ScriptValue iterator_result) {
670 bool done = false;
671 v8::Local<v8::Value> value;
672 if (!V8UnpackIteratorResult(script_state,
673 iterator_result.V8Value().As<v8::Object>(), &done)
674 .ToLocal(&value)) {
675 ADD_FAILURE() << "unable to unpack iterator_result";
676 return {};
677 }
678
679 EXPECT_FALSE(done);
680 auto* array =
681 V8Uint8Array::ToImplWithTypeCheck(script_state->GetIsolate(), value);
682 if (!array) {
683 ADD_FAILURE() << "value was not a Uint8Array";
684 return {};
685 }
686
687 Vector<uint8_t> result;
688 result.Append(array->Data(), array->length());
689 return result;
690 }
691
TEST_F(QuicTransportTest,ReceiveDatagramBeforeRead)692 TEST_F(QuicTransportTest, ReceiveDatagramBeforeRead) {
693 V8TestingScope scope;
694 auto* quic_transport =
695 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
696
697 const std::array<uint8_t, 1> chunk = {'A'};
698 client_remote_->OnDatagramReceived(chunk);
699
700 test::RunPendingTasks();
701
702 auto* readable = quic_transport->receiveDatagrams();
703 auto* script_state = scope.GetScriptState();
704 auto* reader =
705 readable->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
706 ScriptPromise result = reader->read(script_state, ASSERT_NO_EXCEPTION);
707 ScriptPromiseTester tester(script_state, result);
708 tester.WaitUntilSettled();
709 EXPECT_TRUE(tester.IsFulfilled());
710
711 EXPECT_THAT(GetValueAsVector(script_state, tester.Value()), ElementsAre('A'));
712 }
713
TEST_F(QuicTransportTest,ReceiveDatagramDuringRead)714 TEST_F(QuicTransportTest, ReceiveDatagramDuringRead) {
715 V8TestingScope scope;
716 auto* quic_transport =
717 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
718 auto* readable = quic_transport->receiveDatagrams();
719 auto* script_state = scope.GetScriptState();
720 auto* reader =
721 readable->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
722 ScriptPromise result = reader->read(script_state, ASSERT_NO_EXCEPTION);
723
724 const std::array<uint8_t, 1> chunk = {'A'};
725 client_remote_->OnDatagramReceived(chunk);
726
727 ScriptPromiseTester tester(script_state, result);
728 tester.WaitUntilSettled();
729 EXPECT_TRUE(tester.IsFulfilled());
730
731 EXPECT_THAT(GetValueAsVector(script_state, tester.Value()), ElementsAre('A'));
732 }
733
734 // This test documents the current behaviour. If you improve the behaviour,
735 // change the test!
TEST_F(QuicTransportTest,DatagramsAreDropped)736 TEST_F(QuicTransportTest, DatagramsAreDropped) {
737 V8TestingScope scope;
738 auto* quic_transport =
739 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
740
741 // Chunk 'A' gets placed in the readable queue.
742 const std::array<uint8_t, 1> chunk1 = {'A'};
743 client_remote_->OnDatagramReceived(chunk1);
744
745 // Chunk 'B' gets dropped, because there is no space in the readable queue.
746 const std::array<uint8_t, 1> chunk2 = {'B'};
747 client_remote_->OnDatagramReceived(chunk2);
748
749 // Make sure that the calls have run.
750 test::RunPendingTasks();
751
752 auto* readable = quic_transport->receiveDatagrams();
753 auto* script_state = scope.GetScriptState();
754 auto* reader =
755 readable->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
756 ScriptPromise result1 = reader->read(script_state, ASSERT_NO_EXCEPTION);
757 ScriptPromise result2 = reader->read(script_state, ASSERT_NO_EXCEPTION);
758
759 ScriptPromiseTester tester1(script_state, result1);
760 ScriptPromiseTester tester2(script_state, result2);
761 tester1.WaitUntilSettled();
762 EXPECT_TRUE(tester1.IsFulfilled());
763 EXPECT_FALSE(tester2.IsFulfilled());
764
765 EXPECT_THAT(GetValueAsVector(script_state, tester1.Value()),
766 ElementsAre('A'));
767
768 // Chunk 'C' fulfills the pending read.
769 const std::array<uint8_t, 1> chunk3 = {'C'};
770 client_remote_->OnDatagramReceived(chunk3);
771
772 tester2.WaitUntilSettled();
773 EXPECT_TRUE(tester2.IsFulfilled());
774
775 EXPECT_THAT(GetValueAsVector(script_state, tester2.Value()),
776 ElementsAre('C'));
777 }
778
ValidProducerHandle(const mojo::ScopedDataPipeProducerHandle & handle)779 bool ValidProducerHandle(const mojo::ScopedDataPipeProducerHandle& handle) {
780 return handle.is_valid();
781 }
782
ValidConsumerHandle(const mojo::ScopedDataPipeConsumerHandle & handle)783 bool ValidConsumerHandle(const mojo::ScopedDataPipeConsumerHandle& handle) {
784 return handle.is_valid();
785 }
786
TEST_F(QuicTransportTest,CreateSendStream)787 TEST_F(QuicTransportTest, CreateSendStream) {
788 V8TestingScope scope;
789 auto* quic_transport =
790 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
791
792 EXPECT_CALL(*mock_quic_transport_,
793 CreateStream(Truly(ValidConsumerHandle),
794 Not(Truly(ValidProducerHandle)), _))
795 .WillOnce([](Unused, Unused,
796 base::OnceCallback<void(bool, uint32_t)> callback) {
797 std::move(callback).Run(true, 0);
798 });
799
800 auto* script_state = scope.GetScriptState();
801 ScriptPromise send_stream_promise =
802 quic_transport->createSendStream(script_state, ASSERT_NO_EXCEPTION);
803 ScriptPromiseTester tester(script_state, send_stream_promise);
804
805 tester.WaitUntilSettled();
806
807 EXPECT_TRUE(tester.IsFulfilled());
808 auto* send_stream = V8SendStream::ToImplWithTypeCheck(
809 scope.GetIsolate(), tester.Value().V8Value());
810 EXPECT_TRUE(send_stream);
811 }
812
TEST_F(QuicTransportTest,CreateSendStreamBeforeConnect)813 TEST_F(QuicTransportTest, CreateSendStreamBeforeConnect) {
814 V8TestingScope scope;
815
816 auto* script_state = scope.GetScriptState();
817 auto* quic_transport =
818 QuicTransport::Create(script_state, "quic-transport://example.com",
819 EmptyOptions(), ASSERT_NO_EXCEPTION);
820 auto& exception_state = scope.GetExceptionState();
821 ScriptPromise send_stream_promise =
822 quic_transport->createSendStream(script_state, exception_state);
823 EXPECT_TRUE(send_stream_promise.IsEmpty());
824 EXPECT_TRUE(exception_state.HadException());
825 EXPECT_EQ(static_cast<int>(DOMExceptionCode::kNetworkError),
826 exception_state.Code());
827 }
828
TEST_F(QuicTransportTest,CreateSendStreamFailure)829 TEST_F(QuicTransportTest, CreateSendStreamFailure) {
830 V8TestingScope scope;
831 auto* quic_transport =
832 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
833
834 EXPECT_CALL(*mock_quic_transport_, CreateStream(_, _, _))
835 .WillOnce([](Unused, Unused,
836 base::OnceCallback<void(bool, uint32_t)> callback) {
837 std::move(callback).Run(false, 0);
838 });
839
840 auto* script_state = scope.GetScriptState();
841 ScriptPromise send_stream_promise =
842 quic_transport->createSendStream(script_state, ASSERT_NO_EXCEPTION);
843 ScriptPromiseTester tester(script_state, send_stream_promise);
844
845 tester.WaitUntilSettled();
846
847 EXPECT_TRUE(tester.IsRejected());
848 DOMException* exception = V8DOMException::ToImplWithTypeCheck(
849 scope.GetIsolate(), tester.Value().V8Value());
850 EXPECT_EQ(exception->name(), "NetworkError");
851 EXPECT_EQ(exception->message(), "Failed to create send stream.");
852 }
853
854 // Every active stream is kept alive by the QuicTransport object.
TEST_F(QuicTransportTest,SendStreamGarbageCollection)855 TEST_F(QuicTransportTest, SendStreamGarbageCollection) {
856 V8TestingScope scope;
857
858 WeakPersistent<QuicTransport> quic_transport;
859 WeakPersistent<SendStream> send_stream;
860
861 {
862 // The streams created when creating a QuicTransport or SendStream create
863 // some v8 handles. To ensure these are collected, we need to create a
864 // handle scope. This is not a problem for garbage collection in normal
865 // operation.
866 v8::HandleScope handle_scope(scope.GetIsolate());
867
868 quic_transport =
869 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
870 send_stream = CreateSendStreamSuccessfully(scope, quic_transport);
871 }
872
873 ThreadState::Current()->CollectAllGarbageForTesting();
874
875 EXPECT_TRUE(quic_transport);
876 EXPECT_TRUE(send_stream);
877
878 quic_transport->close(nullptr);
879
880 test::RunPendingTasks();
881
882 ThreadState::Current()->CollectAllGarbageForTesting();
883
884 EXPECT_FALSE(quic_transport);
885 EXPECT_FALSE(send_stream);
886 }
887
888 // A live stream will be kept alive even if there is no explicit reference.
889 // When the underlying connection is shut down, the connection will be swept.
TEST_F(QuicTransportTest,SendStreamGarbageCollectionLocalClose)890 TEST_F(QuicTransportTest, SendStreamGarbageCollectionLocalClose) {
891 V8TestingScope scope;
892
893 WeakPersistent<SendStream> send_stream;
894
895 {
896 // The writable stream created when creating a SendStream creates some
897 // v8 handles. To ensure these are collected, we need to create a handle
898 // scope. This is not a problem for garbage collection in normal operation.
899 v8::HandleScope handle_scope(scope.GetIsolate());
900
901 auto* quic_transport =
902 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
903 send_stream = CreateSendStreamSuccessfully(scope, quic_transport);
904 }
905
906 // Pretend the stack is empty. This will avoid accidentally treating any
907 // copies of the |send_stream| pointer as references.
908 ThreadState::Current()->CollectAllGarbageForTesting();
909
910 ASSERT_TRUE(send_stream);
911
912 auto* script_state = scope.GetScriptState();
913
914 ScriptPromise close_promise =
915 send_stream->writable()->close(script_state, ASSERT_NO_EXCEPTION);
916 ScriptPromiseTester tester(script_state, close_promise);
917 tester.WaitUntilSettled();
918 EXPECT_TRUE(tester.IsFulfilled());
919
920 ThreadState::Current()->CollectAllGarbageForTesting();
921
922 EXPECT_FALSE(send_stream);
923 }
924
TEST_F(QuicTransportTest,SendStreamGarbageCollectionRemoteClose)925 TEST_F(QuicTransportTest, SendStreamGarbageCollectionRemoteClose) {
926 V8TestingScope scope;
927
928 WeakPersistent<SendStream> send_stream;
929
930 {
931 v8::HandleScope handle_scope(scope.GetIsolate());
932
933 auto* quic_transport =
934 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
935 send_stream = CreateSendStreamSuccessfully(scope, quic_transport);
936 }
937
938 ThreadState::Current()->CollectAllGarbageForTesting();
939
940 ASSERT_TRUE(send_stream);
941
942 // Close the other end of the pipe.
943 send_stream_consumer_handle_.reset();
944
945 test::RunPendingTasks();
946
947 ThreadState::Current()->CollectAllGarbageForTesting();
948
949 EXPECT_FALSE(send_stream);
950 }
951
952 // A live stream will be kept alive even if there is no explicit reference.
953 // When the underlying connection is shut down, the connection will be swept.
TEST_F(QuicTransportTest,ReceiveStreamGarbageCollectionCancel)954 TEST_F(QuicTransportTest, ReceiveStreamGarbageCollectionCancel) {
955 V8TestingScope scope;
956
957 WeakPersistent<ReceiveStream> receive_stream;
958 mojo::ScopedDataPipeProducerHandle producer;
959
960 {
961 // The readable stream created when creating a ReceiveStream creates some
962 // v8 handles. To ensure these are collected, we need to create a handle
963 // scope. This is not a problem for garbage collection in normal operation.
964 v8::HandleScope handle_scope(scope.GetIsolate());
965
966 auto* quic_transport =
967 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
968
969 producer = DoAcceptUnidirectionalStream();
970 receive_stream = ReadReceiveStream(scope, quic_transport);
971 }
972
973 // Pretend the stack is empty. This will avoid accidentally treating any
974 // copies of the |receive_stream| pointer as references.
975 ThreadState::Current()->CollectAllGarbageForTesting();
976
977 ASSERT_TRUE(receive_stream);
978
979 auto* script_state = scope.GetScriptState();
980
981 ScriptPromise cancel_promise;
982 {
983 // Cancelling also creates v8 handles, so we need a new handle scope as
984 // above.
985 v8::HandleScope handle_scope(scope.GetIsolate());
986 cancel_promise =
987 receive_stream->readable()->cancel(script_state, ASSERT_NO_EXCEPTION);
988 }
989
990 ScriptPromiseTester tester(script_state, cancel_promise);
991 tester.WaitUntilSettled();
992 EXPECT_TRUE(tester.IsFulfilled());
993
994 ThreadState::Current()->CollectAllGarbageForTesting();
995
996 EXPECT_FALSE(receive_stream);
997 }
998
TEST_F(QuicTransportTest,ReceiveStreamGarbageCollectionRemoteClose)999 TEST_F(QuicTransportTest, ReceiveStreamGarbageCollectionRemoteClose) {
1000 V8TestingScope scope;
1001
1002 WeakPersistent<ReceiveStream> receive_stream;
1003 mojo::ScopedDataPipeProducerHandle producer;
1004
1005 {
1006 v8::HandleScope handle_scope(scope.GetIsolate());
1007
1008 auto* quic_transport =
1009 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
1010 producer = DoAcceptUnidirectionalStream();
1011 receive_stream = ReadReceiveStream(scope, quic_transport);
1012 }
1013
1014 ThreadState::Current()->CollectAllGarbageForTesting();
1015
1016 ASSERT_TRUE(receive_stream);
1017
1018 // Close the other end of the pipe.
1019 producer.reset();
1020
1021 test::RunPendingTasks();
1022
1023 ThreadState::Current()->CollectAllGarbageForTesting();
1024
1025 ASSERT_TRUE(receive_stream);
1026
1027 receive_stream->OnIncomingStreamClosed(false);
1028
1029 test::RunPendingTasks();
1030
1031 ThreadState::Current()->CollectAllGarbageForTesting();
1032
1033 EXPECT_FALSE(receive_stream);
1034 }
1035
1036 // This is the same test as ReceiveStreamGarbageCollectionRemoteClose, except
1037 // that the order of the data pipe being reset and the OnIncomingStreamClosed
1038 // message is reversed. It is important that the object is not collected until
1039 // both events have happened.
TEST_F(QuicTransportTest,ReceiveStreamGarbageCollectionRemoteCloseReverse)1040 TEST_F(QuicTransportTest, ReceiveStreamGarbageCollectionRemoteCloseReverse) {
1041 V8TestingScope scope;
1042
1043 WeakPersistent<ReceiveStream> receive_stream;
1044 mojo::ScopedDataPipeProducerHandle producer;
1045
1046 {
1047 v8::HandleScope handle_scope(scope.GetIsolate());
1048
1049 QuicTransport* quic_transport =
1050 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
1051
1052 producer = DoAcceptUnidirectionalStream();
1053 receive_stream = ReadReceiveStream(scope, quic_transport);
1054 }
1055
1056 ThreadState::Current()->CollectAllGarbageForTesting();
1057
1058 ASSERT_TRUE(receive_stream);
1059
1060 receive_stream->OnIncomingStreamClosed(false);
1061
1062 test::RunPendingTasks();
1063
1064 ThreadState::Current()->CollectAllGarbageForTesting();
1065
1066 ASSERT_TRUE(receive_stream);
1067
1068 producer.reset();
1069
1070 test::RunPendingTasks();
1071
1072 ThreadState::Current()->CollectAllGarbageForTesting();
1073
1074 EXPECT_FALSE(receive_stream);
1075 }
1076
TEST_F(QuicTransportTest,CreateSendStreamAbortedByClose)1077 TEST_F(QuicTransportTest, CreateSendStreamAbortedByClose) {
1078 V8TestingScope scope;
1079
1080 auto* script_state = scope.GetScriptState();
1081 auto* quic_transport =
1082 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
1083
1084 base::OnceCallback<void(bool, uint32_t)> create_stream_callback;
1085 EXPECT_CALL(*mock_quic_transport_, CreateStream(_, _, _))
1086 .WillOnce([&](Unused, Unused,
1087 base::OnceCallback<void(bool, uint32_t)> callback) {
1088 create_stream_callback = std::move(callback);
1089 });
1090
1091 ScriptPromise send_stream_promise =
1092 quic_transport->createSendStream(script_state, ASSERT_NO_EXCEPTION);
1093 ScriptPromiseTester tester(script_state, send_stream_promise);
1094
1095 test::RunPendingTasks();
1096
1097 quic_transport->close(nullptr);
1098 std::move(create_stream_callback).Run(true, 0);
1099
1100 tester.WaitUntilSettled();
1101
1102 EXPECT_TRUE(tester.IsRejected());
1103 }
1104
1105 // ReceiveStream functionality is thoroughly tested in incoming_stream_test.cc.
1106 // This test just verifies that the creation is done correctly.
TEST_F(QuicTransportTest,CreateReceiveStream)1107 TEST_F(QuicTransportTest, CreateReceiveStream) {
1108 V8TestingScope scope;
1109
1110 auto* script_state = scope.GetScriptState();
1111 auto* quic_transport =
1112 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
1113
1114 mojo::ScopedDataPipeProducerHandle producer = DoAcceptUnidirectionalStream();
1115
1116 ReceiveStream* receive_stream = ReadReceiveStream(scope, quic_transport);
1117
1118 const char data[] = "what";
1119 uint32_t num_bytes = 4u;
1120
1121 EXPECT_EQ(
1122 producer->WriteData(data, &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE),
1123 MOJO_RESULT_OK);
1124 EXPECT_EQ(num_bytes, 4u);
1125
1126 producer.reset();
1127 quic_transport->OnIncomingStreamClosed(/*stream_id=*/0, true);
1128
1129 auto* reader = receive_stream->readable()->GetDefaultReaderForTesting(
1130 script_state, ASSERT_NO_EXCEPTION);
1131 ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
1132 ScriptPromiseTester read_tester(script_state, read_promise);
1133 read_tester.WaitUntilSettled();
1134 EXPECT_TRUE(read_tester.IsFulfilled());
1135 auto read_result = read_tester.Value().V8Value();
1136 ASSERT_TRUE(read_result->IsObject());
1137 v8::Local<v8::Value> value;
1138 bool done = false;
1139 ASSERT_TRUE(
1140 V8UnpackIteratorResult(script_state, read_result.As<v8::Object>(), &done)
1141 .ToLocal(&value));
1142 DOMUint8Array* u8array =
1143 V8Uint8Array::ToImplWithTypeCheck(scope.GetIsolate(), value);
1144 ASSERT_TRUE(u8array);
1145 EXPECT_THAT(base::make_span(static_cast<uint8_t*>(u8array->Data()),
1146 u8array->byteLength()),
1147 ElementsAre('w', 'h', 'a', 't'));
1148 }
1149
TEST_F(QuicTransportTest,CreateReceiveStreamThenClose)1150 TEST_F(QuicTransportTest, CreateReceiveStreamThenClose) {
1151 V8TestingScope scope;
1152
1153 auto* script_state = scope.GetScriptState();
1154 auto* quic_transport =
1155 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
1156
1157 mojo::ScopedDataPipeProducerHandle producer = DoAcceptUnidirectionalStream();
1158
1159 ReceiveStream* receive_stream = ReadReceiveStream(scope, quic_transport);
1160
1161 auto* reader = receive_stream->readable()->GetDefaultReaderForTesting(
1162 script_state, ASSERT_NO_EXCEPTION);
1163 ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
1164 ScriptPromiseTester read_tester(script_state, read_promise);
1165
1166 quic_transport->close(nullptr);
1167
1168 read_tester.WaitUntilSettled();
1169 EXPECT_TRUE(read_tester.IsRejected());
1170 DOMException* exception = V8DOMException::ToImplWithTypeCheck(
1171 scope.GetIsolate(), read_tester.Value().V8Value());
1172 ASSERT_TRUE(exception);
1173 EXPECT_EQ(exception->code(),
1174 static_cast<uint16_t>(DOMExceptionCode::kNetworkError));
1175
1176 // TODO(ricea): Fix this message if possible.
1177 EXPECT_EQ(exception->message(),
1178 "The stream was aborted by the remote server");
1179 }
1180
TEST_F(QuicTransportTest,CreateReceiveStreamThenRemoteClose)1181 TEST_F(QuicTransportTest, CreateReceiveStreamThenRemoteClose) {
1182 V8TestingScope scope;
1183
1184 auto* script_state = scope.GetScriptState();
1185 auto* quic_transport =
1186 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
1187
1188 mojo::ScopedDataPipeProducerHandle producer = DoAcceptUnidirectionalStream();
1189
1190 ReceiveStream* receive_stream = ReadReceiveStream(scope, quic_transport);
1191
1192 auto* reader = receive_stream->readable()->GetDefaultReaderForTesting(
1193 script_state, ASSERT_NO_EXCEPTION);
1194 ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
1195 ScriptPromiseTester read_tester(script_state, read_promise);
1196
1197 client_remote_.reset();
1198
1199 read_tester.WaitUntilSettled();
1200 EXPECT_TRUE(read_tester.IsRejected());
1201 DOMException* exception = V8DOMException::ToImplWithTypeCheck(
1202 scope.GetIsolate(), read_tester.Value().V8Value());
1203 ASSERT_TRUE(exception);
1204 EXPECT_EQ(exception->code(),
1205 static_cast<uint16_t>(DOMExceptionCode::kNetworkError));
1206
1207 // TODO(ricea): Fix this message if possible.
1208 EXPECT_EQ(exception->message(),
1209 "The stream was aborted by the remote server");
1210 }
1211
1212 // BidirectionalStreams are thoroughly tested in bidirectional_stream_test.cc.
1213 // Here we just test the QuicTransport APIs.
TEST_F(QuicTransportTest,CreateBidirectionalStream)1214 TEST_F(QuicTransportTest, CreateBidirectionalStream) {
1215 V8TestingScope scope;
1216
1217 auto* quic_transport =
1218 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
1219
1220 EXPECT_CALL(
1221 *mock_quic_transport_,
1222 CreateStream(Truly(ValidConsumerHandle), Truly(ValidProducerHandle), _))
1223 .WillOnce([](Unused, Unused,
1224 base::OnceCallback<void(bool, uint32_t)> callback) {
1225 std::move(callback).Run(true, 0);
1226 });
1227
1228 auto* script_state = scope.GetScriptState();
1229 ScriptPromise bidirectional_stream_promise =
1230 quic_transport->createBidirectionalStream(script_state,
1231 ASSERT_NO_EXCEPTION);
1232 ScriptPromiseTester tester(script_state, bidirectional_stream_promise);
1233
1234 tester.WaitUntilSettled();
1235
1236 EXPECT_TRUE(tester.IsFulfilled());
1237 auto* bidirectional_stream = V8BidirectionalStream::ToImplWithTypeCheck(
1238 scope.GetIsolate(), tester.Value().V8Value());
1239 EXPECT_TRUE(bidirectional_stream);
1240 }
1241
TEST_F(QuicTransportTest,ReceiveBidirectionalStream)1242 TEST_F(QuicTransportTest, ReceiveBidirectionalStream) {
1243 V8TestingScope scope;
1244
1245 auto* quic_transport =
1246 CreateAndConnectSuccessfully(scope, "quic-transport://example.com");
1247
1248 mojo::ScopedDataPipeProducerHandle outgoing_producer;
1249 mojo::ScopedDataPipeConsumerHandle outgoing_consumer;
1250 ASSERT_TRUE(CreateDataPipeForWebTransportTests(&outgoing_producer,
1251 &outgoing_consumer));
1252
1253 mojo::ScopedDataPipeProducerHandle incoming_producer;
1254 mojo::ScopedDataPipeConsumerHandle incoming_consumer;
1255 ASSERT_TRUE(CreateDataPipeForWebTransportTests(&incoming_producer,
1256 &incoming_consumer));
1257
1258 std::move(pending_bidirectional_accept_callbacks_.front())
1259 .Run(next_stream_id_++, std::move(incoming_consumer),
1260 std::move(outgoing_producer));
1261
1262 ReadableStream* streams = quic_transport->receiveBidirectionalStreams();
1263
1264 v8::Local<v8::Value> v8value = ReadValueFromStream(scope, streams);
1265
1266 BidirectionalStream* bidirectional_stream =
1267 V8BidirectionalStream::ToImplWithTypeCheck(scope.GetIsolate(), v8value);
1268 EXPECT_TRUE(bidirectional_stream);
1269 }
1270
1271 } // namespace
1272
1273 } // namespace blink
1274