summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc-extensions/RTCRtpTransceiver-headerExtensionControl.html
blob: 796d35dcb6bfffab5d62439da8dd93fc28aec9c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
<!doctype html>
<meta charset=utf-8>
<title>RTCRtpParameters encodings</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webrtc/dictionary-helper.js"></script>
<script src="/webrtc/RTCRtpParameters-helper.js"></script>
<script src="/webrtc/third_party/sdp/sdp.js"></script>
<script>
'use strict';

async function negotiate(pc1, pc2) {
  await pc1.setLocalDescription();
  await pc2.setRemoteDescription(pc1.localDescription);
  await pc2.setLocalDescription();
  await pc1.setRemoteDescription(pc2.localDescription);
}

['audio', 'video'].forEach(kind => {
  test(t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());
    const transceiver = pc.addTransceiver(kind);
    const capabilities = transceiver.getHeaderExtensionsToNegotiate();
    const capability = capabilities.find((capability) => {
        return capability.uri === 'urn:ietf:params:rtp-hdrext:sdes:mid';
    });
    assert_not_equals(capability, undefined);
    assert_equals(capability.direction, 'sendrecv');
  }, `the ${kind} transceiver.getHeaderExtensionsToNegotiate() includes mandatory extensions`);
});

test(t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('audio');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  capabilities[0].uri = '';
  assert_throws_js(TypeError, () => {
    transceiver.setHeaderExtensionsToNegotiate(capabilities);
  }, 'transceiver should throw TypeError when setting an empty URI');
}, `setHeaderExtensionsToNegotiate throws TypeError on encountering missing URI`);

test(t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('audio');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  capabilities[0].direction = '';
  assert_throws_js(TypeError, () => {
    transceiver.setHeaderExtensionsToNegotiate(capabilities);
  }, 'transceiver should throw TypeError when setting an empty direction');
}, `setHeaderExtensionsToNegotiate throws TypeError on encountering missing direction`);

test(t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('audio');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  capabilities[0].uri = '4711';
  assert_throws_dom('InvalidModificationError', () => {
    transceiver.setHeaderExtensionsToNegotiate(capabilities);
  }, 'transceiver should throw InvalidModificationError when setting an unknown URI');
}, `setHeaderExtensionsToNegotiate throws InvalidModificationError on encountering unknown URI`);

test(t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('video');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate().filter(capability => {
    return capability.uri === 'urn:ietf:params:rtp-hdrext:sdes:mid';
  });
  assert_throws_dom('InvalidModificationError', () => {
    transceiver.setHeaderExtensionsToNegotiate(capabilities);
  }, 'transceiver should throw InvalidModificationError when removing elements from the list');
}, `setHeaderExtensionsToNegotiate throws InvalidModificationError when removing elements from the list`);

test(t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('video');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  capabilities.push({
    uri: '4711',
    direction: 'recvonly',
  });
  assert_throws_dom('InvalidModificationError', () => {
    transceiver.setHeaderExtensionsToNegotiate(capabilities);
  }, 'transceiver should throw InvalidModificationError when adding elements to the list');
}, `setHeaderExtensionsToNegotiate throws InvalidModificationError when adding elements to the list`);

test(t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('audio');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const capability = capabilities.find((capability) => {
      return capability.uri === 'urn:ietf:params:rtp-hdrext:sdes:mid';
  });
  ['sendonly', 'recvonly', 'inactive', 'stopped'].map(direction => {
    capability.direction = direction;
    assert_throws_dom('InvalidModificationError', () => {
      transceiver.setHeaderExtensionsToNegotiate(capabilities);
    }, `transceiver should throw InvalidModificationError when setting a mandatory header extension\'s direction to ${direction}`);
  });
}, `setHeaderExtensionsToNegotiate throws InvalidModificationError when setting a mandatory header extension\'s direction to something else than "sendrecv"`);

