| <!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(t, {}, 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> |