From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../tests/mochitests/helpers_from_wpt/sdp.js | 889 +++++++++++++++++++++ 1 file changed, 889 insertions(+) create mode 100644 dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js (limited to 'dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js') diff --git a/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js b/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js new file mode 100644 index 0000000000..6460f64a44 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js @@ -0,0 +1,889 @@ +/* eslint-env node */ +"use strict"; + +// SDP helpers. +var SDPUtils = {}; + +// Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 +SDPUtils.generateIdentifier = function() { + return Math.random() + .toString(36) + .substr(2, 10); +}; + +// The RTCP CNAME used by all peerconnections from the same JS. +SDPUtils.localCName = SDPUtils.generateIdentifier(); + +// Splits SDP into lines, dealing with both CRLF and LF. +SDPUtils.splitLines = function(blob) { + return blob + .trim() + .split("\n") + .map(function(line) { + return line.trim(); + }); +}; +// Splits SDP into sessionpart and mediasections. Ensures CRLF. +SDPUtils.splitSections = function(blob) { + var parts = blob.split("\nm="); + return parts.map(function(part, index) { + return (index > 0 ? "m=" + part : part).trim() + "\r\n"; + }); +}; + +// returns the session description. +SDPUtils.getDescription = function(blob) { + var sections = SDPUtils.splitSections(blob); + return sections && sections[0]; +}; + +// returns the individual media sections. +SDPUtils.getMediaSections = function(blob) { + var sections = SDPUtils.splitSections(blob); + sections.shift(); + return sections; +}; + +// Returns lines that start with a certain prefix. +SDPUtils.matchPrefix = function(blob, prefix) { + return SDPUtils.splitLines(blob).filter(function(line) { + return line.indexOf(prefix) === 0; + }); +}; + +// Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" +SDPUtils.parseCandidate = function(line) { + var parts; + // Parse both variants. + if (line.indexOf("a=candidate:") === 0) { + parts = line.substring(12).split(" "); + } else { + parts = line.substring(10).split(" "); + } + + var candidate = { + foundation: parts[0], + component: parseInt(parts[1], 10), + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + address: parts[4], // address is an alias for ip. + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7], + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case "raddr": + candidate.relatedAddress = parts[i + 1]; + break; + case "rport": + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case "tcptype": + candidate.tcpType = parts[i + 1]; + break; + case "ufrag": + candidate.ufrag = parts[i + 1]; // for backward compability. + candidate.usernameFragment = parts[i + 1]; + break; + default: + // extension handling, in particular ufrag + candidate[parts[i]] = parts[i + 1]; + break; + } + } + return candidate; +}; + +// Translates a candidate object into SDP candidate attribute. +SDPUtils.writeCandidate = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.address || candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push("typ"); + sdp.push(type); + if (type !== "host" && candidate.relatedAddress && candidate.relatedPort) { + sdp.push("raddr"); + sdp.push(candidate.relatedAddress); + sdp.push("rport"); + sdp.push(candidate.relatedPort); + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === "tcp") { + sdp.push("tcptype"); + sdp.push(candidate.tcpType); + } + if (candidate.usernameFragment || candidate.ufrag) { + sdp.push("ufrag"); + sdp.push(candidate.usernameFragment || candidate.ufrag); + } + return "candidate:" + sdp.join(" "); +}; + +// Parses an ice-options line, returns an array of option tags. +// a=ice-options:foo bar +SDPUtils.parseIceOptions = function(line) { + return line.substr(14).split(" "); +}; + +// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// a=rtpmap:111 opus/48000/2 +SDPUtils.parseRtpMap = function(line) { + var parts = line.substr(9).split(" "); + var parsed = { + payloadType: parseInt(parts.shift(), 10), // was: id + }; + + parts = parts[0].split("/"); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + // legacy alias, got renamed back to channels in ORTC. + parsed.numChannels = parsed.channels; + return parsed; +}; + +// Generate an a=rtpmap line from RTCRtpCodecCapability or +// RTCRtpCodecParameters. +SDPUtils.writeRtpMap = function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + var channels = codec.channels || codec.numChannels || 1; + return ( + "a=rtpmap:" + + pt + + " " + + codec.name + + "/" + + codec.clockRate + + (channels !== 1 ? "/" + channels : "") + + "\r\n" + ); +}; + +// Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset +SDPUtils.parseExtmap = function(line) { + var parts = line.substr(9).split(" "); + return { + id: parseInt(parts[0], 10), + direction: parts[0].indexOf("/") > 0 ? parts[0].split("/")[1] : "sendrecv", + uri: parts[1], + }; +}; + +// Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. +SDPUtils.writeExtmap = function(headerExtension) { + return ( + "a=extmap:" + + (headerExtension.id || headerExtension.preferredId) + + (headerExtension.direction && headerExtension.direction !== "sendrecv" + ? "/" + headerExtension.direction + : "") + + " " + + headerExtension.uri + + "\r\n" + ); +}; + +// Parses an ftmp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on +SDPUtils.parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(" ") + 1).split(";"); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split("="); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; +}; + +// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeFmtp = function(codec) { + var line = ""; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function(param) { + if (codec.parameters[param]) { + params.push(param + "=" + codec.parameters[param]); + } else { + params.push(param); + } + }); + line += "a=fmtp:" + pt + " " + params.join(";") + "\r\n"; + } + return line; +}; + +// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi +SDPUtils.parseRtcpFb = function(line) { + var parts = line.substr(line.indexOf(" ") + 1).split(" "); + return { + type: parts.shift(), + parameter: parts.join(" "), + }; +}; +// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeRtcpFb = function(codec) { + var lines = ""; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + lines += + "a=rtcp-fb:" + + pt + + " " + + fb.type + + (fb.parameter && fb.parameter.length ? " " + fb.parameter : "") + + "\r\n"; + }); + } + return lines; +}; + +// Parses an RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something +SDPUtils.parseSsrcMedia = function(line) { + var sp = line.indexOf(" "); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10), + }; + var colon = line.indexOf(":", sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; +}; + +SDPUtils.parseSsrcGroup = function(line) { + var parts = line.substr(13).split(" "); + return { + semantics: parts.shift(), + ssrcs: parts.map(function(ssrc) { + return parseInt(ssrc, 10); + }), + }; +}; + +// Extracts the MID (RFC 5888) from a media section. +// returns the MID or undefined if no mid line was found. +SDPUtils.getMid = function(mediaSection) { + var mid = SDPUtils.matchPrefix(mediaSection, "a=mid:")[0]; + if (mid) { + return mid.substr(6); + } +}; + +SDPUtils.parseFingerprint = function(line) { + var parts = line.substr(14).split(" "); + return { + algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. + value: parts[1], + }; +}; + +// Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. +SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix( + mediaSection + sessionpart, + "a=fingerprint:" + ); + // Note: a=setup line is ignored since we use the 'auto' role. + // Note2: 'algorithm' is not case sensitive except in Edge. + return { + role: "auto", + fingerprints: lines.map(SDPUtils.parseFingerprint), + }; +}; + +// Serializes DTLS parameters to SDP. +SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = "a=setup:" + setupType + "\r\n"; + params.fingerprints.forEach(function(fp) { + sdp += "a=fingerprint:" + fp.algorithm + " " + fp.value + "\r\n"; + }); + return sdp; +}; + +// Parses a=crypto lines into +// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members +SDPUtils.parseCryptoLine = function(line) { + var parts = line.substr(9).split(" "); + return { + tag: parseInt(parts[0], 10), + cryptoSuite: parts[1], + keyParams: parts[2], + sessionParams: parts.slice(3), + }; +}; + +SDPUtils.writeCryptoLine = function(parameters) { + return ( + "a=crypto:" + + parameters.tag + + " " + + parameters.cryptoSuite + + " " + + (typeof parameters.keyParams === "object" + ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) + : parameters.keyParams) + + (parameters.sessionParams ? " " + parameters.sessionParams.join(" ") : "") + + "\r\n" + ); +}; + +// Parses the crypto key parameters into +// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* +SDPUtils.parseCryptoKeyParams = function(keyParams) { + if (keyParams.indexOf("inline:") !== 0) { + return null; + } + var parts = keyParams.substr(7).split("|"); + return { + keyMethod: "inline", + keySalt: parts[0], + lifeTime: parts[1], + mkiValue: parts[2] ? parts[2].split(":")[0] : undefined, + mkiLength: parts[2] ? parts[2].split(":")[1] : undefined, + }; +}; + +SDPUtils.writeCryptoKeyParams = function(keyParams) { + return ( + keyParams.keyMethod + + ":" + + keyParams.keySalt + + (keyParams.lifeTime ? "|" + keyParams.lifeTime : "") + + (keyParams.mkiValue && keyParams.mkiLength + ? "|" + keyParams.mkiValue + ":" + keyParams.mkiLength + : "") + ); +}; + +// Extracts all SDES paramters. +SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, "a=crypto:"); + return lines.map(SDPUtils.parseCryptoLine); +}; + +// Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. +SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var ufrag = SDPUtils.matchPrefix( + mediaSection + sessionpart, + "a=ice-ufrag:" + )[0]; + var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, "a=ice-pwd:")[0]; + if (!(ufrag && pwd)) { + return null; + } + return { + usernameFragment: ufrag.substr(12), + password: pwd.substr(10), + }; +}; + +// Serializes ICE parameters to SDP. +SDPUtils.writeIceParameters = function(params) { + return ( + "a=ice-ufrag:" + + params.usernameFragment + + "\r\n" + + "a=ice-pwd:" + + params.password + + "\r\n" + ); +}; + +// Parses the SDP media section and returns RTCRtpParameters. +SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [], + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(" "); + for (var i = 3; i < mline.length; i++) { + // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, + "a=rtpmap:" + pt + " " + )[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix(mediaSection, "a=fmtp:" + pt + " "); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, + "a=rtcp-fb:" + pt + " " + ).map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case "RED": + case "ULPFEC": + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: + // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, "a=extmap:").forEach(function(line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; +}; + +// Generates parts of the SDP media section describing the capabilities / +// parameters. +SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ""; + + // Build the mline. + sdp += "m=" + kind + " "; + sdp += caps.codecs.length > 0 ? "9" : "0"; // reject if no codecs. + sdp += " UDP/TLS/RTP/SAVPF "; + sdp += + caps.codecs + .map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }) + .join(" ") + "\r\n"; + + sdp += "c=IN IP4 0.0.0.0\r\n"; + sdp += "a=rtcp:9 IN IP4 0.0.0.0\r\n"; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + var maxptime = 0; + caps.codecs.forEach(function(codec) { + if (codec.maxptime > maxptime) { + maxptime = codec.maxptime; + } + }); + if (maxptime > 0) { + sdp += "a=maxptime:" + maxptime + "\r\n"; + } + sdp += "a=rtcp-mux\r\n"; + + if (caps.headerExtensions) { + caps.headerExtensions.forEach(function(extension) { + sdp += SDPUtils.writeExtmap(extension); + }); + } + // FIXME: write fecMechanisms. + return sdp; +}; + +// Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. +SDPUtils.parseRtpEncodingParameters = function(mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf("RED") !== -1; + var hasUlpfec = description.fecMechanisms.indexOf("ULPFEC") !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, "a=ssrc:") + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === "cname"; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + + var flows = SDPUtils.matchPrefix(mediaSection, "a=ssrc-group:FID").map( + function(line) { + var parts = line.substr(17).split(" "); + return parts.map(function(part) { + return parseInt(part, 10); + }); + } + ); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function(codec) { + if (codec.name.toUpperCase() === "RTX" && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10), + }; + if (primarySsrc && secondarySsrc) { + encParam.rtx = { ssrc: secondarySsrc }; + } + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: primarySsrc, + mechanism: hasUlpfec ? "red+ulpfec" : "red", + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc, + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, "b="); + if (bandwidth.length) { + if (bandwidth[0].indexOf("b=TIAS:") === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf("b=AS:") === 0) { + // use formula from JSEP to convert b=AS to TIAS value. + bandwidth = + parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 - 50 * 40 * 8; + } else { + bandwidth = undefined; + } + encodingParameters.forEach(function(params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; +}; + +// parses http://draft.ortc.org/#rtcrtcpparameters* +SDPUtils.parseRtcpParameters = function(mediaSection) { + var rtcpParameters = {}; + + // Gets the first SSRC. Note tha with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, "a=ssrc:") + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === "cname"; + })[0]; + if (remoteSsrc) { + rtcpParameters.cname = remoteSsrc.value; + rtcpParameters.ssrc = remoteSsrc.ssrc; + } + + // Edge uses the compound attribute instead of reducedSize + // compound is !reducedSize + var rsize = SDPUtils.matchPrefix(mediaSection, "a=rtcp-rsize"); + rtcpParameters.reducedSize = rsize.length > 0; + rtcpParameters.compound = rsize.length === 0; + + // parses the rtcp-mux attrŅ–bute. + // Note that Edge does not support unmuxed RTCP. + var mux = SDPUtils.matchPrefix(mediaSection, "a=rtcp-mux"); + rtcpParameters.mux = mux.length > 0; + + return rtcpParameters; +}; + +// parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. +SDPUtils.parseMsid = function(mediaSection) { + var parts; + var spec = SDPUtils.matchPrefix(mediaSection, "a=msid:"); + if (spec.length === 1) { + parts = spec[0].substr(7).split(" "); + return { stream: parts[0], track: parts[1] }; + } + var planB = SDPUtils.matchPrefix(mediaSection, "a=ssrc:") + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(msidParts) { + return msidParts.attribute === "msid"; + }); + if (planB.length > 0) { + parts = planB[0].value.split(" "); + return { stream: parts[0], track: parts[1] }; + } +}; + +// SCTP +// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back +// to draft-ietf-mmusic-sctp-sdp-05 +SDPUtils.parseSctpDescription = function(mediaSection) { + var mline = SDPUtils.parseMLine(mediaSection); + var maxSizeLine = SDPUtils.matchPrefix(mediaSection, "a=max-message-size:"); + var maxMessageSize; + if (maxSizeLine.length > 0) { + maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10); + } + if (isNaN(maxMessageSize)) { + maxMessageSize = 65536; + } + var sctpPort = SDPUtils.matchPrefix(mediaSection, "a=sctp-port:"); + if (sctpPort.length > 0) { + return { + port: parseInt(sctpPort[0].substr(12), 10), + protocol: mline.fmt, + maxMessageSize, + }; + } + var sctpMapLines = SDPUtils.matchPrefix(mediaSection, "a=sctpmap:"); + if (sctpMapLines.length > 0) { + var parts = SDPUtils.matchPrefix(mediaSection, "a=sctpmap:")[0] + .substr(10) + .split(" "); + return { + port: parseInt(parts[0], 10), + protocol: parts[1], + maxMessageSize, + }; + } +}; + +// SCTP +// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers +// support by now receiving in this format, unless we originally parsed +// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line +// protocol of DTLS/SCTP -- without UDP/ or TCP/) +SDPUtils.writeSctpDescription = function(media, sctp) { + var output = []; + if (media.protocol !== "DTLS/SCTP") { + output = [ + "m=" + media.kind + " 9 " + media.protocol + " " + sctp.protocol + "\r\n", + "c=IN IP4 0.0.0.0\r\n", + "a=sctp-port:" + sctp.port + "\r\n", + ]; + } else { + output = [ + "m=" + media.kind + " 9 " + media.protocol + " " + sctp.port + "\r\n", + "c=IN IP4 0.0.0.0\r\n", + "a=sctpmap:" + sctp.port + " " + sctp.protocol + " 65535\r\n", + ]; + } + if (sctp.maxMessageSize !== undefined) { + output.push("a=max-message-size:" + sctp.maxMessageSize + "\r\n"); + } + return output.join(""); +}; + +// Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range +SDPUtils.generateSessionId = function() { + return Math.floor((Math.random() * 4294967296) + 1); +}; + +// Write boilder plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +// sessUser is optional and defaults to 'thisisadapterortc' +SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) { + var sessionId; + var version = sessVer !== undefined ? sessVer : 2; + if (sessId) { + sessionId = sessId; + } else { + sessionId = SDPUtils.generateSessionId(); + } + var user = sessUser || "thisisadapterortc"; + // FIXME: sess-id should be an NTP timestamp. + return ( + "v=0\r\n" + + "o=" + + user + + " " + + sessionId + + " " + + version + + " IN IP4 127.0.0.1\r\n" + + "s=-\r\n" + + "t=0 0\r\n" + ); +}; + +SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters() + ); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === "offer" ? "actpass" : "active" + ); + + sdp += "a=mid:" + transceiver.mid + "\r\n"; + + if (transceiver.direction) { + sdp += "a=" + transceiver.direction + "\r\n"; + } else if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += "a=sendrecv\r\n"; + } else if (transceiver.rtpSender) { + sdp += "a=sendonly\r\n"; + } else if (transceiver.rtpReceiver) { + sdp += "a=recvonly\r\n"; + } else { + sdp += "a=inactive\r\n"; + } + + if (transceiver.rtpSender) { + // spec. + var msid = + "msid:" + stream.id + " " + transceiver.rtpSender.track.id + "\r\n"; + sdp += "a=" + msid; + + // for Chrome. + sdp += "a=ssrc:" + transceiver.sendEncodingParameters[0].ssrc + " " + msid; + if (transceiver.sendEncodingParameters[0].rtx) { + sdp += + "a=ssrc:" + transceiver.sendEncodingParameters[0].rtx.ssrc + " " + msid; + sdp += + "a=ssrc-group:FID " + + transceiver.sendEncodingParameters[0].ssrc + + " " + + transceiver.sendEncodingParameters[0].rtx.ssrc + + "\r\n"; + } + } + // FIXME: this should be written by writeRtpDescription. + sdp += + "a=ssrc:" + + transceiver.sendEncodingParameters[0].ssrc + + " cname:" + + SDPUtils.localCName + + "\r\n"; + if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { + sdp += + "a=ssrc:" + + transceiver.sendEncodingParameters[0].rtx.ssrc + + " cname:" + + SDPUtils.localCName + + "\r\n"; + } + return sdp; +}; + +// Gets the direction from the mediaSection or the sessionpart. +SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case "a=sendrecv": + case "a=sendonly": + case "a=recvonly": + case "a=inactive": + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return "sendrecv"; +}; + +SDPUtils.getKind = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(" "); + return mline[0].substr(2); +}; + +SDPUtils.isRejected = function(mediaSection) { + return mediaSection.split(" ", 2)[1] === "0"; +}; + +SDPUtils.parseMLine = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var parts = lines[0].substr(2).split(" "); + return { + kind: parts[0], + port: parseInt(parts[1], 10), + protocol: parts[2], + fmt: parts.slice(3).join(" "), + }; +}; + +SDPUtils.parseOLine = function(mediaSection) { + var line = SDPUtils.matchPrefix(mediaSection, "o=")[0]; + var parts = line.substr(2).split(" "); + return { + username: parts[0], + sessionId: parts[1], + sessionVersion: parseInt(parts[2], 10), + netType: parts[3], + addressType: parts[4], + address: parts[5], + }; +}; + +// a very naive interpretation of a valid SDP. +SDPUtils.isValidSDP = function(blob) { + if (typeof blob !== "string" || blob.length === 0) { + return false; + } + var lines = SDPUtils.splitLines(blob); + for (var i = 0; i < lines.length; i++) { + if (lines[i].length < 2 || lines[i].charAt(1) !== "=") { + return false; + } + // TODO: check the modifier a bit more. + } + return true; +}; + +// Expose public methods. +if (typeof module === "object") { + module.exports = SDPUtils; +} -- cgit v1.2.3