test(t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('audio');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const selected_capability = capabilities.find((capability) => {
      return capability.direction === 'sendrecv' &&
             capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
  });
  selected_capability.direction = 'stopped';
  const offered_capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const altered_capability = capabilities.find((capability) => {
      return capability.uri === selected_capability.uri &&
             capability.direction === 'stopped';
  });
  assert_not_equals(altered_capability, undefined);
}, `modified direction set by setHeaderExtensionsToNegotiate is visible in subsequent getHeaderExtensionsToNegotiate`);

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('video');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const offer = await pc.createOffer();
  const extensions = SDPUtils.matchPrefix(SDPUtils.splitSections(offer.sdp)[1], 'a=extmap:')
    .map(line => SDPUtils.parseExtmap(line));
  for (const capability of capabilities) {
    if (capability.direction === 'stopped') {
      assert_equals(undefined, extensions.find(e => e.uri === capability.uri));
    } else {
      assert_not_equals(undefined, extensions.find(e => e.uri === capability.uri));
    }
  }
}, `Unstopped extensions turn up in offer`);

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const transceiver = pc.addTransceiver('video');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const selected_capability = capabilities.find((capability) => {
      return capability.direction === 'sendrecv' &&
             capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
  });
  selected_capability.direction = 'stopped';
  transceiver.setHeaderExtensionsToNegotiate(capabilities);
  const offer = await pc.createOffer();
  const extensions = SDPUtils.matchPrefix(SDPUtils.splitSections(offer.sdp)[1], 'a=extmap:')
    .map(line => SDPUtils.parseExtmap(line));
  for (const capability of capabilities) {
    if (capability.direction === 'stopped') {
      assert_equals(undefined, extensions.find(e => e.uri === capability.uri));
    } else {
      assert_not_equals(undefined, extensions.find(e => e.uri === capability.uri));
    }
  }
}, `Stopped extensions do not turn up in offers`);

promise_test(async t => {
  const pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());

  // Disable a non-mandatory extension before first negotiation.
  const transceiver = pc1.addTransceiver('video');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const selected_capability = capabilities.find((capability) => {
      return capability.direction === 'sendrecv' &&
             capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
  });
  selected_capability.direction = 'stopped';
  transceiver.setHeaderExtensionsToNegotiate(capabilities);

  await negotiate(pc1, pc2);
  const negotiated_capabilites = transceiver.getNegotiatedHeaderExtensions();

  assert_equals(capabilities.length, negotiated_capabilites.length);
}, `The set of negotiated extensions has the same size as the set of extensions to negotiate`);

promise_test(async t => {
  const pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());

  // Disable a non-mandatory extension before first negotiation.
  const transceiver = pc1.addTransceiver('video');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const selected_capability = capabilities.find((capability) => {
      return capability.direction === 'sendrecv' &&
             capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
  });
  selected_capability.direction = 'stopped';
  transceiver.setHeaderExtensionsToNegotiate(capabilities);

  await negotiate(pc1, pc2);
  const negotiated_capabilites = transceiver.getNegotiatedHeaderExtensions();

  // Attempt enabling the extension.
  selected_capability.direction = 'sendrecv';

  // The enabled extension should not be part of the negotiated set.
  transceiver.setHeaderExtensionsToNegotiate(capabilities);
  await negotiate(pc1, pc2);
  assert_not_equals(
      transceiver.getNegotiatedHeaderExtensions().find(capability => {
        return capability.uri === selected_capability.uri &&
               capability.direction === 'sendrecv';
      }), undefined);
}, `Header extensions can be reactivated in subsequent offers`);

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());

  const t1 = pc.addTransceiver('video');
  const t2 = pc.addTransceiver('video');
  const extensionUri = 'urn:3gpp:video-orientation';

  assert_true(!!t1.getHeaderExtensionsToNegotiate().find(ext => ext.uri === extensionUri));
  const ext1 = t1.getHeaderExtensionsToNegotiate();
  ext1.find(ext => ext.uri === extensionUri).direction = 'stopped';
  t1.setHeaderExtensionsToNegotiate(ext1);

  assert_true(!!t2.getHeaderExtensionsToNegotiate().find(ext => ext.uri === extensionUri));
  const ext2 = t2.getHeaderExtensionsToNegotiate();
  ext2.find(ext => ext.uri === extensionUri).direction = 'sendrecv';
  t2.setHeaderExtensionsToNegotiate(ext2);

  const offer = await pc.createOffer();
  const sections = SDPUtils.splitSections(offer.sdp);
  sections.shift();
  const extensions = sections.map(section => {
    return SDPUtils.matchPrefix(section, 'a=extmap:')
      .map(SDPUtils.parseExtmap);
  });
  assert_equals(extensions.length, 2);
  assert_false(!!extensions[0].find(extension => extension.uri === extensionUri));
  assert_true(!!extensions[1].find(extension => extension.uri === extensionUri));
}, 'Header extensions can be deactivated on a per-mline basis');

