candidate-exchange.https.html (8285B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Candidate exchange</title> 5 <meta name=timeout content=long> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="../RTCPeerConnection-helper.js"></script> 9 </head> 10 <body> 11 <script> 12 13 class StateLogger { 14 constructor(source, eventname, field) { 15 source.addEventListener(eventname, event => { 16 this.events.push(source[field]); 17 }); 18 this.events = [source[field]]; 19 } 20 } 21 22 class IceStateLogger extends StateLogger { 23 constructor(source) { 24 super(source, 'iceconnectionstatechange', 'iceConnectionState'); 25 } 26 } 27 28 promise_test(async t => { 29 const pc1 = new RTCPeerConnection(); 30 const pc2 = new RTCPeerConnection(); 31 t.add_cleanup(() => pc1.close()); 32 t.add_cleanup(() => pc2.close()); 33 pc1.createDataChannel('datachannel'); 34 pc1IceStates = new IceStateLogger(pc1); 35 pc2IceStates = new IceStateLogger(pc1); 36 exchangeIceCandidates(pc1, pc2); 37 await exchangeOfferAnswer(pc1, pc2); 38 // Note - it's been claimed that this state sometimes jumps straight 39 // to "completed". If so, this test should be flaky. 40 await waitForIceStateChange(pc1, ['connected']); 41 assert_array_equals(pc1IceStates.events, ['new', 'checking', 'connected']); 42 assert_array_equals(pc2IceStates.events, ['new', 'checking', 'connected']); 43 }, 'Two way ICE exchange works'); 44 45 promise_test(async t => { 46 const pc1 = new RTCPeerConnection(); 47 const pc2 = new RTCPeerConnection(); 48 t.add_cleanup(() => pc1.close()); 49 t.add_cleanup(() => pc2.close()); 50 pc1IceStates = new IceStateLogger(pc1); 51 pc2IceStates = new IceStateLogger(pc1); 52 let candidates = []; 53 pc1.createDataChannel('datachannel'); 54 pc1.onicecandidate = e => { 55 candidates.push(e.candidate); 56 } 57 // Candidates from PC2 are not delivered to pc1, so pc1 will use 58 // peer-reflexive candidates. 59 await exchangeOfferAnswer(pc1, pc2); 60 const waiter = waitForIceGatheringState(pc1, ['complete']); 61 await waiter; 62 for (const candidate of candidates) { 63 if (candidate) { 64 pc2.addIceCandidate(candidate); 65 } 66 } 67 await Promise.all([waitForIceStateChange(pc1, ['connected', 'completed']), 68 waitForIceStateChange(pc2, ['connected', 'completed'])]); 69 const candidate_pair = pc1.sctp.transport.iceTransport.getSelectedCandidatePair(); 70 assert_equals(candidate_pair.local.type, 'host'); 71 assert_equals(candidate_pair.remote.type, 'prflx'); 72 assert_array_equals(pc1IceStates.events, ['new', 'checking', 'connected']); 73 assert_array_equals(pc2IceStates.events, ['new', 'checking', 'connected']); 74 }, 'Adding only caller -> callee candidates gives a connection'); 75 76 promise_test(async t => { 77 const pc1 = new RTCPeerConnection(); 78 const pc2 = new RTCPeerConnection(); 79 t.add_cleanup(() => pc1.close()); 80 t.add_cleanup(() => pc2.close()); 81 pc1IceStates = new IceStateLogger(pc1); 82 pc2IceStates = new IceStateLogger(pc1); 83 let candidates = []; 84 pc1.createDataChannel('datachannel'); 85 pc2.onicecandidate = e => { 86 candidates.push(e.candidate); 87 } 88 // Candidates from pc1 are not delivered to pc2. so pc2 will use 89 // peer-reflexive candidates. 90 await exchangeOfferAnswer(pc1, pc2); 91 const waiter = waitForIceGatheringState(pc2, ['complete']); 92 await waiter; 93 for (const candidate of candidates) { 94 if (candidate) { 95 pc1.addIceCandidate(candidate); 96 } 97 } 98 await Promise.all([waitForIceStateChange(pc1, ['connected', 'completed']), 99 waitForIceStateChange(pc2, ['connected', 'completed'])]); 100 const candidate_pair = pc2.sctp.transport.iceTransport.getSelectedCandidatePair(); 101 assert_equals(candidate_pair.local.type, 'host'); 102 assert_equals(candidate_pair.remote.type, 'prflx'); 103 assert_array_equals(pc1IceStates.events, ['new', 'checking', 'connected']); 104 assert_array_equals(pc2IceStates.events, ['new', 'checking', 'connected']); 105 }, 'Adding only callee -> caller candidates gives a connection'); 106 107 promise_test(async t => { 108 const pc1 = new RTCPeerConnection(); 109 const pc2 = new RTCPeerConnection(); 110 t.add_cleanup(() => pc1.close()); 111 t.add_cleanup(() => pc2.close()); 112 pc1IceStates = new IceStateLogger(pc1); 113 pc2IceStates = new IceStateLogger(pc1); 114 let pc2ToPc1Candidates = []; 115 pc1.createDataChannel('datachannel'); 116 pc2.onicecandidate = e => { 117 pc2ToPc1Candidates.push(e.candidate); 118 // This particular test verifies that candidates work 119 // properly if added from the pc2 onicecandidate event. 120 if (!e.candidate) { 121 for (const candidate of pc2ToPc1Candidates) { 122 if (candidate) { 123 pc1.addIceCandidate(candidate); 124 } 125 } 126 } 127 } 128 // Candidates from |pc1| are not delivered to |pc2|. |pc2| will use 129 // peer-reflexive candidates. 130 await exchangeOfferAnswer(pc1, pc2); 131 await Promise.all([waitForIceStateChange(pc1, ['connected', 'completed']), 132 waitForIceStateChange(pc2, ['connected', 'completed'])]); 133 const candidate_pair = pc2.sctp.transport.iceTransport.getSelectedCandidatePair(); 134 assert_equals(candidate_pair.local.type, 'host'); 135 assert_equals(candidate_pair.remote.type, 'prflx'); 136 assert_array_equals(pc1IceStates.events, ['new', 'checking', 'connected']); 137 assert_array_equals(pc2IceStates.events, ['new', 'checking', 'connected']); 138 }, 'Adding callee -> caller candidates from end-of-candidates gives a connection'); 139 140 promise_test(async t => { 141 const pc1 = new RTCPeerConnection(); 142 const pc2 = new RTCPeerConnection(); 143 t.add_cleanup(() => pc1.close()); 144 t.add_cleanup(() => pc2.close()); 145 pc1IceStates = new IceStateLogger(pc1); 146 pc2IceStates = new IceStateLogger(pc1); 147 let pc1ToPc2Candidates = []; 148 let pc2ToPc1Candidates = []; 149 pc1.createDataChannel('datachannel'); 150 pc1.onicecandidate = e => { 151 pc1ToPc2Candidates.push(e.candidate); 152 } 153 pc2.onicecandidate = e => { 154 pc2ToPc1Candidates.push(e.candidate); 155 } 156 const offer = await pc1.createOffer(); 157 await Promise.all([pc1.setLocalDescription(offer), 158 pc2.setRemoteDescription(offer)]); 159 const answer = await pc2.createAnswer(); 160 await waitForIceGatheringState(pc1, ['complete']); 161 await pc2.setLocalDescription(answer).then(() => { 162 for (const candidate of pc1ToPc2Candidates) { 163 if (candidate) { 164 pc2.addIceCandidate(candidate); 165 } 166 } 167 }); 168 await waitForIceGatheringState(pc2, ['complete']); 169 pc1.setRemoteDescription(answer).then(async () => { 170 for (const candidate of pc2ToPc1Candidates) { 171 if (candidate) { 172 await pc1.addIceCandidate(candidate); 173 } 174 } 175 }); 176 await Promise.all([waitForIceStateChange(pc1, ['connected', 'completed']), 177 waitForIceStateChange(pc2, ['connected', 'completed'])]); 178 const candidate_pair = 179 pc1.sctp.transport.iceTransport.getSelectedCandidatePair(); 180 assert_equals(candidate_pair.local.type, 'host'); 181 // When we supply remote candidates, we expect a jump to the 'host' candidate, 182 // but it might also remain as 'prflx'. 183 assert_true(candidate_pair.remote.type == 'host' || 184 candidate_pair.remote.type == 'prflx'); 185 assert_array_equals(pc1IceStates.events, ['new', 'checking', 'connected']); 186 assert_array_equals(pc2IceStates.events, ['new', 'checking', 'connected']); 187 }, 'Explicit offer/answer exchange gives a connection'); 188 189 promise_test(async t => { 190 const pc1 = new RTCPeerConnection(); 191 t.add_cleanup(() => pc1.close()); 192 pc1.createDataChannel('datachannel'); 193 pc1.onicecandidate = assert_unreached; 194 const offer = await pc1.createOffer(); 195 await pc1.setLocalDescription(offer); 196 await new Promise(resolve => { 197 pc1.onicecandidate = resolve; 198 }); 199 }, 'Candidates always arrive after setLocalDescription(offer) resolves'); 200 201 promise_test(async t => { 202 const pc1 = new RTCPeerConnection(); 203 const pc2 = new RTCPeerConnection(); 204 t.add_cleanup(() => pc1.close()); 205 t.add_cleanup(() => pc2.close()); 206 pc1.createDataChannel('datachannel'); 207 pc2.onicecandidate = assert_unreached; 208 const offer = await pc1.createOffer(); 209 await pc2.setRemoteDescription(offer); 210 await pc2.setLocalDescription(await pc2.createAnswer()); 211 await new Promise(resolve => { 212 pc2.onicecandidate = resolve; 213 }); 214 }, 'Candidates always arrive after setLocalDescription(answer) resolves'); 215 216 </script> 217 </body> 218 </html>