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
|
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="iceTestUtils.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1799932",
title: "RTCPeerConnection check renegotiation of extmap"
});
function setExtmap(sdp, uri, id) {
const regex = new RegExp(`a=extmap:[0-9]+(\/[a-z]+)? ${uri}`, 'g');
if (id) {
return sdp.replaceAll(regex, `a=extmap:${id}$1 ${uri}`);
} else {
return sdp.replaceAll(regex, `a=unknownattr`);
}
}
function getExtmap(sdp, uri) {
const regex = new RegExp(`a=extmap:([0-9]+)(\/[a-z]+)? ${uri}`);
return sdp.match(regex)[1];
}
function replaceExtUri(sdp, oldUri, newUri) {
const regex = new RegExp(`(a=extmap:[0-9]+\/[a-z]+)? ${oldUri}`, 'g');
return sdp.replaceAll(regex, `$1 ${newUri}`);
}
const tests = [
async function checkAudioMidChange() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await connect(pc1, pc2, 32000, "Initial connection");
// Sadly, there's no way to tell the offerer to change the extmap. Other
// types of endpoint could conceivably do this, so we at least don't want
// to crash.
// TODO: Would be nice to be able to test this with an endpoint that
// actually changes the ids it uses.
const reoffer = await pc1.createOffer();
reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", 14);
info(`New reoffer: ${reoffer.sdp}`);
await pc2.setRemoteDescription(reoffer);
await pc2.setLocalDescription();
await wait(2000);
},
async function checkVideoMidChange() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({video: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await connect(pc1, pc2, 32000, "Initial connection");
// Sadly, there's no way to tell the offerer to change the extmap. Other
// types of endpoint could conceivably do this, so we at least don't want
// to crash.
// TODO: Would be nice to be able to test this with an endpoint that
// actually changes the ids it uses.
const reoffer = await pc1.createOffer();
reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", 14);
info(`New reoffer: ${reoffer.sdp}`);
await pc2.setRemoteDescription(reoffer);
await pc2.setLocalDescription();
await wait(2000);
},
async function checkAudioMidSwap() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await connect(pc1, pc2, 32000, "Initial connection");
// Sadly, there's no way to tell the offerer to change the extmap. Other
// types of endpoint could conceivably do this, so we at least don't want
// to crash.
// TODO: Would be nice to be able to test this with an endpoint that
// actually changes the ids it uses.
const reoffer = await pc1.createOffer();
const midId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid");
const ssrcLevelId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", ssrcLevelId);
reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", midId);
info(`New reoffer: ${reoffer.sdp}`);
try {
await pc2.setRemoteDescription(reoffer);
ok(false, "sRD should fail when it attempts extension id remapping");
} catch (e) {
ok(true, "sRD should fail when it attempts extension id remapping");
}
},
async function checkVideoMidSwap() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({video: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await connect(pc1, pc2, 32000, "Initial connection");
// Sadly, there's no way to tell the offerer to change the extmap. Other
// types of endpoint could conceivably do this, so we at least don't want
// to crash.
// TODO: Would be nice to be able to test this with an endpoint that
// actually changes the ids it uses.
const reoffer = await pc1.createOffer();
const midId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid");
const toffsetId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset");
reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", toffsetId);
reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset", midId);
info(`New reoffer: ${reoffer.sdp}`);
try {
await pc2.setRemoteDescription(reoffer);
ok(false, "sRD should fail when it attempts extension id remapping");
} catch (e) {
ok(true, "sRD should fail when it attempts extension id remapping");
}
},
async function checkAudioIdReuse() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await connect(pc1, pc2, 32000, "Initial connection");
// Sadly, there's no way to tell the offerer to change the extmap. Other
// types of endpoint could conceivably do this, so we at least don't want
// to crash.
// TODO: Would be nice to be able to test this with an endpoint that
// actually changes the ids it uses.
const reoffer = await pc1.createOffer();
// Change uri, but not the id, so the id now refers to foo.
reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "foo");
info(`New reoffer: ${reoffer.sdp}`);
try {
await pc2.setRemoteDescription(reoffer);
ok(false, "sRD should fail when it attempts extension id remapping");
} catch (e) {
ok(true, "sRD should fail when it attempts extension id remapping");
}
},
async function checkVideoIdReuse() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({video: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await connect(pc1, pc2, 32000, "Initial connection");
// Sadly, there's no way to tell the offerer to change the extmap. Other
// types of endpoint could conceivably do this, so we at least don't want
// to crash.
// TODO: Would be nice to be able to test this with an endpoint that
// actually changes the ids it uses.
const reoffer = await pc1.createOffer();
// Change uri, but not the id, so the id now refers to foo.
reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset", "foo");
info(`New reoffer: ${reoffer.sdp}`);
try {
await pc2.setRemoteDescription(reoffer);
ok(false, "sRD should fail when it attempts extension id remapping");
} catch (e) {
ok(true, "sRD should fail when it attempts extension id remapping");
}
},
// What happens when remote answer uses an extmap id, and then a remote
// reoffer tries to use the same id for something else?
async function checkAudioIdReuseOffererThenAnswerer() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await connect(pc1, pc2, 32000, "Initial connection");
const reoffer = await pc2.createOffer();
// Change uri, but not the id, so the id now refers to foo.
reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "foo");
info(`New reoffer: ${reoffer.sdp}`);
try {
await pc1.setRemoteDescription(reoffer);
ok(false, "sRD should fail when it attempts extension id remapping");
} catch (e) {
ok(true, "sRD should fail when it attempts extension id remapping");
}
},
// What happens when a remote offer uses a different extmap id than the
// default? Does the answerer remember the new id in reoffers?
async function checkAudioIdReuseOffererThenAnswerer() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
// Negotiate, but change id for ssrc-audio-level to something pc2 would
// not typically use.
await pc1.setLocalDescription();
const mungedOffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 12);
await pc2.setRemoteDescription({sdp: mungedOffer, type: "offer"});
await pc2.setLocalDescription();
const reoffer = await pc2.createOffer();
is(getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"), "12");
},
async function checkAudioUnnegotiatedIdReuse1() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
// Negotiate, but remove ssrc-audio-level from answer
await pc1.setLocalDescription();
const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription();
const answerNoExt = setExtmap(pc2.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
await pc1.setRemoteDescription({sdp: answerNoExt, type: "answer"});
// Renegotiate, and use the id that offerer used for ssrc-audio-level for
// something different (while making sure we don't use it twice)
await pc2.setLocalDescription();
const mungedReoffer = setExtmap(pc2.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId);
const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
await pc1.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"});
},
async function checkAudioUnnegotiatedIdReuse2() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
// Negotiate, but remove ssrc-audio-level from offer. pc2 has never seen
// |levelId| in extmap yet, but internally probably wants to use that for
// ssrc-audio-level
await pc1.setLocalDescription();
const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
const offerNoExt = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
await pc2.setRemoteDescription({sdp: offerNoExt, type: "offer"});
await pc2.setLocalDescription();
await pc1.setRemoteDescription(pc2.localDescription);
// Renegotiate, but use |levelId| for something other than
// ssrc-audio-level. pc2 should not throw.
await pc1.setLocalDescription();
const mungedReoffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId);
const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
await pc2.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"});
},
async function checkAudioUnnegotiatedIdReuse3() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
// Negotiate, but replace ssrc-audio-level with something pc2 won't
// support in offer.
await pc1.setLocalDescription();
const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
const mungedOffer = replaceExtUri(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "fooba");
await pc2.setRemoteDescription({sdp: mungedOffer, type: "offer"});
await pc2.setLocalDescription();
await pc1.setRemoteDescription(pc2.localDescription);
// Renegotiate, and use levelId for something pc2 _will_ support.
await pc1.setLocalDescription();
const mungedReoffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId);
const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
await pc2.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"});
},
];
runNetworkTest(async () => {
for (const test of tests) {
info(`Running test: ${test.name}`);
await test();
info(`Done running test: ${test.name}`);
}
});
</script>
</pre>
</body>
</html>
|