| <!doctype html> | |
| <meta charset=utf-8> | |
| <title>RTCIceTransport</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 | |
| // The following helper functions are called from RTCPeerConnection-helper.js: | |
| // createDataChannelPair | |
| // awaitMessage | |
| /* | |
| 5.6. RTCIceTransport Interface | |
| interface RTCIceTransport { | |
| readonly attribute RTCIceRole role; | |
| readonly attribute RTCIceComponent component; | |
| readonly attribute RTCIceTransportState state; | |
| readonly attribute RTCIceGathererState gatheringState; | |
| sequence<RTCIceCandidate> getLocalCandidates(); | |
| sequence<RTCIceCandidate> getRemoteCandidates(); | |
| RTCIceCandidatePair? getSelectedCandidatePair(); | |
| RTCIceParameters? getLocalParameters(); | |
| RTCIceParameters? getRemoteParameters(); | |
| ... | |
| }; | |
| getLocalCandidates | |
| Returns a sequence describing the local ICE candidates gathered for this | |
| RTCIceTransport and sent in onicecandidate | |
| getRemoteCandidates | |
| Returns a sequence describing the remote ICE candidates received by this | |
| RTCIceTransport via addIceCandidate() | |
| getSelectedCandidatePair | |
| Returns the selected candidate pair on which packets are sent, or null if | |
| there is no such pair. | |
| getLocalParameters | |
| Returns the local ICE parameters received by this RTCIceTransport via | |
| setLocalDescription , or null if the parameters have not yet been received. | |
| getRemoteParameters | |
| Returns the remote ICE parameters received by this RTCIceTransport via | |
| setRemoteDescription or null if the parameters have not yet been received. | |
| */ | |
| function getIceTransportFromSctp(pc) { | |
| const sctpTransport = pc.sctp; | |
| assert_true(sctpTransport instanceof RTCSctpTransport, | |
| 'Expect pc.sctp to be instantiated from RTCSctpTransport'); | |
| const dtlsTransport = sctpTransport.transport; | |
| assert_true(dtlsTransport instanceof RTCDtlsTransport, | |
| 'Expect sctp.transport to be an RTCDtlsTransport'); | |
| const iceTransport = dtlsTransport.iceTransport; | |
| assert_true(iceTransport instanceof RTCIceTransport, | |
| 'Expect dtlsTransport.transport to be an RTCIceTransport'); | |
| return iceTransport; | |
| } | |
| function validateCandidates(candidates) { | |
| assert_greater_than(candidates.length, 0, | |
| 'Expect at least one ICE candidate returned from get*Candidates()'); | |
| for(const candidate of candidates) { | |
| assert_true(candidate instanceof RTCIceCandidate, | |
| 'Expect candidate elements to be instance of RTCIceCandidate'); | |
| } | |
| } | |
| function validateCandidateParameter(param) { | |
| assert_not_equals(param, null, | |
| 'Expect candidate parameter to be non-null after data channels are connected'); | |
| assert_equals(typeof param.usernameFragment, 'string', | |
| 'Expect param.usernameFragment to be set with string value'); | |
| assert_equals(typeof param.password, 'string', | |
| 'Expect param.password to be set with string value'); | |
| } | |
| function validateConnectedIceTransport(iceTransport) { | |
| const { state, gatheringState, role, component } = iceTransport; | |
| assert_true(role === 'controlling' || role === 'controlled', | |
| 'Expect RTCIceRole to be either controlling or controlled, found ' + role); | |
| assert_true(component === 'rtp' || component === 'rtcp', | |
| 'Expect RTCIceComponent to be either rtp or rtcp'); | |
| assert_true(state === 'connected' || state === 'completed', | |
| 'Expect ICE transport to be in connected or completed state after data channels are connected'); | |
| assert_true(gatheringState === 'gathering' || gatheringState === 'completed', | |
| 'Expect ICE transport to be in gathering or completed gatheringState after data channels are connected'); | |
| validateCandidates(iceTransport.getLocalCandidates()); | |
| validateCandidates(iceTransport.getRemoteCandidates()); | |
| const candidatePair = iceTransport.getSelectedCandidatePair(); | |
| assert_not_equals(candidatePair, null, | |
| 'Expect selected candidate pair to be non-null after ICE transport is connected'); | |
| assert_true(candidatePair.local instanceof RTCIceCandidate, | |
| 'Expect candidatePair.local to be instance of RTCIceCandidate'); | |
| assert_true(candidatePair.remote instanceof RTCIceCandidate, | |
| 'Expect candidatePair.remote to be instance of RTCIceCandidate'); | |
| validateCandidateParameter(iceTransport.getLocalParameters()); | |
| validateCandidateParameter(iceTransport.getRemoteParameters()); | |
| } | |
| promise_test(t => { | |
| const pc1 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc1.close()); | |
| const pc2 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc2.close()); | |
| return createDataChannelPair(pc1, pc2) | |
| .then(([channel1, channel2]) => { | |
| // Send a ping message and wait for it just to make sure | |
| // that the connection is fully working before testing | |
| channel1.send('ping'); | |
| return awaitMessage(channel2); | |
| }) | |
| .then(() => { | |
| const iceTransport1 = getIceTransportFromSctp(pc1); | |
| const iceTransport2 = getIceTransportFromSctp(pc2); | |
| validateConnectedIceTransport(iceTransport1); | |
| validateConnectedIceTransport(iceTransport2); | |
| assert_equals( | |
| iceTransport1.getLocalCandidates().length, | |
| iceTransport2.getRemoteCandidates().length, | |
| `Expect iceTransport1 to have same number of local candidate as iceTransport2's remote candidates`); | |
| assert_equals( | |
| iceTransport1.getRemoteCandidates().length, | |
| iceTransport2.getLocalCandidates().length, | |
| `Expect iceTransport1 to have same number of remote candidate as iceTransport2's local candidates`); | |
| const candidatePair1 = iceTransport1.getSelectedCandidatePair(); | |
| const candidatePair2 = iceTransport2.getSelectedCandidatePair(); | |
| assert_equals(candidatePair1.local.candidate, candidatePair2.remote.candidate, | |
| 'Expect selected local candidate of one pc is the selected remote candidate or another'); | |
| assert_equals(candidatePair1.remote.candidate, candidatePair2.local.candidate, | |
| 'Expect selected local candidate of one pc is the selected remote candidate or another'); | |
| assert_equals(iceTransport1.role, 'controlling', | |
| `Expect offerer's iceTransport to take the controlling role`); | |
| assert_equals(iceTransport2.role, 'controlled', | |
| `Expect answerer's iceTransport to take the controlled role`); | |
| }); | |
| }, 'Two connected iceTransports should has matching local/remote candidates returned'); | |
| promise_test(t => { | |
| const pc1 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc1.close()); | |
| const pc2 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc2.close()); | |
| pc1.createDataChannel(''); | |
| // setRemoteDescription(answer) without the other peer | |
| // setting answer it's localDescription | |
| return pc1.createOffer() | |
| .then(offer => | |
| pc1.setLocalDescription(offer) | |
| .then(() => pc2.setRemoteDescription(offer)) | |
| .then(() => pc2.createAnswer())) | |
| .then(answer => pc1.setRemoteDescription(answer)) | |
| .then(() => { | |
| const iceTransport = getIceTransportFromSctp(pc1); | |
| assert_array_equals(iceTransport.getRemoteCandidates(), [], | |
| 'Expect iceTransport to not have any remote candidate'); | |
| assert_equals(iceTransport.getSelectedCandidatePair(), null, | |
| 'Expect selectedCandidatePair to be null'); | |
| }); | |
| }, 'Unconnected iceTransport should have empty remote candidates and selected pair'); | |
| </script> |