Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
<!DOCTYPE HTML>
<html>
<script type="application/javascript" src="pc.js"></script>
<head>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1927886",
title: "Test that usrsctp and dcsctp interop"
});
async function createChannel(pc, impl, name, options) {
async function create() {
return pc.createDataChannel(name, options);
}
if (impl == "usrsctp") {
return withPrefs([["media.peerconnection.sctp.use_dcsctp", false]], create);
} else if (impl == "dcsctp" ){
return withPrefs([["media.peerconnection.sctp.use_dcsctp", true]], create);
} else {
throw new Error(`Unknown sctp impl "${impl}"`);
}
}
async function connect(offerer, answerer) {
offerer.onicecandidate = e => answerer.addIceCandidate(e.candidate);
answerer.onicecandidate = e => offerer.addIceCandidate(e.candidate);
const offererConnected = new Promise(r => {
offerer.oniceconnectionstatechange = () => {
if (offerer.iceConnectionState == 'connected') {
r();
}
};
});
const answererConnected = new Promise(r => {
answerer.oniceconnectionstatechange = () => {
if (answerer.iceConnectionState == 'connected') {
r();
}
};
});
await offerer.setLocalDescription();
await answerer.setRemoteDescription(offerer.localDescription);
await answerer.setLocalDescription();
await offerer.setRemoteDescription(answerer.localDescription);
await offererConnected;
await answererConnected;
}
async function channelOpen(dc) {
return new Promise((res, rej) => {
if (dc.readyState == "open") {
res();
} else {
dc.onopen = res;
dc.onclose = rej;
dc.onerror = rej;
}
});
}
async function channelCreated(pc, label) {
return new Promise((res, rej) => {
pc.addEventListener("datachannel", (e) => {
if (label === undefined || label == e.channel.label) {
res(e);
}
});
pc.addEventListener("connectionstatechange", e => {
if (pc.connectionState == "closed") {
rej("PC closed before channel was created");
}
});
});
}
async function sendRecvMessages(sender, receiver, messages) {
if (!Array.isArray(messages)) {
messages = [messages];
}
const received = [];
messages.forEach(msg => sender.send(msg));
return new Promise((res, rej) => {
receiver.onmessage = (e) => {
received.push(e.data);
if (received.length == messages.length) {
res(received);
}
};
receiver.onerror = rej;
receiver.onclose = rej;
});
}
async function sendRecvMessagesUnreliable(sender, receiver, messages) {
if (!Array.isArray(messages)) {
messages = [messages];
}
const received = [];
messages.forEach(msg => sender.send(msg));
return new Promise((res, rej) => {
// Wait at most a couple of seconds; there's no guarantee all will arrive
setTimeout(() => res(received), 2000);
receiver.onmessage = (e) => {
received.push(e.data);
if (received.length == messages.length) {
res(received);
}
};
receiver.onerror = rej;
receiver.onclose = rej;
});
}
function checkMessages(expected, observed, options) {
let expectedCopy;
// Default value for ordered is true. Why they didn't use "unordered" with
// a default of false is anyone's guess.
if (options && options.ordered === false) {
expectedCopy = expected.toSorted();
observed.sort();
} else {
expectedCopy = new Array(...expected.values());
}
if (options &&
(options.maxRetransmits !== undefined ||
options.maxPacketLifetime !== undefined)) {
// Realistically, what can we check here? Packet loss should be rare, but
// with lots of messages it can happen. Maybe just sanity check that at
// least one got through, and that everything we observed could be found
// in what we sent, in the right order?
ok(observed.length,
"Should have received at least one of the sent messages");
ok(observed.length <= expectedCopy.length,
`Should not have received more messages (${observed.length}) than we sent (${expectedCopy.length})`);
observed.forEach(msg => {
const index = expectedCopy.indexOf(msg);
if (index < 0) {
ok(false, `We observed ${msg}, so we should have sent it.`);
}
expectedCopy = expectedCopy.slice(index + 1);
});
} else {
isDeeply(observed, expectedCopy, `Expected to have received the messages we sent`);
}
}
async function checkSendRecv(sender, receiver, msgs, options) {
let observedMsgs = [];
if (options &&
(options.maxRetransmits !== undefined ||
options.maxPacketLifetime !== undefined)) {
observedMsgs = await sendRecvMessagesUnreliable(sender, receiver, msgs);
} else {
observedMsgs = await sendRecvMessages(sender, receiver, msgs);
}
checkMessages(msgs, observedMsgs, options);
}
async function checkSingleChannel(impl1, impl2, msgs, options) {
if (!Array.isArray(msgs)) {
msgs = [msgs];
}
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
const dc1 = await createChannel(offerer, impl1, "test", options);
const dcsctp = impl2 == "dcsctp";
await withPrefs([["media.peerconnection.sctp.use_dcsctp", dcsctp]], async () => {
const dc1Open = channelOpen(dc1);
let dc2;
if (options && options.negotiated) {
dc2 = await createChannel(answerer, impl2, "test", options);
await connect(offerer, answerer);
} else {
const dc2Created = channelCreated(answerer);
await connect(offerer, answerer);
dc2 = (await dc2Created).channel;
}
await channelOpen(dc2);
await dc1Open;
// Ping pong to ensure that any open acks have made it across.
// Even if we're in unreliable mode, we should not see packet loss on
// something this small.
await sendRecvMessages(dc1, dc2, ["ping"]);
await sendRecvMessages(dc2, dc1, ["pong"]);
await checkSendRecv(dc1, dc2, msgs, options);
await checkSendRecv(dc2, dc1, msgs, options);
});
}
async function checkMultiChannel(impl1, impl2, msgs, optionsArray) {
if (!Array.isArray(msgs)) {
msgs = [msgs];
}
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
const dcs1 = await Promise.all(optionsArray.map(
(options, index) => createChannel(offerer, impl1, `test_${index}`, options)));
const dcsctp = impl2 == "dcsctp";
await withPrefs([["media.peerconnection.sctp.use_dcsctp", dcsctp]], async () => {
const dcs1Open = dcs1.map(dc => channelOpen(dc));
const dcs2Created = optionsArray.map(async (options, index) => {
if (options && options.negotiated) {
return createChannel(answerer, impl2, `test_${index}`, options);
} else {
return (await channelCreated(answerer, `test_${index}`)).channel;
}
});
await connect(offerer, answerer);
const dcs2 = await Promise.all(dcs2Created);
await Promise.all(dcs2.map(dc => channelOpen(dc)));
await Promise.all(dcs1Open);
// We are ordering these by label, but make sure the ids line up too
isDeeply(dcs1.map(dc => dc.id), dcs2.map(dc => dc.id),
"Channels should have the same ids");
// Ping pong to give acks time to come across.
// Even if we're in unreliable mode, we should not see packet loss on
// something this small.
await sendRecvMessages(dcs1[0], dcs2[0], ["ping"]);
await sendRecvMessages(dcs2[0], dcs1[0], ["pong"]);
await Promise.all(dcs1.map(
(dc, index) => checkSendRecv(dc, dcs2[index], msgs, optionsArray[index])));
await Promise.all(dcs2.map(
(dc, index) => checkSendRecv(dc, dcs1[index], msgs, optionsArray[index])));
});
}
async function runTestVariants(func, ...args) {
for (const [impl1, impl2] of
[["usrsctp", "usrsctp"], ["dcsctp", "dcsctp"],
["usrsctp", "dcsctp"], ["dcsctp", "usrsctp"]]) {
info(`Running variant ${impl1}/${impl2}`);
await func(impl1, impl2, ...args);
info(`Done running variant ${impl1}/${impl2}`);
}
}
const tests = [
async function testBasicOperation() {
await runTestVariants(checkSingleChannel, "test_message");
},
async function testNegotiated() {
await runTestVariants(checkSingleChannel, "test_message",
{negotiated: true, id:42});
},
async function testLargeMessage() {
const largeMessage = "test".repeat(100000);
await runTestVariants(checkSingleChannel, largeMessage);
},
async function testManyMessages() {
const messageArray = [];
for (let i = 0; i < 1000; i++) {
messageArray.push(`test_${i}`);
}
await runTestVariants(checkSingleChannel, messageArray);
},
async function testUnordered() {
const messageArray = [];
for (let i = 0; i < 1000; i++) {
messageArray.push(`test_${i}`);
}
await runTestVariants(checkSingleChannel, messageArray, {ordered: false});
},
async function testNoRetransmit() {
await runTestVariants(checkSingleChannel, "test_message",
{maxRetransmits: 0});
},
async function testNoTTL() {
await runTestVariants(checkSingleChannel, "test_message",
{maxPacketLifetime: 0});
},
async function test1Retransmit() {
const messageArray = [];
for (let i = 0; i < 1000; i++) {
messageArray.push(`test_${i}`);
}
await runTestVariants(checkSingleChannel, messageArray,
{maxRetransmits: 1});
},
async function testShortTTL() {
const messageArray = [];
for (let i = 0; i < 1000; i++) {
messageArray.push(`test_${i}`);
}
await runTestVariants(checkSingleChannel, messageArray,
{maxPacketLifetime: 1});
},
async function testLargeStreamId() {
await runTestVariants(checkSingleChannel, "test_message",
{negotiated: true, id: 2000});
},
async function testMultiChannel() {
await runTestVariants(checkMultiChannel, "test_message", [{}, {}]);
},
async function testManyChannel() {
const bigOptionsArray = [];
for (let i = 0; i < 100; i++) {
bigOptionsArray.push({});
}
await runTestVariants(checkMultiChannel, "test_message", bigOptionsArray);
},
async function testMixedNegotiated() {
await runTestVariants(checkMultiChannel, "test_message",
[{negotiated: true, id:42}, {}]);
},
async function testMixedReliability() {
await runTestVariants(checkMultiChannel,
["test_message1", "test_message2"],
[{maxRetransmits: 0}, {maxRetransmits: 1}, {maxPacketLifetime: 0},
{maxPacketLifetime: 1}, {}]);
},
async function testMixedOrdered() {
await runTestVariants(checkMultiChannel,
["test_message1", "test_message2"],
[{ordered: false}, {ordered: true}, {}]);
},
];
runNetworkTest(async () => {
for (const test of tests) {
info(`Running test: ${test.name}`);
await test();
info(`Done running test: ${test.name}`);
// Make sure we don't build up a pile of GC work, and also get PCImpl to
// print their timecards.
await new Promise(r => SpecialPowers.exactGC(r));
}
});
</script>
</pre>
</body>
</html>