| Philipp Hancke | b7014f7 | 2017-02-27 13:24:20 | [diff] [blame] | 1 | <!doctype html> | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame^] | 2 | <meta charset=utf-8> | 
|  | 3 | <title>RTCPeerConnection.prototype.setRemoteDescription</title> | 
|  | 4 | <script src="/resources/testharness.js"></script> | 
|  | 5 | <script src="/resources/testharnessreport.js"></script> | 
|  | 6 | <script src="RTCPeerConnection-helper.js"></script> | 
|  | 7 | <script> | 
| Soares Chen | 0f17643 | 2017-05-04 04:40:47 | [diff] [blame] | 8 | 'use strict'; | 
|  | 9 |  | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame^] | 10 | // Test is based on the following editor draft: | 
|  | 11 | // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html | 
|  | 12 |  | 
|  | 13 | /* | 
|  | 14 | * 4.3.2. setRemoteDescription(offer) | 
|  | 15 | */ | 
|  | 16 |  | 
|  | 17 | promise_test(t => { | 
|  | 18 | const pc = new RTCPeerConnection(); | 
|  | 19 |  | 
|  | 20 | test_state_change_event(t, pc, ['have-remote-offer']); | 
|  | 21 |  | 
|  | 22 | return generateOffer({ data: true }) | 
|  | 23 | .then(offer => | 
|  | 24 | pc.setRemoteDescription(offer) | 
|  | 25 | .then(offer => { | 
|  | 26 | assert_equals(pc.signalingState, 'have-remote-offer'); | 
|  | 27 | assert_session_desc_equals(pc.remoteDescription, offer); | 
|  | 28 | assert_session_desc_equals(pc.pendingRemoteDescription, offer); | 
|  | 29 | assert_equals(pc.currentRemoteDescription, null); | 
|  | 30 | })); | 
|  | 31 | }, 'setRemoteDescription with valid offer should succeed'); | 
|  | 32 |  | 
|  | 33 | promise_test(t => { | 
|  | 34 | const pc = new RTCPeerConnection(); | 
|  | 35 |  | 
|  | 36 | // have-remote-offer event should only fire once | 
|  | 37 | test_state_change_event(t, pc, ['have-remote-offer']); | 
|  | 38 |  | 
|  | 39 | return Promise.all([ | 
|  | 40 | generateOffer({ audio: true }), | 
|  | 41 | generateOffer({ data: true }) | 
|  | 42 | ]).then(([offer1, offer2]) => | 
|  | 43 | pc.setRemoteDescription(offer1) | 
|  | 44 | .then(() => pc.setRemoteDescription(offer2)) | 
|  | 45 | .then(() => { | 
|  | 46 | assert_equals(pc.signalingState, 'have-remote-offer'); | 
|  | 47 | assert_session_desc_equals(pc.remoteDescription, offer2); | 
|  | 48 | assert_session_desc_equals(pc.pendingRemoteDescription, offer2); | 
|  | 49 | assert_equals(pc.currentRemoteDescription, null); | 
|  | 50 | })); | 
|  | 51 | }, 'Setting remote description multiple times with different offer should succeed'); | 
|  | 52 |  | 
|  | 53 | test_never_resolve(t => { | 
|  | 54 | const pc = new RTCPeerConnection(); | 
|  | 55 |  | 
|  | 56 | return generateOffer() | 
|  | 57 | .then(offer => { | 
|  | 58 | const promise = pc.setRemoteDescription(offer); | 
|  | 59 | pc.close(); | 
|  | 60 | return promise; | 
|  | 61 | }); | 
|  | 62 | }, 'setRemoteDescription(offer) should never resolve if connection is closed in parallel') | 
|  | 63 |  | 
|  | 64 | /* | 
|  | 65 | * 4.3.1. Setting an RTCSessionDescription | 
|  | 66 | * 2.4. If the content of description is not valid SDP syntax, then reject p | 
|  | 67 | * with an RTCError (with errorDetail set to "sdp-syntax-error" and the | 
|  | 68 | * sdpLineNumber attribute set to the line number in the SDP where the | 
|  | 69 | * syntax error was detected) and abort these steps. | 
|  | 70 | */ | 
|  | 71 | promise_test(t => { | 
|  | 72 | const pc = new RTCPeerConnection(); | 
|  | 73 |  | 
|  | 74 | return pc.setRemoteDescription({ | 
|  | 75 | type: 'offer', | 
|  | 76 | sdp: 'Invalid SDP' | 
|  | 77 | }) | 
|  | 78 | .then(() => { | 
|  | 79 | assert_unreached('Expect promise to be rejected'); | 
|  | 80 | }, err => { | 
|  | 81 | assert_equals(err.errorDetail, 'sdp-syntax-error', | 
|  | 82 | 'Expect error detail field to set to sdp-syntax-error'); | 
|  | 83 |  | 
|  | 84 | assert_true(err instanceof RTCError, | 
|  | 85 | 'Expect err to be instance of RTCError'); | 
|  | 86 | }); | 
|  | 87 |  | 
|  | 88 | return promise_rejects(t, | 
|  | 89 | new RTCError('sdp-syntax-error'), | 
|  | 90 | ); | 
|  | 91 | }, 'setRemoteDescription(offer) with invalid SDP should reject with RTCError'); | 
|  | 92 |  | 
|  | 93 | /* | 
|  | 94 | * 4.6.1. enum RTCSdpType | 
|  | 95 | */ | 
|  | 96 | promise_test(t => { | 
|  | 97 | const pc = new RTCPeerConnection(); | 
|  | 98 |  | 
|  | 99 | // SDP is validated after WebIDL validation | 
|  | 100 | return promise_rejects(t, new TypeError(), | 
|  | 101 | pc.setRemoteDescription({ | 
|  | 102 | type: 'bogus', | 
|  | 103 | sdp: 'bogus' | 
|  | 104 | })); | 
|  | 105 | }, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError') | 
|  | 106 |  | 
|  | 107 | promise_test(t => { | 
|  | 108 | const pc = new RTCPeerConnection(); | 
|  | 109 |  | 
|  | 110 | // SDP is validated after validating type | 
|  | 111 | return promise_rejects(t, 'InvalidStateError', | 
|  | 112 | pc.setRemoteDescription({ | 
|  | 113 | type: 'answer', | 
|  | 114 | sdp: 'invalid' | 
|  | 115 | })); | 
|  | 116 | }, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError') | 
|  | 117 |  | 
|  | 118 | /* | 
|  | 119 | * TODO | 
|  | 120 | * Setting an RTCSessionDescription | 
|  | 121 | * 2.1.5. If the content of description is invalid, then reject p with | 
|  | 122 | * a newly created InvalidAccessError and abort these steps. | 
|  | 123 | * 2.2.5-10 | 
|  | 124 | * setRemoteDescription(rollback) | 
|  | 125 | * setRemoteDescription(pranswer) | 
|  | 126 | * | 
|  | 127 | * Non-testable | 
|  | 128 | * Setting an RTCSessionDescription | 
|  | 129 | * 6. For all other errors, for example if description cannot be | 
|  | 130 | * applied at the media layer, reject p with a newly created OperationError. | 
|  | 131 | */ | 
|  | 132 |  | 
|  | 133 | /* | 
|  | 134 | * 4.3.2. setRemoteDescription(answer) | 
|  | 135 | */ | 
|  | 136 |  | 
|  | 137 | promise_test(t => { | 
|  | 138 | const pc = new RTCPeerConnection(); | 
|  | 139 | test_state_change_event(t, pc, ['have-local-offer', 'stable']); | 
|  | 140 |  | 
|  | 141 | return pc.createOffer({ offerToReceiveVideo: true }) | 
|  | 142 | .then(offer => | 
|  | 143 | pc.setLocalDescription(offer) | 
|  | 144 | .then(() => generateAnswer(offer)) | 
|  | 145 | .then(answer => | 
|  | 146 | pc.setRemoteDescription(answer) | 
|  | 147 | .then(() => { | 
|  | 148 | assert_session_desc_equals(pc.localDescription, offer); | 
|  | 149 | assert_session_desc_equals(pc.remoteDescription, answer); | 
|  | 150 |  | 
|  | 151 | assert_session_desc_equals(pc.currentLocalDescription, offer); | 
|  | 152 | assert_session_desc_equals(pc.currentRemoteDescription, answer); | 
|  | 153 |  | 
|  | 154 | assert_equals(pc.pendingLocalDescription, null); | 
|  | 155 | assert_equals(pc.pendingRemoteDescription, null); | 
|  | 156 | }))); | 
|  | 157 | }, 'setRemoteDescription() with valid state and answer should succeed'); | 
|  | 158 |  | 
|  | 159 | /* | 
|  | 160 | * TODO | 
|  | 161 | * 4.3.2 setRemoteDescription | 
|  | 162 | * If an a=identity attribute is present in the session description, | 
|  | 163 | * the browser validates the identity assertion. | 
|  | 164 | * | 
|  | 165 | * If the "peerIdentity" configuration is applied to the RTCPeerConnection, | 
|  | 166 | * this establishes a target peer identity of the provided value. Alternatively, | 
|  | 167 | * if the RTCPeerConnection has previously authenticated the identity of the | 
|  | 168 | * peer (that is, there is a current value for peerIdentity ), then this also | 
|  | 169 | * establishes a target peer identity. | 
|  | 170 | * | 
|  | 171 | * The target peer identity cannot be changed once set. Once set, | 
|  | 172 | * if a different value is provided, the user agent must reject | 
|  | 173 | * the returned promise with a newly created InvalidModificationError | 
|  | 174 | * and abort this operation. The RTCPeerConnection must be closed if | 
|  | 175 | * the validated peer identity does not match the target peer identity. | 
|  | 176 | */ | 
|  | 177 |  | 
|  | 178 | /* | 
|  | 179 | * Operations after returning to stable state | 
|  | 180 | */ | 
|  | 181 |  | 
|  | 182 | promise_test(t => { | 
|  | 183 | const pc = new RTCPeerConnection(); | 
|  | 184 | const pc2 = new RTCPeerConnection(); | 
|  | 185 |  | 
|  | 186 | test_state_change_event(t, pc, | 
|  | 187 | ['have-remote-offer', 'stable', 'have-remote-offer']); | 
|  | 188 |  | 
|  | 189 | return generateOffer({ audio: true }) | 
|  | 190 | .then(offer1 => | 
|  | 191 | pc.setRemoteDescription(offer1) | 
|  | 192 | .then(() => pc.createAnswer()) | 
|  | 193 | .then(answer => pc.setLocalDescription(answer)) | 
|  | 194 | .then(() => generateOffer({ data: true })) | 
|  | 195 | .then(offer2 => | 
|  | 196 | pc.setRemoteDescription(offer2) | 
|  | 197 | .then(() => { | 
|  | 198 | assert_equals(pc.signalingState, 'have-remote-offer'); | 
|  | 199 | assert_session_desc_not_equals(offer1, offer2); | 
|  | 200 | assert_session_desc_equals(pc.remoteDescription, offer2); | 
|  | 201 | assert_session_desc_equals(pc.currentRemoteDescription, offer1); | 
|  | 202 | assert_session_desc_equals(pc.pendingRemoteDescription, offer2); | 
|  | 203 | }))); | 
|  | 204 | }, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed'); | 
|  | 205 |  | 
|  | 206 | promise_test(t => { | 
|  | 207 | const pc = new RTCPeerConnection(); | 
|  | 208 |  | 
|  | 209 | test_state_change_event(t, pc, | 
|  | 210 | ['have-local-offer', 'stable', 'have-remote-offer']); | 
|  | 211 |  | 
|  | 212 | return pc.createOffer({ offerToReceiveAudio: true }) | 
|  | 213 | .then(offer => | 
|  | 214 | pc.setLocalDescription(offer) | 
|  | 215 | .then(() => generateAnswer(offer))) | 
|  | 216 | .then(answer => | 
|  | 217 | pc.setRemoteDescription(answer) | 
|  | 218 | .then(() => generateOffer({ data: true })) | 
|  | 219 | .then(offer => | 
|  | 220 | pc.setRemoteDescription(offer) | 
|  | 221 | .then(() => { | 
|  | 222 | assert_equals(pc.signalingState, 'have-remote-offer'); | 
|  | 223 | assert_session_desc_equals(pc.remoteDescription, offer); | 
|  | 224 | assert_session_desc_equals(pc.currentRemoteDescription, answer); | 
|  | 225 | assert_session_desc_equals(pc.pendingRemoteDescription, offer); | 
|  | 226 | }))); | 
|  | 227 | }, 'Switching role from offerer to answerer after going back to stable state should succeed'); | 
|  | 228 |  | 
|  | 229 | /* | 
|  | 230 | * InvalidStateError | 
|  | 231 | * [webrtc-pc] 4.3.1. Setting the RTCSessionDescription | 
|  | 232 | * 2.3. If the description's type is invalid for the current signaling state | 
|  | 233 | * of connection, then reject p with a newly created InvalidStateError | 
|  | 234 | * and abort these steps. | 
|  | 235 | */ | 
|  | 236 |  | 
|  | 237 | /* | 
|  | 238 | * [jsep] 5.6. If the type is "pranswer" or "answer", the PeerConnection | 
|  | 239 | * state MUST be either "have-local-offer" or "have-remote-pranswer". | 
|  | 240 | */ | 
|  | 241 | promise_test(t => { | 
|  | 242 | const pc = new RTCPeerConnection(); | 
|  | 243 |  | 
|  | 244 | return generateOffer() | 
|  | 245 | .then(offer => | 
|  | 246 | promise_rejects(t, 'InvalidStateError', | 
|  | 247 | pc.setRemoteDescription({ type: 'answer', sdp: offer.sdp }))); | 
|  | 248 | }, 'Calling setRemoteDescription(answer) from stable state should reject with InvalidStateError'); | 
|  | 249 |  | 
|  | 250 |  | 
|  | 251 | /* | 
|  | 252 | * [jsep] 5.6. If the type is "offer", the PeerConnection state | 
|  | 253 | * MUST be either "stable" or "have-remote-offer". | 
|  | 254 | */ | 
|  | 255 | promise_test(t => { | 
|  | 256 | const pc = new RTCPeerConnection(); | 
|  | 257 |  | 
|  | 258 | return pc.createOffer() | 
|  | 259 | .then(offer => pc.setLocalDescription(offer)) | 
|  | 260 | .then(() => generateOffer()) | 
|  | 261 | .then(offer => | 
|  | 262 | promise_rejects(t, 'InvalidStateError', | 
|  | 263 | pc.setRemoteDescription(offer))); | 
|  | 264 | }, 'Calling setRemoteDescription(offer) from have-local-offer state should reject with InvalidStateError'); | 
|  | 265 |  | 
|  | 266 | /* | 
|  | 267 | * [jsep] 5.6. If the type is "pranswer" or "answer", the PeerConnection | 
|  | 268 | * state MUST be either "have-local-offer" or "have-remote-pranswer". | 
|  | 269 | */ | 
|  | 270 | promise_test(t => { | 
|  | 271 | const pc = new RTCPeerConnection(); | 
|  | 272 |  | 
|  | 273 | return generateOffer() | 
|  | 274 | .then(offer => | 
|  | 275 | pc.setRemoteDescription(offer) | 
|  | 276 | .then(() => generateAnswer(offer))) | 
|  | 277 | .then(answer => | 
|  | 278 | promise_rejects(t, 'InvalidStateError', | 
|  | 279 | pc.setRemoteDescription(answer))); | 
|  | 280 |  | 
|  | 281 | }, 'Calling setRemoteDescription(answer) from have-remote-offer state should reject with InvalidStateError'); | 
|  | 282 |  | 
| Philipp Hancke | 86eb3cb | 2017-04-13 16:19:50 | [diff] [blame] | 283 | // tests that ontrack is called and parses the msid information from the SDP and creates | 
| Philipp Hancke | 0e55032 | 2017-05-03 13:46:49 | [diff] [blame] | 284 | // the streams with matching identifiers. | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame^] | 285 | async_test(t => { | 
|  | 286 | const pc = new RTCPeerConnection(); | 
| Philipp Hancke | b7014f7 | 2017-02-27 13:24:20 | [diff] [blame] | 287 |  | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame^] | 288 | // Fail the test if the ontrack event handler is not implemented | 
|  | 289 | assert_own_property(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute'); | 
| Philipp Hancke | b7014f7 | 2017-02-27 13:24:20 | [diff] [blame] | 290 |  | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame^] | 291 | const sdp = `v=0 | 
|  | 292 | o=- 166855176514521964 2 IN IP4 127.0.0.1 | 
|  | 293 | s=- | 
|  | 294 | t=0 0 | 
|  | 295 | a=msid-semantic:WMS * | 
|  | 296 | m=audio 9 UDP/TLS/RTP/SAVPF 111 | 
|  | 297 | c=IN IP4 0.0.0.0 | 
|  | 298 | a=rtcp:9 IN IP4 0.0.0.0 | 
|  | 299 | a=ice-ufrag:someufrag | 
|  | 300 | a=ice-pwd:somelongpwdwithenoughrandomness | 
|  | 301 | a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4 | 
|  | 302 | a=setup:actpass | 
|  | 303 | a=rtcp-mux | 
|  | 304 | a=mid:mid1 | 
|  | 305 | a=sendonly | 
|  | 306 | a=rtpmap:111 opus/48000/2 | 
|  | 307 | a=msid:stream1 track1 | 
|  | 308 | a=ssrc:1001 cname:some | 
|  | 309 | `; | 
|  | 310 |  | 
|  | 311 | pc.ontrack = t.step_func(event => { | 
| Philipp Hancke | 86eb3cb | 2017-04-13 16:19:50 | [diff] [blame] | 312 | assert_equals(event.streams.length, 1, 'the track belongs to one MediaStream'); | 
|  | 313 | assert_equals(event.streams[0].id, 'stream1', 'the stream name is parsed from the MSID line'); | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame^] | 314 | t.done(); | 
| Philipp Hancke | b7014f7 | 2017-02-27 13:24:20 | [diff] [blame] | 315 | }); | 
|  | 316 |  | 
|  | 317 | pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp})) | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame^] | 318 | .catch(t.step_func(err => { | 
|  | 319 | assert_unreached('Error ' + err.name + ': ' + err.message); | 
| Philipp Hancke | b7014f7 | 2017-02-27 13:24:20 | [diff] [blame] | 320 | })); | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame^] | 321 | }, 'setRemoteDescription should trigger ontrack event when the MSID of the stream is is parsed.'); | 
| Soares Chen | 0f17643 | 2017-05-04 04:40:47 | [diff] [blame] | 322 |  | 
| Philipp Hancke | b7014f7 | 2017-02-27 13:24:20 | [diff] [blame] | 323 | </script> |