promise_test(async t => {
  const pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());

  const t1 = pc1.addTransceiver('video');

  await pc1.setLocalDescription();
  await pc2.setRemoteDescription(pc1.localDescription);
  // Get the transceiver after it is created by SRD.
  const t2 = pc2.getTransceivers()[0];
  const t2_capabilities = t2.getHeaderExtensionsToNegotiate();
  const t2_capability_to_stop = t2_capabilities
    .find(capability => capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid');
  assert_not_equals(undefined, t2_capability_to_stop);
  t2_capability_to_stop.direction = 'stopped';
  t2.setHeaderExtensionsToNegotiate(t2_capabilities);

  await pc2.setLocalDescription();
  await pc1.setRemoteDescription(pc2.localDescription);

  const t1_negotiated = t1.getNegotiatedHeaderExtensions()
    .find(extension => extension.uri === t2_capability_to_stop.uri);
  assert_not_equals(undefined, t1_negotiated);
  assert_equals(t1_negotiated.direction, 'stopped');
  const t1_capability = t1.getHeaderExtensionsToNegotiate()
    .find(extension => extension.uri === t2_capability_to_stop.uri);
  assert_not_equals(undefined, t1_capability);
  assert_equals(t1_capability.direction, 'sendrecv');
}, 'Extensions not negotiated by the peer are `stopped` in getNegotiatedHeaderExtensions');

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());

  const transceiver = pc.addTransceiver('video');
  const negotiated_capabilites = transceiver.getNegotiatedHeaderExtensions();
  assert_equals(negotiated_capabilites.length,
                transceiver.getHeaderExtensionsToNegotiate().length);
  for (const capability of negotiated_capabilites) {
    assert_equals(capability.direction, 'stopped');
  }
}, 'Prior to negotiation, getNegotiatedHeaderExtensions() returns `stopped` for all extensions.');

promise_test(async t => {
  const pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());

  // Disable a non-mandatory extension before first negotiation.
  const transceiver = pc1.addTransceiver('video');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const selected_capability = capabilities.find((capability) => {
      return capability.direction === 'sendrecv' &&
             capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
  });
  selected_capability.direction = 'stopped';
  transceiver.setHeaderExtensionsToNegotiate(capabilities);

  await negotiate(pc1, pc2);
  const negotiated_capabilites = transceiver.getNegotiatedHeaderExtensions();

  const local_negotiated = transceiver.getNegotiatedHeaderExtensions().find(ext => {
    return ext.uri === selected_capability.uri;
  });
  assert_equals(local_negotiated.direction, 'stopped');
  const remote_negotiated = pc2.getTransceivers()[0].getNegotiatedHeaderExtensions().find(ext => {
    return ext.uri === selected_capability.uri;
  });
  assert_equals(remote_negotiated.direction, 'stopped');
}, 'Answer header extensions are a subset of the offered header extensions');

promise_test(async t => {
  const pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());

  // Disable a non-mandatory extension before first negotiation.
  const transceiver = pc1.addTransceiver('video');
  const capabilities = transceiver.getHeaderExtensionsToNegotiate();
  const selected_capability = capabilities.find((capability) => {
      return capability.direction === 'sendrecv' &&
             capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
  });
  selected_capability.direction = 'stopped';
  transceiver.setHeaderExtensionsToNegotiate(capabilities);

  await negotiate(pc1, pc2);
  // Negotiate, switching sides.
  await negotiate(pc2, pc1);

  // PC2 will re-offer the extension.
  const remote_reoffered = pc2.getTransceivers()[0].getHeaderExtensionsToNegotiate().find(ext => {
    return ext.uri === selected_capability.uri;
  });
  assert_equals(remote_reoffered.direction, 'sendrecv');

  // But PC1 will still reject the extension.
  const negotiated_capabilites = transceiver.getNegotiatedHeaderExtensions();
  const local_negotiated = transceiver.getNegotiatedHeaderExtensions().find(ext => {
    return ext.uri === selected_capability.uri;
  });
  assert_equals(local_negotiated.direction, 'stopped');
}, 'A subsequent offer from the other side will reoffer extensions not negotiated by the initial offerer');
</script>