summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/RTCPeerConnection-iceConnectionState.https.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCPeerConnection-iceConnectionState.https.html')
-rw-r--r--testing/web-platform/tests/webrtc/RTCPeerConnection-iceConnectionState.https.html396
1 files changed, 396 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCPeerConnection-iceConnectionState.https.html b/testing/web-platform/tests/webrtc/RTCPeerConnection-iceConnectionState.https.html
new file mode 100644
index 0000000000..5083be6cdf
--- /dev/null
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-iceConnectionState.https.html
@@ -0,0 +1,396 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>RTCPeerConnection.prototype.iceConnectionState</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+ 'use strict';
+
+ // Test is based on the following editor draft:
+ // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+
+ /*
+ 4.3.2. Interface Definition
+ interface RTCPeerConnection : EventTarget {
+ ...
+ readonly attribute RTCIceConnectionState iceConnectionState;
+ attribute EventHandler oniceconnectionstatechange;
+ };
+
+ 4.4.4 RTCIceConnectionState Enum
+ enum RTCIceConnectionState {
+ "new",
+ "checking",
+ "connected",
+ "completed",
+ "failed",
+ "disconnected",
+ "closed"
+ };
+
+ 5.6. RTCIceTransport Interface
+ interface RTCIceTransport {
+ readonly attribute RTCIceTransportState state;
+ attribute EventHandler onstatechange;
+
+ ...
+ };
+
+ enum RTCIceTransportState {
+ "new",
+ "checking",
+ "connected",
+ "completed",
+ "failed",
+ "disconnected",
+ "closed"
+ };
+ */
+
+ /*
+ 4.4.4 RTCIceConnectionState Enum
+ new
+ Any of the RTCIceTransports are in the new state and none of them
+ are in the checking, failed or disconnected state, or all
+ RTCIceTransport s are in the closed state.
+ */
+ test(t => {
+ const pc = new RTCPeerConnection();
+ assert_equals(pc.iceConnectionState, 'new');
+ }, 'Initial iceConnectionState should be new');
+
+ test(t => {
+ const pc = new RTCPeerConnection();
+ pc.close();
+ assert_equals(pc.iceConnectionState, 'closed');
+ }, 'Closing the connection should set iceConnectionState to closed');
+
+ /*
+ 4.4.4 RTCIceConnectionState Enum
+ checking
+ Any of the RTCIceTransport s are in the checking state and none of
+ them are in the failed or disconnected state.
+
+ connected
+ All RTCIceTransport s are in the connected, completed or closed state
+ and at least one of them is in the connected state.
+
+ completed
+ All RTCIceTransport s are in the completed or closed state and at least
+ one of them is in the completed state.
+
+ checking
+ The RTCIceTransport has received at least one remote candidate and
+ is checking candidate pairs and has either not yet found a connection
+ or consent checks [RFC7675] have failed on all previously successful
+ candidate pairs. In addition to checking, it may also still be gathering.
+
+ 5.6. enum RTCIceTransportState
+ connected
+ The RTCIceTransport has found a usable connection, but is still
+ checking other candidate pairs to see if there is a better connection.
+ It may also still be gathering and/or waiting for additional remote
+ candidates. If consent checks [RFC7675] fail on the connection in use,
+ and there are no other successful candidate pairs available, then the
+ state transitions to "checking" (if there are candidate pairs remaining
+ to be checked) or "disconnected" (if there are no candidate pairs to
+ check, but the peer is still gathering and/or waiting for additional
+ remote candidates).
+
+ completed
+ The RTCIceTransport has finished gathering, received an indication that
+ there are no more remote candidates, finished checking all candidate
+ pairs and found a connection. If consent checks [RFC7675] subsequently
+ fail on all successful candidate pairs, the state transitions to "failed".
+ */
+ async_test(t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ let had_checking = false;
+
+ const onIceConnectionStateChange = t.step_func(() => {
+ const {iceConnectionState} = pc1;
+ if (iceConnectionState === 'checking') {
+ had_checking = true;
+ } else if (iceConnectionState === 'connected' ||
+ iceConnectionState === 'completed') {
+ assert_true(had_checking, 'state should pass checking before' +
+ ' reaching connected or completed');
+ t.done();
+ } else if (iceConnectionState === 'failed') {
+ assert_unreached("ICE should not fail");
+ }
+ });
+
+ pc1.createDataChannel('test');
+
+ pc1.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
+
+ exchangeIceCandidates(pc1, pc2);
+ exchangeOfferAnswer(pc1, pc2);
+ }, 'connection with one data channel should eventually have connected or ' +
+ 'completed connection state');
+
+async_test(t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc2.close());
+
+ const onIceConnectionStateChange = t.step_func(() => {
+ const { iceConnectionState } = pc1;
+
+ if(iceConnectionState === 'checking') {
+ const iceTransport = pc1.sctp.transport.iceTransport;
+
+ assert_equals(iceTransport.state, 'checking',
+ 'Expect ICE transport to be in checking state when' +
+ ' iceConnectionState is checking');
+
+ } else if(iceConnectionState === 'connected') {
+ const iceTransport = pc1.sctp.transport.iceTransport;
+
+ assert_equals(iceTransport.state, 'connected',
+ 'Expect ICE transport to be in connected state when' +
+ ' iceConnectionState is connected');
+ t.done();
+ } else if(iceConnectionState === 'completed') {
+ const iceTransport = pc1.sctp.transport.iceTransport;
+
+ assert_equals(iceTransport.state, 'completed',
+ 'Expect ICE transport to be in connected state when' +
+ ' iceConnectionState is completed');
+ t.done();
+ } else if (iceConnectionState === 'failed') {
+ assert_unreached("ICE should not fail");
+ }
+ });
+
+ pc1.createDataChannel('test');
+
+ assert_equals(pc1.oniceconnectionstatechange, null,
+ 'Expect connection to have iceconnectionstatechange event');
+
+ pc1.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
+
+ exchangeIceCandidates(pc1, pc2);
+ exchangeOfferAnswer(pc1, pc2);
+ }, 'connection with one data channel should eventually ' +
+ 'have connected connection state');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ const stream = await getNoiseStream({audio: true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ stream.getTracks().forEach(track => pc1.addTrack(track, stream));
+
+ exchangeIceCandidates(pc1, pc2);
+ exchangeOfferAnswer(pc1, pc2);
+ await listenToIceConnected(pc1);
+ }, 'connection with audio track should eventually ' +
+ 'have connected connection state');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ const stream = await getNoiseStream({audio: true, video:true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ stream.getTracks().forEach(track => pc1.addTrack(track, stream));
+
+ exchangeIceCandidates(pc1, pc2);
+ exchangeOfferAnswer(pc1, pc2);
+ await listenToIceConnected(pc1);
+ }, 'connection with audio and video tracks should eventually ' +
+ 'have connected connection state');
+
+ promise_test(async t => {
+ const caller = new RTCPeerConnection();
+ t.add_cleanup(() => caller.close());
+ const callee = new RTCPeerConnection();
+ t.add_cleanup(() => callee.close());
+
+ caller.addTransceiver('audio', {direction:'recvonly'});
+ const stream = await getNoiseStream({audio:true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track] = stream.getTracks();
+ callee.addTrack(track, stream);
+ exchangeIceCandidates(caller, callee);
+ await exchangeOfferAnswer(caller, callee);
+
+ assert_equals(caller.getTransceivers().length, 1);
+ const [transceiver] = caller.getTransceivers();
+ assert_equals(transceiver.currentDirection, 'recvonly');
+
+ await listenToIceConnected(caller);
+ }, 'ICE can connect in a recvonly usecase');
+
+ /*
+ TODO
+ 4.4.4 RTCIceConnectionState Enum
+ failed
+ Any of the RTCIceTransport s are in the failed state.
+
+ disconnected
+ Any of the RTCIceTransport s are in the disconnected state and none of
+ them are in the failed state.
+
+ closed
+ The RTCPeerConnection object's [[ isClosed]] slot is true.
+
+ 5.6. enum RTCIceTransportState
+ new
+ The RTCIceTransport is gathering candidates and/or waiting for
+ remote candidates to be supplied, and has not yet started checking.
+
+ failed
+ The RTCIceTransport has finished gathering, received an indication that
+ there are no more remote candidates, finished checking all candidate pairs,
+ and all pairs have either failed connectivity checks or have lost consent.
+
+ disconnected
+ The ICE Agent has determined that connectivity is currently lost for this
+ RTCIceTransport . This is more aggressive than failed, and may trigger
+ intermittently (and resolve itself without action) on a flaky network.
+ The way this state is determined is implementation dependent.
+
+ Examples include:
+ Losing the network interface for the connection in use.
+ Repeatedly failing to receive a response to STUN requests.
+
+ Alternatively, the RTCIceTransport has finished checking all existing
+ candidates pairs and failed to find a connection (or consent checks
+ [RFC7675] once successful, have now failed), but it is still gathering
+ and/or waiting for additional remote candidates.
+
+ closed
+ The RTCIceTransport has shut down and is no longer responding to STUN requests.
+ */
+
+for (let bundle_policy of ['balanced', 'max-bundle', 'max-compat']) {
+
+
+ promise_test(async t => {
+ const caller = new RTCPeerConnection({bundlePolicy: bundle_policy});
+ t.add_cleanup(() => caller.close());
+ const stream = await getNoiseStream(
+ {audio: true, video:true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track1, track2] = stream.getTracks();
+ const sender1 = caller.addTrack(track1);
+ const sender2 = caller.addTrack(track2);
+ caller.createDataChannel('datachannel');
+ const callee = new RTCPeerConnection();
+ t.add_cleanup(() => callee.close());
+ exchangeIceCandidates(caller, callee);
+ const offer = await caller.createOffer();
+ await caller.setLocalDescription(offer);
+ const [caller_transceiver1, caller_transceiver2] = caller.getTransceivers();
+ assert_equals(sender1.transport, caller_transceiver1.sender.transport);
+ await callee.setRemoteDescription(offer);
+ const [callee_transceiver1, callee_transceiver2] = callee.getTransceivers();
+ const answer = await callee.createAnswer();
+ await callee.setLocalDescription(answer);
+ await caller.setRemoteDescription(answer);
+ // At this point, we should have a single ICE transport, and it
+ // should eventually get to the "connected" state.
+ await waitForState(caller_transceiver1.receiver.transport.iceTransport,
+ 'connected');
+ // The PeerConnection's iceConnectionState should therefore be 'connected'
+ assert_equals(caller.iceConnectionState, 'connected',
+ 'PC.iceConnectionState:');
+ }, 'iceConnectionState changes at the right time, with bundle policy ' +
+ bundle_policy);
+}
+
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+ pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
+ pc1.candidateBuffer = [];
+ pc2.onicecandidate = e => {
+ // Don't add candidate if candidate buffer is already used
+ if (pc1.candidateBuffer) {
+ pc1.candidateBuffer.push(e.candidate)
+ }
+ };
+ pc1.iceStates = [pc1.iceConnectionState];
+ pc2.iceStates = [pc2.iceConnectionState];
+ pc1.oniceconnectionstatechange = () => {
+ pc1.iceStates.push(pc1.iceConnectionState);
+ };
+ pc2.oniceconnectionstatechange = () => {
+ pc2.iceStates.push(pc2.iceConnectionState);
+ };
+
+ const localStream = await getNoiseStream({audio: true, video: true});
+ const localStream2 = await getNoiseStream({audio: true, video: true});
+ const remoteStream = await getNoiseStream({audio: true, video: true});
+ for (const stream of [localStream, localStream2, remoteStream]) {
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ }
+ localStream.getTracks().forEach(t => pc1.addTrack(t, localStream));
+ localStream2.getTracks().forEach(t => pc1.addTrack(t, localStream2));
+ remoteStream.getTracks().forEach(t => pc2.addTrack(t, remoteStream));
+ const offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+ await pc1.setLocalDescription(offer);
+ const answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+ pc1.candidateBuffer.forEach(c => pc1.addIceCandidate(c));
+ delete pc1.candidateBuffer;
+ await listenToIceConnected(pc1);
+ await listenToIceConnected(pc2);
+ // While we're waiting for pc2, pc1 may or may not have transitioned
+ // to "completed" state, so allow for both cases.
+ if (pc1.iceStates.length == 3) {
+ assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected']);
+ } else {
+ assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected',
+ 'completed']);
+ }
+ assert_array_equals(pc2.iceStates, ['new', 'checking', 'connected']);
+}, 'Responder ICE connection state behaves as expected');
+
+/*
+ Test case for step 11 of PeerConnection.close().
+ ...
+ 11. Set connection's ICE connection state to "closed". This does not invoke
+ the "update the ICE connection state" procedure, and does not fire any
+ event.
+ ...
+*/
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ const stream = await getNoiseStream({ audio: true });
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+
+ stream.getTracks().forEach(track => pc1.addTrack(track, stream));
+ exchangeIceCandidates(pc1, pc2);
+ exchangeOfferAnswer(pc1, pc2);
+ await listenToIceConnected(pc2);
+
+ pc2.oniceconnectionstatechange = t.unreached_func();
+ pc2.close();
+ assert_equals(pc2.iceConnectionState, 'closed');
+ await new Promise(r => t.step_timeout(r, 100));
+}, 'Closing a PeerConnection should not fire iceconnectionstatechange event');
+
+</script>