| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCQuicTransport.https.html</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../webrtc/RTCIceTransport-extension-helper.js"></script> |
| <script src="../webrtc/RTCPeerConnection-helper.js"></script> |
| <script src="RTCQuicTransport-helper.js"></script> |
| <script src="../webrtc/dictionary-helper.js"></script> |
| <script> |
| 'use strict'; |
| |
| // These tests are based on the following specification: |
| // https://w3c.github.io/webrtc-quic/ |
| |
| // The following helper functions are called from |
| // RTCIceTransport-extension-helper.js: |
| // makeIceTransport |
| // makeAndGatherTwoIceTransports |
| |
| // The following helper functions are called from RTCQuicTransport-helper.js: |
| // makeQuicTransport |
| // makeStandaloneQuicTransport |
| // makeAndStartTwoQuicTransports |
| // makeTwoConnectedQuicTransports |
| // sleep |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| const quicTransport = makeQuicTransport(t, iceTransport); |
| assert_equals(quicTransport.transport, iceTransport, |
| 'Expect transport to be the same as the one passed in the constructor.'); |
| assert_equals(quicTransport.state, 'new', `Expect state to be 'new'.`); |
| }, 'RTCQuicTransport initial properties are set.'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.stop(); |
| assert_throws('InvalidStateError', |
| () => makeQuicTransport(t, iceTransport)); |
| }, 'RTCQuicTransport constructor throws if passed a closed RTCIceTransport.'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| const firstQuicTransport = |
| makeQuicTransport(t, iceTransport); |
| assert_throws('InvalidStateError', |
| () => makeQuicTransport(t, iceTransport)); |
| }, 'RTCQuicTransport constructor throws if passed an RTCIceTransport that ' + |
| 'already has an active RTCQuicTransport.'); |
| |
| promise_test(async t => { |
| const pc1 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc2.close()); |
| |
| pc1.createDataChannel('test'); |
| await doSignalingHandshake(pc1, pc2); |
| const iceTransport = pc1.sctp.transport.iceTransport; |
| |
| assert_throws('InvalidStateError', |
| () => makeQuicTransport(t, iceTransport)); |
| }, 'RTCQuicTransport constructor throws if passed an RTCIceTransport that ' + |
| 'came from an RTCPeerConnection.'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.stop(); |
| assert_equals(quicTransport.state, 'closed'); |
| }, `stop() changes state to 'closed'.`); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.transport.stop(); |
| assert_equals(quicTransport.state, 'closed'); |
| }, `RTCIceTransport.stop() changes RTCQuicTransport.state to 'closed'.`); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| makeAndStartTwoQuicTransports(t); |
| const localWatcher = new EventWatcher(t, localQuicTransport, 'statechange'); |
| const remoteWatcher = new EventWatcher(t, remoteQuicTransport, 'statechange'); |
| await Promise.all([ |
| localWatcher.wait_for('statechange').then(() => { |
| assert_equals(localQuicTransport.state, 'connected'); |
| }), |
| remoteWatcher.wait_for('statechange').then(() => { |
| assert_equals(remoteQuicTransport.state, 'connected'); |
| }), |
| ]); |
| }, 'Two RTCQuicTransports connect to each other.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| localQuicTransport.stop(); |
| const remoteWatcher = new EventWatcher(t, remoteQuicTransport, 'statechange'); |
| await remoteWatcher.wait_for('statechange'); |
| assert_equals(remoteQuicTransport.state, 'closed'); |
| }, `stop() fires a statechange event to 'closed' on the remote transport`); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.connect(); |
| assert_equals(quicTransport.state, 'connecting'); |
| }, `connect() changes state to 'connecting'.`); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.connect(); |
| assert_throws('InvalidStateError', |
| () => quicTransport.connect()); |
| }, 'connect() throws if already called connect().'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.listen(new Uint8Array([12345])); |
| assert_throws('InvalidStateError', |
| () => quicTransport.connect()); |
| }, 'connect() throws if already called listen().'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.stop(); |
| assert_throws('InvalidStateError', |
| () => quicTransport.connect()); |
| }, 'connect() throws after stop().'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.transport.stop(); |
| assert_throws('InvalidStateError', |
| () => quicTransport.connect()); |
| }, 'connect() throws if called after RTCIceTransport has stopped.'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.listen(new Uint8Array([12345])); |
| assert_equals(quicTransport.state, 'connecting'); |
| }, `listen() changes state to 'connecting'.`); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.connect(); |
| assert_throws('InvalidStateError', |
| () => quicTransport.listen(new Uint8Array([12345]))); |
| }, 'listen() throws if already called connect().'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.listen(new Uint8Array([12345])); |
| assert_throws('InvalidStateError', |
| () => quicTransport.listen(new Uint8Array([12345]))); |
| }, 'listen() throws if already called listen().'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.stop(); |
| assert_throws('InvalidStateError', |
| () => quicTransport.listen(new Uint8Array([12345]))); |
| }, 'listen() throws after stop().'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.transport.stop(); |
| assert_throws('InvalidStateError', |
| () => quicTransport.listen(new Uint8Array([12345]))); |
| }, 'listen() throws if called after RTCIceTransport has stopped.'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| const key = quicTransport.getKey(); |
| assert_equals(key.byteLength, 16); |
| }, 'RTCQuicTransport.getKey() attribute is 16 bytes.'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| const key = new Uint8Array(); |
| assert_throws('NotSupportedError', |
| () => quicTransport.listen(key)); |
| }, 'listen() throws if given an empty key.'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| const key = quicTransport.getKey(); |
| let update_key = new Uint8Array(key); |
| for (let i = 0; i < update_key.length; i++) { |
| update_key[i] = 0; |
| } |
| const new_key = quicTransport.getKey(); |
| assert_not_equals(update_key, new Uint8Array(new_key)); |
| }, 'Cannot mutate key retrieved from getKey().'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| makeAndStartTwoQuicTransports(t); |
| const stats = await localQuicTransport.getStats(); |
| assert_number_field(stats, 'timestamp'); |
| assert_unsigned_int_field(stats, 'bytesSent'); |
| assert_unsigned_int_field(stats, 'packetsSent'); |
| assert_unsigned_int_field(stats, 'streamBytesSent'); |
| assert_unsigned_int_field(stats, 'streamBytesReceived'); |
| assert_unsigned_int_field(stats, 'numOutgoingStreamsCreated'); |
| assert_unsigned_int_field(stats, 'numIncomingStreamsCreated'); |
| assert_unsigned_int_field(stats, 'bytesReceived'); |
| assert_unsigned_int_field(stats, 'packetsReceived'); |
| assert_unsigned_int_field(stats, 'packetsProcessed'); |
| assert_unsigned_int_field(stats, 'bytesRetransmitted'); |
| assert_unsigned_int_field(stats, 'packetsRetransmitted'); |
| assert_unsigned_int_field(stats, 'packetsLost'); |
| assert_unsigned_int_field(stats, 'packetsDropped'); |
| assert_unsigned_int_field(stats, 'cryptoRetransmitCount'); |
| assert_unsigned_int_field(stats, 'minRttUs'); |
| assert_unsigned_int_field(stats, 'smoothedRttUs'); |
| assert_unsigned_int_field(stats, 'maxPacketSize'); |
| assert_unsigned_int_field(stats, 'maxReceivedPacketSize'); |
| assert_unsigned_int_field(stats, 'estimatedBandwidthBps'); |
| assert_unsigned_int_field(stats, 'packetsReordered'); |
| assert_unsigned_int_field(stats, 'blockedFramesReceived'); |
| assert_unsigned_int_field(stats, 'blockedFramesSent'); |
| assert_unsigned_int_field(stats, 'connectivityProbingPacketsReceived'); |
| }, 'Stats returned by getStats() are present.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| const localStream = localQuicTransport.createStream(); |
| localStream.write({ finish: true }); |
| const remoteWatcher = new EventWatcher(t, remoteQuicTransport, 'quicstream'); |
| await remoteWatcher.wait_for('quicstream'); |
| const localStats = await localQuicTransport.getStats(); |
| const remoteStats = await remoteQuicTransport.getStats(); |
| assert_equals(localStats.numOutgoingStreamsCreated, 1); |
| assert_equals(localStats.numIncomingStreamsCreated, 0); |
| assert_equals(remoteStats.numOutgoingStreamsCreated, 0); |
| assert_equals(remoteStats.numIncomingStreamsCreated, 1); |
| }, 'getStats() returns proper stream counts after creating streams.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| makeAndStartTwoQuicTransports(t); |
| const stats1 = await localQuicTransport.getStats(); |
| await new Promise(resolve => t.step_timeout(resolve, 20)); |
| const stats2 = await localQuicTransport.getStats(); |
| assert_greater_than(stats2.timestamp, stats1.timestamp); |
| }, 'Two separate stats returned by getStats() give different timestamps.'); |
| |
| promise_test(async t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| const promise = quicTransport.getStats(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'getStats() promises immediately rejected with InvalidStateError ' + |
| `if called before 'connecting'.`); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| const promise = localQuicTransport.getStats(); |
| localQuicTransport.stop(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'getStats() promises rejected with InvalidStateError if stop() ' + |
| 'is called before being fulfilled.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| const promise = localQuicTransport.getStats(); |
| localQuicTransport.transport.stop(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'getStats() promises rejected with InvalidStateError if ' + |
| 'RTCIceTransport calls stop() before being fulfilled.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| localQuicTransport.transport.stop(); |
| const promise = localQuicTransport.getStats(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'getStats() promises immediately rejected if called after ' + |
| `'closed' state.`); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| assert_throws('InvalidStateError', |
| () => quicTransport.sendDatagram(new Uint8Array([1]))); |
| }, `sendDatagram() throws InvalidStateError if called before 'connected'.`); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| quicTransport.stop(); |
| assert_equals(quicTransport.state, 'closed'); |
| assert_throws('InvalidStateError', |
| () => quicTransport.sendDatagram(new Uint8Array([1]))); |
| }, `sendDatagram() throws InvalidStateError if called when 'closed'.`); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| assert_equals(quicTransport.maxDatagramLength, null); |
| }, 'maxDatagramLength 0 before connected.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| assert_greater_than(localQuicTransport.maxDatagramLength, 0); |
| }, 'maxDatagramLength larger than 0 after connected.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| const bigData = new Uint8Array(localQuicTransport.maxDatagramLength + 1); |
| assert_throws('InvalidStateError', |
| () => localQuicTransport.sendDatagram(bigData)); |
| }, 'sendDatagram() throws InvalidStateError if called with data larger ' + |
| 'than maxDatagramLength()'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| const datagram = new Uint8Array([42]); |
| await localQuicTransport.readyToSendDatagram(); |
| localQuicTransport.sendDatagram(datagram); |
| const receiveDatagrams = await remoteQuicTransport.receiveDatagrams(); |
| assert_equals(receiveDatagrams.length, 1); |
| const receiveDatagram = new Uint8Array(receiveDatagrams[0]); |
| assert_array_equals(receiveDatagram, datagram); |
| }, 'sendDatagram() sends a datagram to remote side'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| const datagram = new Uint8Array([42]); |
| const datagram2 = new Uint8Array([43]); |
| await localQuicTransport.readyToSendDatagram(); |
| localQuicTransport.sendDatagram(datagram); |
| const receiveDatagrams = await remoteQuicTransport.receiveDatagrams(); |
| assert_equals(receiveDatagrams.length, 1); |
| const receiveDatagram = new Uint8Array(receiveDatagrams[0]); |
| assert_array_equals(receiveDatagram, datagram); |
| await localQuicTransport.readyToSendDatagram(); |
| localQuicTransport.sendDatagram(datagram2); |
| const receiveDatagrams2 = await remoteQuicTransport.receiveDatagrams(); |
| assert_equals(receiveDatagrams2.length, 1); |
| const receiveDatagram2 = new Uint8Array(receiveDatagrams2[0]); |
| assert_array_equals(receiveDatagram2, datagram2); |
| }, 'sendDatagram() sends a multiple datagrams to remote side'); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| const promise = quicTransport.readyToSendDatagram(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'readyToSendDatagram() promise immediately rejected if called before ' + |
| 'connecting'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| localQuicTransport.stop(); |
| const promise = localQuicTransport.readyToSendDatagram(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'readyToSendDatagram() promise immediately rejected if called after ' + |
| `'closed' state.`); |
| |
| test(t => { |
| const quicTransport = makeStandaloneQuicTransport(t); |
| const promise = quicTransport.receiveDatagrams(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'receiveDatagrams() promise immediately rejected if called before ' + |
| 'connecting.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| localQuicTransport.stop(); |
| const promise = localQuicTransport.receiveDatagrams(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'receiveDatagrams() promise immediately rejected if called after ' + |
| `'closed' state.`); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| const promise = localQuicTransport.receiveDatagrams(); |
| localQuicTransport.stop(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'receiveDatagrams() promise rejected with InvalidStateError if stop() ' + |
| 'is called before being fulfilled.'); |
| |
| promise_test(async t => { |
| const [ localQuicTransport, remoteQuicTransport ] = |
| await makeTwoConnectedQuicTransports(t); |
| const promise = localQuicTransport.receiveDatagrams(); |
| localQuicTransport.transport.stop(); |
| promise_rejects(t, 'InvalidStateError', promise); |
| }, 'receiveDatagrams() promises rejected with InvalidStateError if ' + |
| 'RTCIceTransport calls stop() before being fulfilled.'); |
| |
| </script> |
| |