| <!doctype html> |
| <meta charset=utf-8> |
| <meta name="timeout" content="long"> |
| <title>RTCPeerConnection Failed State</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../RTCPeerConnection-helper.js"></script> |
| <script> |
| 'use strict'; |
| |
| // Tests for correct behavior of ICE state. |
| |
| // SDP copied from JSEP Example 7.1 |
| // It contains two media streams with different ufrags, and bundle |
| // turned on. |
| const kSdp = `v=0 |
| o=- 4962303333179871722 1 IN IP4 0.0.0.0 |
| s=- |
| t=0 0 |
| a=ice-options:trickle |
| a=group:BUNDLE a1 v1 |
| a=group:LS a1 v1 |
| m=audio 10100 UDP/TLS/RTP/SAVPF 96 0 8 97 98 |
| c=IN IP4 203.0.113.100 |
| a=mid:a1 |
| a=sendrecv |
| a=rtpmap:96 opus/48000/2 |
| a=rtpmap:0 PCMU/8000 |
| a=rtpmap:8 PCMA/8000 |
| a=rtpmap:97 telephone-event/8000 |
| a=rtpmap:98 telephone-event/48000 |
| a=maxptime:120 |
| a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid |
| a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level |
| a=msid:47017fee-b6c1-4162-929c-a25110252400 f83006c5-a0ff-4e0a-9ed9-d3e6747be7d9 |
| a=ice-ufrag:ETEn |
| a=ice-pwd:OtSK0WpNtpUjkY4+86js7ZQl |
| a=fingerprint:sha-256 19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 |
| a=setup:actpass |
| a=dtls-id:1 |
| a=rtcp:10101 IN IP4 203.0.113.100 |
| a=rtcp-mux |
| a=rtcp-rsize |
| m=video 10102 UDP/TLS/RTP/SAVPF 100 101 |
| c=IN IP4 203.0.113.100 |
| a=mid:v1 |
| a=sendrecv |
| a=rtpmap:100 VP8/90000 |
| a=rtpmap:101 rtx/90000 |
| a=fmtp:101 apt=100 |
| a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid |
| a=rtcp-fb:100 ccm fir |
| a=rtcp-fb:100 nack |
| a=rtcp-fb:100 nack pli |
| a=msid:47017fee-b6c1-4162-929c-a25110252400 f30bdb4a-5db8-49b5-bcdc-e0c9a23172e0 |
| a=ice-ufrag:BGKk |
| a=ice-pwd:mqyWsAjvtKwTGnvhPztQ9mIf |
| a=fingerprint:sha-256 19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 |
| a=setup:actpass |
| a=dtls-id:1 |
| a=rtcp:10103 IN IP4 203.0.113.100 |
| a=rtcp-mux |
| a=rtcp-rsize |
| `; |
| |
| // Returns a promise that resolves when |pc.iceConnectionState| is in one of the |
| // wanted states, and rejects if it is in one of the unwanted states. |
| // This is a variant of the function in RTCPeerConnection-helper.js. |
| function waitForIceStateChange(pc, wantedStates, unwantedStates=[]) { |
| return new Promise((resolve, reject) => { |
| if (wantedStates.includes(pc.iceConnectionState)) { |
| resolve(); |
| return; |
| } else if (unwantedStates.includes(pc.iceConnectionState)) { |
| reject('Unexpected state encountered: ' + pc.iceConnectionState); |
| return; |
| } |
| pc.addEventListener('iceconnectionstatechange', () => { |
| if (wantedStates.includes(pc.iceConnectionState)) { |
| resolve(); |
| } else if (unwantedStates.includes(pc.iceConnectionState)) { |
| reject('Unexpected state encountered: ' + pc.iceConnectionState); |
| } |
| }); |
| }); |
| } |
| |
| promise_test(async t => { |
| const pc1 = new RTCPeerConnection(); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| t.add_cleanup(() => pc2.close()); |
| let [track, streams] = await getTrackFromUserMedia('video'); |
| const sender = pc1.addTrack(track); |
| exchangeIceCandidates(pc1, pc2); |
| await exchangeOfferAnswer(pc1, pc2); |
| await waitForIceStateChange(pc1, ['connected', 'completed']); |
| }, 'PC should enter connected (or completed) state when candidates are sent'); |
| |
| promise_test(async t => { |
| const pc1 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| let [track, streams] = await getTrackFromUserMedia('video'); |
| const sender = pc1.addTrack(track); |
| const offer = await pc1.createOffer(); |
| assert_greater_than_equal(offer.sdp.search('a=ice-options:trickle'), 0); |
| }, 'PC should generate offer with a=ice-options:trickle'); |
| |
| promise_test(async t => { |
| const pc1 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| await pc1.setRemoteDescription({type: 'offer', sdp: kSdp}); |
| const answer = await pc1.createAnswer(); |
| await pc1.setLocalDescription(answer); |
| assert_greater_than_equal(answer.sdp.search('a=ice-options:trickle'), 0); |
| // When we use trickle ICE, and don't signal end-of-caniddates, we |
| // expect failure to result in 'disconnected' state rather than 'failed'. |
| const stateWaiter = waitForIceStateChange(pc1, ['disconnected'], |
| ['failed']); |
| // Add a bogus candidate. The candidate is drawn from the |
| // IANA "test-net-3" pool (RFC5737), so is guaranteed not to respond. |
| const candidateStr1 = |
| 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host'; |
| await pc1.addIceCandidate({candidate: candidateStr1, |
| sdpMid: 'a1', |
| usernameFragment: 'ETEn'}); |
| await stateWaiter; |
| }, 'PC should enter disconnected state when a failing candidate is sent'); |
| |
| </script> |