blob: b0cb16c04d61b0a1f986322ff477b10677413ba3 [file] [log] [blame]
Philipp Hanckeb7014f72017-02-27 13:24:201<!doctype html>
Soares Chenfda87822017-06-05 14:00:302<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 Chen0f176432017-05-04 04:40:478 'use strict';
9
Soares Chenfda87822017-06-05 14:00:3010 // 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 Hancke86eb3cb2017-04-13 16:19:50283 // tests that ontrack is called and parses the msid information from the SDP and creates
Philipp Hancke0e550322017-05-03 13:46:49284 // the streams with matching identifiers.
Soares Chenfda87822017-06-05 14:00:30285 async_test(t => {
286 const pc = new RTCPeerConnection();
Philipp Hanckeb7014f72017-02-27 13:24:20287
Soares Chenfda87822017-06-05 14:00:30288 // 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 Hanckeb7014f72017-02-27 13:24:20290
Soares Chenfda87822017-06-05 14:00:30291 const sdp = `v=0
292o=- 166855176514521964 2 IN IP4 127.0.0.1
293s=-
294t=0 0
295a=msid-semantic:WMS *
296m=audio 9 UDP/TLS/RTP/SAVPF 111
297c=IN IP4 0.0.0.0
298a=rtcp:9 IN IP4 0.0.0.0
299a=ice-ufrag:someufrag
300a=ice-pwd:somelongpwdwithenoughrandomness
301a=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
302a=setup:actpass
303a=rtcp-mux
304a=mid:mid1
305a=sendonly
306a=rtpmap:111 opus/48000/2
307a=msid:stream1 track1
308a=ssrc:1001 cname:some
309`;
310
311 pc.ontrack = t.step_func(event => {
Philipp Hancke86eb3cb2017-04-13 16:19:50312 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 Chenfda87822017-06-05 14:00:30314 t.done();
Philipp Hanckeb7014f72017-02-27 13:24:20315 });
316
317 pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp}))
Soares Chenfda87822017-06-05 14:00:30318 .catch(t.step_func(err => {
319 assert_unreached('Error ' + err.name + ': ' + err.message);
Philipp Hanckeb7014f72017-02-27 13:24:20320 }));
Soares Chenfda87822017-06-05 14:00:30321 }, 'setRemoteDescription should trigger ontrack event when the MSID of the stream is is parsed.');
Soares Chen0f176432017-05-04 04:40:47322
Philipp Hanckeb7014f72017-02-27 13:24:20323</script>