summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/protocol/rtp-headerextensions.html
blob: d1d8bb62bf8005cc8f5f9fc06b88902fd9fab05a (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
<!doctype html>
<meta charset=utf-8>
<title>payload type handling (assuming rtcp-mux)</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../third_party/sdp/sdp.js"></script>
<script>
'use strict';
// Tests behaviour from https://www.rfc-editor.org/rfc/rfc8834.html#name-header-extensions

function createOfferSdp(extmaps) {
  let sdp = `v=0
o=- 0 3 IN IP4 127.0.0.1
s=-
t=0 0
a=fingerprint:sha-256 A7:24:72:CA:6E:02:55:39:BA:66:DF:6E:CC:4C:D8:B0:1A:BF:1A:56:65:7D:F4:03:AD:7E:77:43:2A:29:EC:93
a=ice-ufrag:6HHHdzzeIhkE0CKj
a=ice-pwd:XYDGVpfvklQIEnZ6YnyLsAew
`;
  sdp += 'a=group:BUNDLE ' + ['audio', 'video'].filter(kind => extmaps[kind]).join(' ') + '\r\n';
  if (extmaps.audio) {
    sdp += `m=audio 9 RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=rtcp-mux
a=sendonly
a=mid:audio
a=rtpmap:111 opus/48000/2
a=setup:actpass
` + extmaps.audio.map(ext => SDPUtils.writeExtmap(ext)).join('');
  }
  if (extmaps.video) {
    sdp += `m=video 9 RTP/SAVPF 112
c=IN IP4 0.0.0.0
a=rtcp-mux
a=sendonly
a=mid:video
a=rtpmap:112 VP8/90000
a=setup:actpass
` + extmaps.video.map(ext => SDPUtils.writeExtmap(ext)).join('');
  }
  return sdp;
}

[
  // https://www.rfc-editor.org/rfc/rfc8834.html#section-5.2.4
  {
    audio: [{id: 1, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid'}],
    video: [{id: 1, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid'}],
    description: 'MID',
  },
  {
    // https://www.rfc-editor.org/rfc/rfc8834.html#section-5.2.2
    audio: [{id: 1, uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level'}],
    description: 'Audio level',
  },
  {
    // https://www.rfc-editor.org/rfc/rfc8834.html#section-5.2.5
    video: [{id: 1, uri: 'urn:3gpp:video-orientation'}],
    description: 'Video orientation',
  }
].forEach(testcase => {
  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    await pc.setRemoteDescription({type: 'offer', sdp: createOfferSdp(testcase)});
    const answer = await pc.createAnswer();
    const sections = SDPUtils.splitSections(answer.sdp);
    sections.shift();
    sections.forEach(section => {
      const rtpParameters = SDPUtils.parseRtpParameters(section);
      assert_equals(rtpParameters.headerExtensions.length, 1);
      assert_equals(rtpParameters.headerExtensions[0].id, testcase[SDPUtils.getKind(section)][0].id);
      assert_equals(rtpParameters.headerExtensions[0].uri, testcase[SDPUtils.getKind(section)][0].uri);
    });
  }, testcase.description + ' header extension is supported.');
});

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

  pc.addTransceiver('video');
  const offer = await pc.createOffer();
  const section = SDPUtils.splitSections(offer.sdp)[1];
  const extensions = SDPUtils.matchPrefix(section, 'a=extmap:')
    .map(line => SDPUtils.parseExtmap(line));
  const extension_not_mid = extensions.find(e => e.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid');
  await pc.setRemoteDescription({type :'offer', sdp: offer.sdp.replace(extension_not_mid.uri, 'bogus')});

  await pc.setLocalDescription();
  const answer_section = SDPUtils.splitSections(pc.localDescription.sdp)[1];
  const answer_extensions = SDPUtils.matchPrefix(answer_section, 'a=extmap:')
    .map(line => SDPUtils.parseExtmap(line));
  assert_equals(answer_extensions.length, extensions.length - 1);
  assert_false(!!extensions.find(e => e.uri === 'bogus'));
  for (const answer_extension of answer_extensions) {
    assert_true(!!extensions.find(e => e.uri === answer_extension.uri));
  }
}, 'Negotiates the subset of supported extensions offered');

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

  // Some implementations may refuse 15 as invalid id because of
  // https://www.rfc-editor.org/rfc/rfc8285#section-4.2
  // which only applies to one-byte extensions with ids 0-14.
  const sdp = createOfferSdp({audio: [{
    id: 15, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid',
  }]});
  await pc.setRemoteDescription({type: 'offer', sdp});
}, 'Supports header extensions with id=15');

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

  const sdp = createOfferSdp({audio: [{
    id: 16, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid',
  }, {
    id: 17, uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',
  }]});
  await pc.setRemoteDescription({type: 'offer', sdp});
  await pc.setLocalDescription();
  const answer_section = SDPUtils.splitSections(pc.localDescription.sdp)[1];
  const answer_extensions = SDPUtils.matchPrefix(answer_section, 'a=extmap:')
    .map(line => SDPUtils.parseExtmap(line));
  assert_equals(answer_extensions.length, 2);
  assert_true(!!answer_extensions.find(e => e.uri === 'urn:ietf:params:rtp-hdrext:sdes:mid'));
  assert_true(!!answer_extensions.find(e => e.uri === 'urn:ietf:params:rtp-hdrext:ssrc-audio-level'));
}, 'Supports two-byte header extensions');
</script>