import { test } from "node:test"; import assert from "node:assert/strict"; import parse from "ltx/lib/parse.js"; import { xml } from "@xmpp/client"; import { candidateToElement, candidateToSdp, iceCredentials, jingleToSdp, mediaOf, sdpToJingle } from "../src/call/jingle.ts"; const el = (s: string) => parse(s) as any; // createOffer() of HeadlessChrome 154 with one audio track (2026-09-29). const chromeOffer = [ "v=0", "o=- 4672937091243051060 2 IN IP4 127.0.0.1", "s=-", "t=0 0", "a=group:BUNDLE 0", "a=extmap-allow-mixed", "a=msid-semantic: WMS 03a9ddca-6bcd-4716-96fe-c8c235f860dc", "m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126", "c=IN IP4 0.0.0.0", "a=rtcp:9 IN IP4 0.0.0.0", "a=ice-ufrag:qk6w", "a=ice-pwd:ZG57QvXY+/NrUiHOgyhdPPGf", "a=ice-options:trickle", "a=fingerprint:sha-256 AA:3E:52:FB:2D:C7:B9:98:4D:28:B7:A3:36:B4:A8:91:5D:91:83:C6:99:71:2F:06:7A:B1:55:81:B1:C2:2B:48", "a=setup:actpass", "a=mid:0", "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level", "a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", "a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", "a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid", "a=sendrecv", "a=msid:03a9ddca-6bcd-4716-96fe-c8c235f860dc afdb8a22-1c3e-4d29-87c4-243624a64a4b", "a=rtcp-mux", "a=rtcp-rsize", "a=rtpmap:111 opus/48000/2", "a=rtcp-fb:111 transport-cc", "a=fmtp:111 minptime=10;useinbandfec=1", "a=rtpmap:63 red/48000/2", "a=fmtp:63 111/111", "a=rtpmap:9 G722/8000", "a=rtpmap:0 PCMU/8000", "a=rtpmap:8 PCMA/8000", "a=rtpmap:13 CN/8000", "a=rtpmap:110 telephone-event/48000", "a=rtpmap:126 telephone-event/8000", "a=ssrc:743182082 cname:m9CPfpHn7vEwqprQ", "a=ssrc:743182082 msid:03a9ddca-6bcd-4716-96fe-c8c235f860dc afdb8a22-1c3e-4d29-87c4-243624a64a4b", "", ].join("\r\n"); // A session-initiate as Monal's sdp-to-jingle writes it (shape from its // SDPTest.m), audio only, with a relay candidate on a documentation address. const monalInitiate = ` 3D:7C:7D:EC:CC:84:39:F2:46:A9:10:03:9E:09:FD:4D:E7:9A:49:6D:54:84:F5:5A:10:C1:09:A3:1F:B1:68:D2 `; const wrap = (children: any[]) => { const j = xml("jingle", { xmlns: "urn:xmpp:jingle:1", action: "session-initiate", sid: "s1" }); j.append(...children); // Through a string, as the peer would receive it. return el(j.toString()); }; test("Chrome offer as Jingle", () => { const [group, content] = sdpToJingle(chromeOffer, true); assert.equal(group.attrs.semantics, "BUNDLE"); assert.deepEqual(group.getChildren("content").map((c: any) => c.attrs.name), ["0"]); assert.equal(content.attrs.creator, "initiator"); assert.equal(content.attrs.name, "0"); assert.equal(content.attrs.senders, "both"); const desc = content.getChild("description", "urn:xmpp:jingle:apps:rtp:1"); assert.equal(desc.attrs.media, "audio"); const pts = desc.getChildren("payload-type"); assert.deepEqual(pts.map((p: any) => p.attrs.id), ["111", "63", "9", "0", "8", "13", "110", "126"]); const opus = pts[0]; assert.deepEqual(opus.attrs, { id: "111", name: "opus", clockrate: "48000", channels: "2" }); assert.deepEqual( opus.getChildren("parameter").map((p: any) => `${p.attrs.name}=${p.attrs.value}`), ["minptime=10", "useinbandfec=1"], ); assert.equal(opus.getChild("rtcp-fb").attrs.type, "transport-cc"); assert.deepEqual(pts[1].getChildren("parameter").map((p: any) => p.attrs.value), ["111", "111"]); assert.equal(pts[2].attrs.channels, undefined); assert.equal(desc.getChildren("rtp-hdrext").length, 4); assert.ok(desc.getChild("rtcp-mux")); const source = desc.getChild("source", "urn:xmpp:jingle:apps:rtp:ssma:0"); assert.equal(source.attrs.ssrc, "743182082"); assert.equal(source.getChildren("parameter")[0].attrs.value, "m9CPfpHn7vEwqprQ"); const transport = content.getChild("transport", "urn:xmpp:jingle:transports:ice-udp:1"); assert.equal(transport.attrs.ufrag, "qk6w"); assert.equal(transport.attrs.pwd, "ZG57QvXY+/NrUiHOgyhdPPGf"); const fp = transport.getChild("fingerprint", "urn:xmpp:jingle:apps:dtls:0"); assert.equal(fp.attrs.hash, "sha-256"); assert.equal(fp.attrs.setup, "actpass"); assert.match(fp.text(), /^AA:3E:52/); }); test("Chrome offer survives the round trip through Jingle", () => { const sdp = jingleToSdp(wrap(sdpToJingle(chromeOffer, true)), "s1", true); for (const line of [ "a=group:BUNDLE 0", "m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126", "a=ice-ufrag:qk6w", "a=ice-pwd:ZG57QvXY+/NrUiHOgyhdPPGf", "a=setup:actpass", "a=mid:0", "a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", "a=sendrecv", "a=rtcp-mux", "a=rtpmap:111 opus/48000/2", "a=rtcp-fb:111 transport-cc", "a=fmtp:111 minptime=10;useinbandfec=1", "a=fmtp:63 111/111", "a=rtpmap:9 G722/8000", "a=ssrc:743182082 cname:m9CPfpHn7vEwqprQ", ]) { assert.ok(sdp.split("\r\n").includes(line), `missing ${line}\n${sdp}`); } assert.match(sdp, /^v=0\r\n/); assert.match(sdp, /a=fingerprint:sha-256 AA:3E:52:FB/); }); test("Monal's session-initiate as SDP for the browser", () => { const sdp = jingleToSdp(el(monalInitiate), "0d6e9a52-8c5b-4c3e-9f5e-2a1b3c4d5e6f", true).split("\r\n"); assert.ok(sdp.includes("m=audio 9 UDP/TLS/RTP/SAVPF 111 63 0 126")); // Booleans back as 1, the H.264 default dropped from audio codecs. assert.ok(sdp.includes("a=fmtp:111 useinbandfec=1;minptime=10")); assert.ok(sdp.includes("a=fmtp:63 111/111")); assert.ok(sdp.includes("a=ice-options:trickle renomination")); assert.ok(sdp.includes("a=fingerprint:sha-256 3D:7C:7D:EC:CC:84:39:F2:46:A9:10:03:9E:09:FD:4D:E7:9A:49:6D:54:84:F5:5A:10:C1:09:A3:1F:B1:68:D2")); assert.ok(sdp.includes("a=candidate:1076231993 1 udp 41885694 198.51.100.52 50002 typ relay raddr 0.0.0.0 rport 0 generation 0")); assert.ok(sdp.includes("a=ssrc:388312053 msid:stream audio0")); assert.ok(sdp.includes("a=extmap-allow-mixed")); assert.ok(!sdp.some((l) => l.includes("profile"))); }); test("Monal's H.264 parameters as one fmtp line each", () => { const video = el(` `); const sdp = jingleToSdp(video, "x", true).split("\r\n"); assert.ok(sdp.includes("m=video 9 UDP/TLS/RTP/SAVPF 102 103")); assert.ok(sdp.includes("a=fmtp:102 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42001f")); assert.ok(sdp.includes("a=fmtp:103 apt=102")); }); test("senders follow who sends, relative to the initiator", () => { const answer = chromeOffer.replace("a=sendrecv", "a=recvonly").replace("a=setup:actpass", "a=setup:active"); const [, content] = sdpToJingle(answer, false); // The responder only receives: only the initiator sends. assert.equal(content.attrs.senders, "initiator"); assert.equal(content.getChild("transport").getChild("fingerprint").attrs.setup, "active"); // Read back by the initiator, it is the responder's recvonly again. assert.ok(jingleToSdp(wrap([content]), "s1", false).includes("a=recvonly")); assert.ok(jingleToSdp(wrap([content]), "s1", true).includes("a=sendonly")); }); test("candidates both ways", () => { const srflx = "candidate:842163049 1 udp 1677729535 203.0.113.7 46154 typ srflx raddr 192.0.2.10 rport 46154 generation 0 ufrag qk6w network-id 1 network-cost 10"; const c = candidateToElement(srflx)!; assert.equal(c.attrs.type, "srflx"); assert.equal(c.attrs.ip, "203.0.113.7"); assert.equal(c.attrs["rel-addr"], "192.0.2.10"); assert.equal(c.attrs["rel-port"], "46154"); assert.equal(c.attrs.generation, "0"); assert.equal(c.attrs.network, "1"); assert.ok(c.attrs.id); assert.equal(candidateToSdp(c), "candidate:842163049 1 udp 1677729535 203.0.113.7 46154 typ srflx raddr 192.0.2.10 rport 46154 generation 0"); const tcp = candidateToElement("a=candidate:4250347086 1 tcp 1518214911 192.0.2.10 9 typ host tcptype active generation 0 network-id 1")!; assert.equal(tcp.attrs["tcp-type"], "active"); assert.equal(candidateToSdp(tcp), "candidate:4250347086 1 tcp 1518214911 192.0.2.10 9 typ host tcptype active generation 0"); assert.equal(candidateToElement("candidate:bogus"), null); }); test("ICE credentials per content and media of a propose", () => { assert.deepEqual([...iceCredentials(chromeOffer)], [["0", { ufrag: "qk6w", pwd: "ZG57QvXY+/NrUiHOgyhdPPGf" }]]); const propose = el(``); assert.deepEqual(mediaOf(propose), ["audio", "video"]); assert.deepEqual(mediaOf(el(monalInitiate)), ["audio"]); }); test("Opus comes first, whatever order the peer listed", () => { const j = el(monalInitiate.replace(/()([\s\S]*?)( l.startsWith("m=audio")); assert.equal(m, "m=audio 9 UDP/TLS/RTP/SAVPF 111 63 0 126"); });