@irbisadm/simple-sdp 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,7 +3,35 @@
3
3
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The project
4
4
  follows [Semantic Versioning](https://semver.org/).
5
5
 
6
- ## [1.0.1] — unreleased
6
+ ## [1.2.0] — 2026-11-01
7
+
8
+ ### Added
9
+ - `@irbisadm/simple-sdp/wasm`: the same API and types as the main entry point, run by the
10
+ WebAssembly module (ESM with top-level await; works in Node.js and with bundlers that
11
+ handle `new URL(…, import.meta.url)`, checked with Vite 6). The module itself is
12
+ exported as `@irbisadm/simple-sdp/wasm/simple-sdp.wasm`.
13
+ - Kotlin library on JitPack: `com.github.irbisadm:simple-sdp:v1.2.0` (`jitpack.yml`).
14
+ - GitHub Releases carry `simple-sdp.wasm` for other runtimes (`.github/workflows/release.yml`).
15
+
16
+ ## [1.1.0] — 2026-11-01
17
+
18
+ ### Added
19
+ - AssemblyScript port (`assembly/`): the parser, writer and validator compiled to a
20
+ WebAssembly module with no imports (`yarn asbuild`). Results are JSON with the shape of
21
+ the TypeScript model; a C-style interface on UTF-8 buffers (`sdp_parse`, `sdp_write`,
22
+ `sdp_validate`, `sdp_validate_model`, …) serves non-JS hosts. `yarn test:wasm` runs the
23
+ test suites against the module, plus a differential test against the TypeScript code.
24
+ - Swift package `SimpleSdp` for iOS 18+ / macOS 15+ (WasmKit), `Package.swift` in the root.
25
+ - Kotlin library `dev.irbisadm:simple-sdp` for Android and the JVM (Chicory; the module is
26
+ compiled to bytecode at build time), `platforms/kotlin`.
27
+
28
+ ### Fixed
29
+ - Attributes named like `Object.prototype` members (`a=constructor`, `a=toString`,
30
+ `a=__proto__`, …) failed to parse; an ssrc attribute with such a name was dropped, a
31
+ `__proto__` key of fmtp/ssrc/candidate records changed the object prototype, and
32
+ `a=fingerprint:constructor …` got a validator message with "function Object()".
33
+
34
+ ## [1.0.1] — 2026-11-01
7
35
 
8
36
  ### Fixed
9
37
  - Validator accepted malformed `a=fingerprint` values such as `sha-256 AA:BB`.
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- class e extends Error{constructor(e,t,r){super(t>0?`SDP line ${t} "${r}": ${e}`:`SDP: ${e}`),this.name="SdpParseError",this.reason=e,this.lineNumber=t,this.line=r}}const t=/^\d+$/,r=/^\d+$/,i=(e,t="value")=>{if(!r.test(e))throw new Error(`${t} "${e}" is not a non-negative integer`);return parseInt(e,10)},s={d:86400,h:3600,m:60,s:1},n=(e,t=!1)=>{if(t&&e.startsWith("-"))return-n(e.slice(1));const r=e.slice(-1);return r in s?s[r]*i(e.slice(0,-1),"time"):i(e,"time")},o=e=>{const t=e.indexOf(":"),r=-1===t?e:e.slice(0,t);if(0===r.length)throw new Error("empty key method");return-1===t?{method:r}:{method:r,encryptionKey:e.slice(t+1)}},a=e=>/(^|\/)RTP\//.test(e),d=e=>{const t=e.indexOf(":");if(t<=0)throw new Error('expected "<bwtype>:<bandwidth>"');return{bwtype:e.slice(0,t),bandwidth:i(e.slice(t+1),"bandwidth")}},c=e=>{const t=e.split(" ");if(3!==t.length)throw new Error('expected "<nettype> <addrtype> <connection-address>"');const[r,i,s]=t;return{netType:r,addrType:i,connectionAddress:s}},p=(e,t)=>{if(void 0===e||0===e.length)throw new Error(`expected a value: ${t}`);return e},l=e=>{if(void 0!==e)throw new Error("flag attribute must not have a value");return!0},m=e=>{const t=e.indexOf(" ");return-1===t?[e,void 0]:[e.slice(0,t),e.slice(t+1)]},u=(e,t)=>{var r;const s=i(t,"payload type");return null===(r=e.codecs)||void 0===r?void 0:r.find((e=>e.payloadType===s))},f=(e,t,r,i)=>{if(void 0!==e[t])throw new Error(`duplicate a=${i}`);e[t]=r},v=(e,t,r)=>{void 0===e[t]&&(e[t]=[]),e[t].push(r)},h=["sendrecv","sendonly","recvonly","inactive"],g=e=>{const t={};for(const r of e.split(";")){const e=r.trim();if(0===e.length)continue;const i=e.indexOf("=");-1===i?t[e]=null:t[e.slice(0,i)]=e.slice(i+1)}return t},$='"<foundation> <component> <transport> <priority> <address> <port> typ <type> [raddr <addr>] [rport <port>] *(<ext-name> <ext-value>)"',w=e=>{const t=i(e,"ssrc-id");if(t>4294967295)throw new Error(`ssrc-id ${t} does not fit into 32 bits`);return t},y=/^[A-Za-z0-9_-]+$/,b=["session","media"],x=["media"],k=["session"],E=["active","passive","actpass","holdconn"],T=e=>({scopes:b,parse:t=>f(t,"direction",e,e)}),j={sendrecv:T("sendrecv"),sendonly:T("sendonly"),recvonly:T("recvonly"),inactive:T("inactive"),"ice-ufrag":{scopes:b,parse:(e,t)=>f(e,"iceUfrag",p(t,"a=ice-ufrag:<ufrag>"),"ice-ufrag")},"ice-pwd":{scopes:b,parse:(e,t)=>f(e,"icePwd",p(t,"a=ice-pwd:<password>"),"ice-pwd")},"ice-options":{scopes:b,parse:(e,t)=>f(e,"iceOptions",p(t,"a=ice-options:<option> ...").split(" "),"ice-options")},"ice-lite":{scopes:k,parse:(e,t)=>f(e,"iceLite",l(t),"ice-lite")},candidate:{scopes:x,parse:(e,t)=>v(e,"candidates",(e=>{var t;const r=e.split(" ");if(r.length<8||"typ"!==r[6]||r.length%2!=0)throw new Error(`expected ${$}`);const[s,n,o,a,d,c,,p]=r,l={foundation:s,component:i(n,"component id"),transport:o,priority:i(a,"priority"),address:d,port:i(c,"port"),type:p};for(let e=8;e<r.length;e+=2){const[s,n]=[r[e],r[e+1]];if(0===s.length||0===n.length)throw new Error(`expected ${$}`);switch(s){case"raddr":l.relatedAddress=n;break;case"rport":l.relatedPort=i(n,"rport");break;case"tcptype":l.tcpType=n;break;default:(null!==(t=l.extensions)&&void 0!==t?t:l.extensions={})[s]=n}}return l})(p(t,"a=candidate:<candidate>")))},"end-of-candidates":{scopes:b,parse:(e,t)=>f(e,"endOfCandidates",l(t),"end-of-candidates")},fingerprint:{scopes:b,parse:(e,t)=>{const[r,i]=m(p(t,"a=fingerprint:<hash-function> <fingerprint>"));if(!i||i.includes(" "))throw new Error('expected "a=fingerprint:<hash-function> <fingerprint>"');v(e,"fingerprints",{hashFunction:r,fingerprint:i})}},setup:{scopes:b,parse:(e,t)=>{if(!E.includes(t))throw new Error(`a=setup must be one of ${E.join(", ")}`);f(e,"setup",t,"setup")}},extmap:{scopes:b,parse:(e,t)=>v(e,"extmap",(e=>{const[t,r]=m(e);if(!r)throw new Error('expected "<id>[/<direction>] <uri> [<attributes>]"');const[s,n,...o]=t.split("/");if(o.length||void 0!==n&&!h.includes(n))throw new Error(`invalid extmap id "${t}"`);const[a,d]=m(r),c={id:i(s,"extmap id"),uri:a};return void 0!==n&&(c.direction=n),void 0!==d&&(c.attributes=d),c})(p(t,"a=extmap:<id> <uri>")))},group:{scopes:k,parse:(e,t)=>v(e,"group",(e=>{const[t,...r]=e.split(" ");if(0===t.length||r.some((e=>0===e.length)))throw new Error('expected "<semantics> <mid> ..."');return{semantics:t,mids:r}})(p(t,"a=group:<semantics> <mid> ...")))},mid:{scopes:x,parse:(e,t)=>f(e,"mid",p(t,"a=mid:<identification-tag>"),"mid")},msid:{scopes:x,parse:(e,t)=>v(e,"msid",(e=>{const[t,r,...i]=e.split(" ");if(0===t.length||""===r||i.length)throw new Error('expected "<stream id> [<track id>]"');return void 0===r?{streamId:t}:{streamId:t,trackId:r}})(p(t,"a=msid:<stream id> [<track id>]")))},ssrc:{scopes:x,parse:(e,t)=>((e,t)=>{var r;const[i,s]=m(t);if(!s)throw new Error('expected "<ssrc-id> <attribute>[:<value>]"');const n=w(i),o=s.indexOf(":"),a=-1===o?s:s.slice(0,o),d=-1===o?null:s.slice(o+1),c=null!==(r=e.ssrcs)&&void 0!==r?r:e.ssrcs=[];let p=c.find((e=>e.id===n));return p||(p={id:n,attributes:{}},c.push(p)),!(a in p.attributes)&&(p.attributes[a]=d,!0)})(e,p(t,"a=ssrc:<ssrc-id> <attribute>[:<value>]"))},"ssrc-group":{scopes:x,parse:(e,t)=>v(e,"ssrcGroups",(e=>{const[t,...r]=e.split(" ");if(0===t.length)throw new Error('expected "<semantics> <ssrc-id> ..."');return{semantics:t,ssrcs:r.map(w)}})(p(t,"a=ssrc-group:<semantics> <ssrc-id> ...")))},rid:{scopes:x,parse:(e,t)=>v(e,"rids",(e=>{const[t,r]=m(e),[s,n]=r?m(r):[void 0,void 0];if(!y.test(t)||"send"!==s&&"recv"!==s||""===n)throw new Error('expected "<rid-id> <send|recv> [<params>]"');const o={id:t,direction:s};if(void 0!==n){const e=g(n);if("pt"in e){if(!n.startsWith("pt=")||null===e.pt)throw new Error('"pt=" must be the first rid parameter');o.payloadTypes=e.pt.split(",").map((e=>i(e,"payload type"))),delete e.pt}Object.keys(e).length&&(o.restrictions=e)}return o})(p(t,"a=rid:<rid-id> <send|recv>")))},simulcast:{scopes:x,parse:(e,t)=>f(e,"simulcast",(e=>{const t=e.split(" ");if(2!==t.length&&4!==t.length)throw new Error('expected "send|recv <streams> [send|recv <streams>]"');const r={};for(let e=0;e<t.length;e+=2){const i=t[e];if("send"!==i&&"recv"!==i||r[i])throw new Error('expected "send|recv <streams> [send|recv <streams>]"');r[i]=t[e+1].split(";").map((e=>e.split(",").map((e=>{const t=e.startsWith("~"),r=t?e.slice(1):e;if(!y.test(r))throw new Error(`invalid simulcast rid "${e}"`);return t?{rid:r,paused:t}:{rid:r}}))))}return r})(p(t,"a=simulcast:<send|recv> <streams>")),"simulcast")},"extmap-allow-mixed":{scopes:b,parse:(e,t)=>f(e,"extmapAllowMixed",l(t),"extmap-allow-mixed")},"rtcp-mux":{scopes:x,parse:(e,t)=>f(e,"rtcpMux",l(t),"rtcp-mux")},"rtcp-rsize":{scopes:x,parse:(e,t)=>f(e,"rtcpRsize",l(t),"rtcp-rsize")},rtcp:{scopes:x,parse:(e,t)=>{const r=p(t,"a=rtcp:<port> [<nettype> <addrtype> <address>]").split(" ");if(1!==r.length&&4!==r.length)throw new Error('expected "a=rtcp:<port> [<nettype> <addrtype> <address>]"');const[s,n,o,a]=r;f(e,"rtcp",1===r.length?{port:i(s,"port")}:{port:i(s,"port"),netType:n,addrType:o,address:a},"rtcp")}},"sctp-port":{scopes:x,parse:(e,t)=>f(e,"sctpPort",i(p(t,"a=sctp-port:<port>"),"sctp-port"),"sctp-port")},"max-message-size":{scopes:x,parse:(e,t)=>f(e,"maxMessageSize",i(p(t,"a=max-message-size:<size>"),"max-message-size"),"max-message-size")},rtpmap:{scopes:x,parse:(e,t)=>{const[r,s]=m(p(t,"a=rtpmap:<payload type> <encoding>")),n=e.codecs?u(e,r):void 0;if(!n||void 0!==n.encodingName)return!1;((e,t)=>{const[r,s,n,...o]=t.split("/");if(!r||void 0===s||o.length)throw new Error('expected "<payload type> <encoding name>/<clock rate>[/<encoding parameters>]"');e.encodingName=r,e.clockRate=i(s,"clock rate"),void 0!==n&&(e.encodingParameters=i(n,"encoding parameters"))})(n,null!=s?s:"")}},fmtp:{scopes:x,parse:(e,t)=>{const[r,i]=m(p(t,"a=fmtp:<format> <parameters>")),s=e.codecs?u(e,r):void 0;if(!s||void 0!==s.parameters||void 0===i)return!1;s.parameters=g(i)}},"rtcp-fb":{scopes:x,parse:(e,t)=>{const[r,i]=m(p(t,"a=rtcp-fb:<payload type> <feedback>"));if(!i||!e.codecs)return!1;if("*"===r)return void v(e,"rtcpFeedbackWildcard",i);const s=u(e,r);if(!s)return!1;v(s,"rtcpFeedback",i)}}},F=(e,t,r)=>{const i=r.indexOf(":"),s=-1===i?r:r.slice(0,i),n=-1===i?void 0:r.slice(i+1);if(0===s.length)throw new Error("empty attribute name");const o=j[s];o&&o.scopes.includes(t)&&!1!==o.parse(e,n)||v(e,"attributes",void 0===n?{name:s}:{name:s,value:n})},P={v:0,o:1,s:2,i:3,u:4,e:5,p:6,c:7,b:8,t:9,r:10,z:11,k:12,a:13},z=new Set(["e","p","b","a"]),I={m:0,i:1,c:2,b:3,k:4,a:5},C=new Set(["b","a"]),R={session:"v o s i u e p c b t r z k a",media:"m i c b k a"},O=r=>{const s=r.split(/\r?\n/);""===s[s.length-1]&&s.pop();const p={media:[]};let l=null,m=null,u=-1;s.forEach(((r,s)=>{var f,v,h,g,$,w,y;const b=t=>{throw new e(t,s+1,r)};0===r.length&&b("empty line"),(r.length<2||"="!==r[1])&&b('expected "<type>=<value>"');const x=r[0],k=r.slice(2);if(0===s&&"v"!==x&&b('a session description must start with "v="'),"m"===x)u=0;else{const e=l?"media":"session",t=l?C:z,r=(l?I:P)[x];void 0===r&&b(x in P?`"${x}=" is not allowed in a media description`:`unknown line type "${x}"`),l||"t"!==x?l||"r"!==x&&"z"!==x?r<u?b(`line is out of order (${e} order: ${R[e]})`):r!==u||t.has(x)||b(l&&"c"===x?'multiple "c=" lines in one media description are not supported':`duplicate "${x}=" line`):u!==P.t&&u!==P.r&&b(`"${x}=" must follow "t=" or "r="`):u>P.z&&b(`"t=" must precede "k=" and "a=" (order: ${R.session})`),u=r}try{if(l){switch(x){case"i":l.information=k;break;case"c":l.connectionInformation=c(k);break;case"b":(null!==(f=l.bandwidths)&&void 0!==f?f:l.bandwidths=[]).push(d(k));break;case"k":l.encryptionKey=o(k);break;case"a":F(l,"media",k)}if("m"!==x)return}switch(x){case"v":"0"!==k&&b(`unsupported protocol version "${k}"`),p.protocolVersion=0;break;case"o":p.origin=(e=>{const r=e.split(" ");if(6!==r.length)throw new Error('expected "<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>"');const[i,s,n,o,a,d]=r;if(!t.test(s))throw new Error(`sess-id "${s}" is not numeric`);if(!t.test(n))throw new Error(`sess-version "${n}" is not numeric`);return{username:i,sessId:s,sessVersion:n,netType:o,addrType:a,unicastAddress:d}})(k);break;case"s":p.sessionName=k;break;case"i":p.sessionInformation=k;break;case"u":p.uri=k;break;case"e":(null!==(v=p.emailAddresses)&&void 0!==v?v:p.emailAddresses=[]).push(k);break;case"p":(null!==(h=p.phoneNumbers)&&void 0!==h?h:p.phoneNumbers=[]).push(k);break;case"c":p.connectionInformation=c(k);break;case"b":(null!==(g=p.bandwidths)&&void 0!==g?g:p.bandwidths=[]).push(d(k));break;case"t":m=(e=>{const t=e.split(" ");if(2!==t.length)throw new Error('expected "<start-time> <stop-time>"');return{startTime:i(t[0],"start time"),stopTime:i(t[1],"stop time")}})(k),(null!==($=p.timing)&&void 0!==$?$:p.timing=[]).push(m);break;case"r":(null!==(w=(y=m).repeat)&&void 0!==w?w:y.repeat=[]).push((e=>{const t=e.split(" ");if(t.length<3)throw new Error('expected "<repeat interval> <active duration> <offset>..."');const[r,i,...s]=t.map((e=>n(e)));return{repeatInterval:r,activeDuration:i,offsets:s}})(k));break;case"z":m.zoneAdjustments=(e=>{const t=e.split(" ");if(t.length<2||t.length%2!=0)throw new Error('expected pairs of "<adjustment time> <offset>"');const r=[];for(let e=0;e<t.length;e+=2)r.push({adjustmentTime:i(t[e],"adjustment time"),offset:n(t[e+1],!0)});return r})(k);break;case"k":p.encryptionKey=o(k);break;case"a":F(p,"session",k);break;case"m":l=(e=>{const t=e.split(" ");if(t.length<4)throw new Error('expected "<media> <port> <proto> <fmt> ..."');const[r,s,n,...o]=t,[d,c,...p]=s.split("/");if(p.length)throw new Error(`invalid port "${s}"`);const l={type:r,port:i(d,"port"),proto:n};return void 0!==c&&(l.numberOfPorts=i(c,"number of ports")),a(n)?l.codecs=o.map((e=>{const t=i(e,"payload type");if(t>127)throw new Error(`payload type ${t} is out of range 0-127`);return{payloadType:t}})):l.formats=o,l})(k),p.media.push(l)}}catch(t){if(t instanceof e)throw t;b(t.message)}}));for(const[t,r]of[["protocolVersion","v"],["origin","o"],["sessionName","s"],["timing","t"]])if(void 0===p[t])throw new e(`missing mandatory "${r}=" line`,0,"");return p},A={parse:O,isValid:(e,t=!1)=>{try{return O(e),!0}catch(e){return t&&console.error(e),!1}}},N=e=>`c=${e.netType} ${e.addrType} ${e.connectionAddress}`,S=e=>`b=${e.bwtype}:${e.bandwidth}`,U=e=>`k=${e.method}${void 0!==e.encryptionKey?`:${e.encryptionKey}`:""}`,M=e=>Object.entries(e).map((([e,t])=>null===t?e:`${e}=${t}`)).join(";"),q=e=>{var t,r;return[`t=${e.startTime} ${e.stopTime}`,...(null!==(t=e.repeat)&&void 0!==t?t:[]).map((e=>`r=${[e.repeatInterval,e.activeDuration,...e.offsets].join(" ")}`)),...(null===(r=e.zoneAdjustments)||void 0===r?void 0:r.length)?[`z=${e.zoneAdjustments.map((e=>`${e.adjustmentTime} ${e.offset}`)).join(" ")}`]:[]]},K=e=>{var t;return[...void 0!==e.encodingName?[`a=rtpmap:${e.payloadType} ${e.encodingName}/${e.clockRate}${void 0!==e.encodingParameters?`/${e.encodingParameters}`:""}`]:[],...(null!==(t=e.rtcpFeedback)&&void 0!==t?t:[]).map((t=>`a=rtcp-fb:${e.payloadType} ${t}`)),...void 0!==e.parameters?[`a=fmtp:${e.payloadType} ${M(e.parameters)}`]:[]]},V=e=>{const t=[...e.payloadTypes?[`pt=${e.payloadTypes.join(",")}`]:[],...e.restrictions&&Object.keys(e.restrictions).length?[M(e.restrictions)]:[]].join(";");return`a=rid:${e.id} ${e.direction}${t?` ${t}`:""}`},D=(e,t)=>e?[`a=${t}`]:[],L=e=>{var t;return[...void 0!==e.iceUfrag?[`a=ice-ufrag:${e.iceUfrag}`]:[],...void 0!==e.icePwd?[`a=ice-pwd:${e.icePwd}`]:[],...e.iceOptions?[`a=ice-options:${e.iceOptions.join(" ")}`]:[],...(null!==(t=e.fingerprints)&&void 0!==t?t:[]).map((e=>`a=fingerprint:${e.hashFunction} ${e.fingerprint}`)),...void 0!==e.setup?[`a=setup:${e.setup}`]:[]]},W=e=>{var t;return[...(null!==(t=e.extmap)&&void 0!==t?t:[]).map((e=>`a=extmap:${e.id}${e.direction?`/${e.direction}`:""} ${e.uri}${void 0!==e.attributes?` ${e.attributes}`:""}`)),...D(e.extmapAllowMixed,"extmap-allow-mixed")]},G=e=>{var t;return(null!==(t=e.attributes)&&void 0!==t?t:[]).map((e=>void 0===e.value?`a=${e.name}`:`a=${e.name}:${e.value}`))},B=e=>{var t,r,i,s,n,o,a,d;const c=e.codecs?e.codecs.map((e=>String(e.payloadType))):e.formats;if(!c||0===c.length)throw new Error(`media section "${e.type}" has neither codecs nor formats`);return[`m=${e.type} ${e.port}${void 0!==e.numberOfPorts?`/${e.numberOfPorts}`:""} ${e.proto} ${c.join(" ")}`,...void 0!==e.information?[`i=${e.information}`]:[],...e.connectionInformation?[N(e.connectionInformation)]:[],...(null!==(t=e.bandwidths)&&void 0!==t?t:[]).map(S),...e.encryptionKey?[U(e.encryptionKey)]:[],...e.rtcp?[`a=rtcp:${e.rtcp.port}${void 0!==e.rtcp.netType?` ${e.rtcp.netType} ${e.rtcp.addrType} ${e.rtcp.address}`:""}`]:[],...L(e),...void 0!==e.mid?[`a=mid:${e.mid}`]:[],...W(e),...e.direction?[`a=${e.direction}`]:[],...(null!==(r=e.msid)&&void 0!==r?r:[]).map((e=>`a=msid:${e.streamId}${void 0!==e.trackId?` ${e.trackId}`:""}`)),...D(e.rtcpMux,"rtcp-mux"),...D(e.rtcpRsize,"rtcp-rsize"),...(null!==(i=e.codecs)&&void 0!==i?i:[]).flatMap(K),...(null!==(s=e.rtcpFeedbackWildcard)&&void 0!==s?s:[]).map((e=>`a=rtcp-fb:* ${e}`)),...void 0!==e.sctpPort?[`a=sctp-port:${e.sctpPort}`]:[],...void 0!==e.maxMessageSize?[`a=max-message-size:${e.maxMessageSize}`]:[],...(null!==(n=e.rids)&&void 0!==n?n:[]).map(V),...e.simulcast?[(p=e.simulcast,`a=simulcast:${["send","recv"].filter((e=>p[e])).map((e=>`${e} ${p[e].map((e=>e.map((e=>`${e.paused?"~":""}${e.rid}`)).join(","))).join(";")}`)).join(" ")}`)]:[],...(null!==(o=e.ssrcGroups)&&void 0!==o?o:[]).map((e=>`a=ssrc-group:${[e.semantics,...e.ssrcs].join(" ")}`)),...(null!==(a=e.ssrcs)&&void 0!==a?a:[]).flatMap((e=>Object.entries(e.attributes).map((([t,r])=>`a=ssrc:${e.id} ${t}${null===r?"":`:${r}`}`)))),...(null!==(d=e.candidates)&&void 0!==d?d:[]).map((e=>`a=candidate:${(e=>{var t;return[e.foundation,e.component,e.transport,e.priority,e.address,e.port,"typ",e.type,...void 0!==e.relatedAddress?["raddr",e.relatedAddress]:[],...void 0!==e.relatedPort?["rport",e.relatedPort]:[],...void 0!==e.tcpType?["tcptype",e.tcpType]:[],...Object.entries(null!==(t=e.extensions)&&void 0!==t?t:{}).flat()].join(" ")})(e)}`)),...D(e.endOfCandidates,"end-of-candidates"),...G(e)];var p},Z={write:e=>{var t,r,i,s,n,o,a,d;const{origin:c}=e;if(!c)throw new Error("origin (o=) is required");if(!(null===(t=e.timing)||void 0===t?void 0:t.length))throw new Error("at least one timing (t=) entry is required");return[`v=${null!==(r=e.protocolVersion)&&void 0!==r?r:0}`,`o=${c.username} ${c.sessId} ${c.sessVersion} ${c.netType} ${c.addrType} ${c.unicastAddress}`,`s=${null!==(i=e.sessionName)&&void 0!==i?i:"-"}`,...void 0!==e.sessionInformation?[`i=${e.sessionInformation}`]:[],...void 0!==e.uri?[`u=${e.uri}`]:[],...(null!==(s=e.emailAddresses)&&void 0!==s?s:[]).map((e=>`e=${e}`)),...(null!==(n=e.phoneNumbers)&&void 0!==n?n:[]).map((e=>`p=${e}`)),...e.connectionInformation?[N(e.connectionInformation)]:[],...(null!==(o=e.bandwidths)&&void 0!==o?o:[]).map(S),...e.timing.flatMap(q),...e.encryptionKey?[U(e.encryptionKey)]:[],...(null!==(a=e.group)&&void 0!==a?a:[]).map((e=>`a=group:${[e.semantics,...e.mids].join(" ")}`)),...D(e.iceLite,"ice-lite"),...L(e),...W(e),...e.direction?[`a=${e.direction}`]:[],...D(e.endOfCandidates,"end-of-candidates"),...G(e),...(null!==(d=e.media)&&void 0!==d?d:[]).flatMap(B)].join("\r\n")+"\r\n"}},_={md2:16,md5:16,"sha-1":20,"sha-224":28,"sha-256":32,"sha-384":48,"sha-512":64},H=/^[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2})*$/,J={validate:(t,r={})=>{var i,s,n,o;let d;if("string"==typeof t)try{d=A.parse(t)}catch(t){if(t instanceof e)return[{severity:"error",code:"parse-error",message:t.message,lineNumber:t.lineNumber}];throw t}else d=t;const c=[],p=(e,t,r)=>c.push(void 0===r?{severity:"error",code:e,message:t}:{severity:"error",code:e,message:t,mediaIndex:r}),l=(e,t,r)=>c.push(void 0===r?{severity:"warning",code:e,message:t}:{severity:"warning",code:e,message:t,mediaIndex:r}),m=null!==(i=d.media)&&void 0!==i?i:[];0!==d.protocolVersion&&p("invalid-version",`v= must be 0, got ${d.protocolVersion}`),d.origin||p("missing-origin","o= is required"),void 0===d.sessionName?p("missing-session-name","s= is required"):""===d.sessionName&&l("empty-session-name",'s= must not be empty; use "s= " or "s=-" (RFC 8866 §5.3)'),(null===(s=d.timing)||void 0===s?void 0:s.length)||p("missing-timing","at least one t= line is required"),d.connectionInformation||m.forEach(((e,t)=>{e.connectionInformation||p("missing-connection","c= is required at session level or in every media description (RFC 8866 §5.7)",t)}));const u=new Set;m.forEach(((e,t)=>{void 0!==e.mid&&(u.has(e.mid)&&p("duplicate-mid",`a=mid:${e.mid} is used by more than one media description`,t),u.add(e.mid))}));for(const e of null!==(n=d.group)&&void 0!==n?n:[])for(const t of e.mids)u.has(t)||p("group-unknown-mid",`a=group:${e.semantics} references unknown mid "${t}"`);(null===(o=d.group)||void 0===o?void 0:o.some((e=>"BUNDLE"===e.semantics)))&&m.forEach(((e,t)=>{void 0===e.mid&&p("bundle-missing-mid","every media description needs a=mid when BUNDLE is used (RFC 9143)",t)}));const f=void 0!==d.iceUfrag||void 0!==d.icePwd||d.iceLite||m.some((e=>{var t;return void 0!==e.iceUfrag||void 0!==e.icePwd||(null===(t=e.candidates)||void 0===t?void 0:t.length)})),v=(e,t,r)=>{void 0!==e&&(e.length<4||e.length>256)&&p("ice-ufrag-length",`ice-ufrag must be 4-256 characters, got ${e.length} (RFC 8839 §5.4)`,r),void 0!==t&&(t.length<22||t.length>256)&&p("ice-pwd-length",`ice-pwd must be 22-256 characters, got ${t.length} (RFC 8839 §5.4)`,r)};v(d.iceUfrag,d.icePwd);const h=(e,t)=>{var r;for(const{hashFunction:i,fingerprint:s}of null!==(r=e.fingerprints)&&void 0!==r?r:[]){const e=_[i.toLowerCase()];H.test(s)?void 0!==e&&s.split(":").length!==e?p("invalid-fingerprint",`a=fingerprint:${i} has ${s.split(":").length} bytes, expected ${e} (RFC 8122 §5)`,t):s!==s.toUpperCase()&&l("fingerprint-not-uppercase",`a=fingerprint:${i} must use upper-case hex (RFC 8122 §5)`,t):p("invalid-fingerprint",`a=fingerprint:${i} must be colon-separated hex pairs (RFC 8122 §5)`,t)}};return h(d),m.forEach(((e,t)=>{var i,s,n,o,c,m,u,g,$,w,y,b,x,k,E,T;const j=0!==e.port;if(v(e.iceUfrag,e.icePwd,t),h(e,t),f&&j&&(void 0===(null!==(i=e.iceUfrag)&&void 0!==i?i:d.iceUfrag)&&p("missing-ice-ufrag","a=ice-ufrag is required (RFC 8839 §5.4)",t),void 0===(null!==(s=e.icePwd)&&void 0!==s?s:d.icePwd)&&p("missing-ice-pwd","a=ice-pwd is required (RFC 8839 §5.4)",t)),(e=>/(^|\/)(D?TLS)(\/|$)/.test(e.proto))(e)&&j){(null===(n=e.fingerprints)||void 0===n?void 0:n.length)||(null===(o=d.fingerprints)||void 0===o?void 0:o.length)||p("missing-fingerprint",`a=fingerprint is required for ${e.proto} (RFC 8842 §5.2, §5.3)`,t);const i=null!==(c=e.setup)&&void 0!==c?c:d.setup;void 0===i?p("missing-setup",`a=setup is required for ${e.proto} (RFC 8842 §5.2, §5.3)`,t):"answer"===r.type&&"actpass"===i&&p("setup-actpass-in-answer","an answer must use a=setup:active or a=setup:passive (RFC 5763 §5)",t)}if(a(e.proto)){for(const r of null!==(m=e.codecs)&&void 0!==m?m:[])r.payloadType>34&&void 0===r.encodingName&&p("missing-rtpmap",`payload type ${r.payloadType} is dynamic and needs a=rtpmap (RFC 8866 §6.6)`,t);for(const r of null!==(u=e.attributes)&&void 0!==u?u:[])"rtpmap"!==r.name&&"fmtp"!==r.name&&"rtcp-fb"!==r.name||l("unknown-payload-type",`a=${r.name}:${r.value} refers to a payload type that is not in the m= line, or repeats`,t)}const F=new Set;for(const r of null!==(g=e.extmap)&&void 0!==g?g:[])(r.id<1||15===r.id||r.id>255)&&p("extmap-invalid-id",`extmap id ${r.id} must be 1-14 or 16-255 (RFC 8285)`,t),F.has(r.id)&&p("extmap-duplicate-id",`extmap id ${r.id} is used more than once`,t),F.add(r.id);for(const r of["send","recv"])for(const i of(null!==(w=null===($=e.simulcast)||void 0===$?void 0:$[r])&&void 0!==w?w:[]).flat())(null===(y=e.rids)||void 0===y?void 0:y.some((e=>e.id===i.rid&&e.direction===r)))||p("simulcast-unknown-rid",`a=simulcast ${r} references "${i.rid}" without a matching a=rid:${i.rid} ${r} (RFC 8853 §5.1)`,t);for(const r of null!==(b=e.rids)&&void 0!==b?b:[])for(const i of null!==(x=r.payloadTypes)&&void 0!==x?x:[])(null===(k=e.codecs)||void 0===k?void 0:k.some((e=>e.payloadType===i)))||p("rid-unknown-payload-type",`a=rid:${r.id} references payload type ${i} that is not in the m= line (RFC 8851)`,t);for(const r of null!==(E=e.ssrcGroups)&&void 0!==E?E:[])for(const i of r.ssrcs)(null===(T=e.ssrcs)||void 0===T?void 0:T.some((e=>e.id===i)))||l("ssrc-group-unknown-ssrc",`a=ssrc-group:${r.semantics} references ssrc ${i} without an a=ssrc line`,t)})),c}},Q="1.0.1";export{e as SdpParseError,A as sdpReader,J as sdpValidator,Z as sdpWriter,Q as version};
1
+ class e extends Error{constructor(e,t,r){super(t>0?`SDP line ${t} "${r}": ${e}`:`SDP: ${e}`),this.name="SdpParseError",this.reason=e,this.lineNumber=t,this.line=r}}const t=/^\d+$/,r=/^\d+$/,i=(e,t="value")=>{if(!r.test(e))throw new Error(`${t} "${e}" is not a non-negative integer`);return parseInt(e,10)},s={d:86400,h:3600,m:60,s:1},n=(e,t=!1)=>{if(t&&e.startsWith("-"))return-n(e.slice(1));const r=e.slice(-1);return r in s?s[r]*i(e.slice(0,-1),"time"):i(e,"time")},o=e=>{const t=e.indexOf(":"),r=-1===t?e:e.slice(0,t);if(0===r.length)throw new Error("empty key method");return-1===t?{method:r}:{method:r,encryptionKey:e.slice(t+1)}},a=e=>/(^|\/)RTP\//.test(e),d=e=>{const t=e.indexOf(":");if(t<=0)throw new Error('expected "<bwtype>:<bandwidth>"');return{bwtype:e.slice(0,t),bandwidth:i(e.slice(t+1),"bandwidth")}},c=e=>{const t=e.split(" ");if(3!==t.length)throw new Error('expected "<nettype> <addrtype> <connection-address>"');const[r,i,s]=t;return{netType:r,addrType:i,connectionAddress:s}},p=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0,l=(e,t,r)=>{Object.defineProperty(e,t,{value:r,writable:!0,enumerable:!0,configurable:!0})},m=(e,t)=>{if(void 0===e||0===e.length)throw new Error(`expected a value: ${t}`);return e},u=e=>{if(void 0!==e)throw new Error("flag attribute must not have a value");return!0},f=e=>{const t=e.indexOf(" ");return-1===t?[e,void 0]:[e.slice(0,t),e.slice(t+1)]},v=(e,t)=>{var r;const s=i(t,"payload type");return null===(r=e.codecs)||void 0===r?void 0:r.find((e=>e.payloadType===s))},h=(e,t,r,i)=>{if(void 0!==e[t])throw new Error(`duplicate a=${i}`);e[t]=r},g=(e,t,r)=>{void 0===e[t]&&(e[t]=[]),e[t].push(r)},$=["sendrecv","sendonly","recvonly","inactive"],w=e=>{const t={};for(const r of e.split(";")){const e=r.trim();if(0===e.length)continue;const i=e.indexOf("=");-1===i?l(t,e,null):l(t,e.slice(0,i),e.slice(i+1))}return t},y='"<foundation> <component> <transport> <priority> <address> <port> typ <type> [raddr <addr>] [rport <port>] *(<ext-name> <ext-value>)"',b=e=>{const t=i(e,"ssrc-id");if(t>4294967295)throw new Error(`ssrc-id ${t} does not fit into 32 bits`);return t},x=/^[A-Za-z0-9_-]+$/,k=["session","media"],E=["media"],T=["session"],j=["active","passive","actpass","holdconn"],P=e=>({scopes:k,parse:t=>h(t,"direction",e,e)}),F={sendrecv:P("sendrecv"),sendonly:P("sendonly"),recvonly:P("recvonly"),inactive:P("inactive"),"ice-ufrag":{scopes:k,parse:(e,t)=>h(e,"iceUfrag",m(t,"a=ice-ufrag:<ufrag>"),"ice-ufrag")},"ice-pwd":{scopes:k,parse:(e,t)=>h(e,"icePwd",m(t,"a=ice-pwd:<password>"),"ice-pwd")},"ice-options":{scopes:k,parse:(e,t)=>h(e,"iceOptions",m(t,"a=ice-options:<option> ...").split(" "),"ice-options")},"ice-lite":{scopes:T,parse:(e,t)=>h(e,"iceLite",u(t),"ice-lite")},candidate:{scopes:E,parse:(e,t)=>g(e,"candidates",(e=>{var t;const r=e.split(" ");if(r.length<8||"typ"!==r[6]||r.length%2!=0)throw new Error(`expected ${y}`);const[s,n,o,a,d,c,,p]=r,m={foundation:s,component:i(n,"component id"),transport:o,priority:i(a,"priority"),address:d,port:i(c,"port"),type:p};for(let e=8;e<r.length;e+=2){const[s,n]=[r[e],r[e+1]];if(0===s.length||0===n.length)throw new Error(`expected ${y}`);switch(s){case"raddr":m.relatedAddress=n;break;case"rport":m.relatedPort=i(n,"rport");break;case"tcptype":m.tcpType=n;break;default:l(null!==(t=m.extensions)&&void 0!==t?t:m.extensions={},s,n)}}return m})(m(t,"a=candidate:<candidate>")))},"end-of-candidates":{scopes:k,parse:(e,t)=>h(e,"endOfCandidates",u(t),"end-of-candidates")},fingerprint:{scopes:k,parse:(e,t)=>{const[r,i]=f(m(t,"a=fingerprint:<hash-function> <fingerprint>"));if(!i||i.includes(" "))throw new Error('expected "a=fingerprint:<hash-function> <fingerprint>"');g(e,"fingerprints",{hashFunction:r,fingerprint:i})}},setup:{scopes:k,parse:(e,t)=>{if(!j.includes(t))throw new Error(`a=setup must be one of ${j.join(", ")}`);h(e,"setup",t,"setup")}},extmap:{scopes:k,parse:(e,t)=>g(e,"extmap",(e=>{const[t,r]=f(e);if(!r)throw new Error('expected "<id>[/<direction>] <uri> [<attributes>]"');const[s,n,...o]=t.split("/");if(o.length||void 0!==n&&!$.includes(n))throw new Error(`invalid extmap id "${t}"`);const[a,d]=f(r),c={id:i(s,"extmap id"),uri:a};return void 0!==n&&(c.direction=n),void 0!==d&&(c.attributes=d),c})(m(t,"a=extmap:<id> <uri>")))},group:{scopes:T,parse:(e,t)=>g(e,"group",(e=>{const[t,...r]=e.split(" ");if(0===t.length||r.some((e=>0===e.length)))throw new Error('expected "<semantics> <mid> ..."');return{semantics:t,mids:r}})(m(t,"a=group:<semantics> <mid> ...")))},mid:{scopes:E,parse:(e,t)=>h(e,"mid",m(t,"a=mid:<identification-tag>"),"mid")},msid:{scopes:E,parse:(e,t)=>g(e,"msid",(e=>{const[t,r,...i]=e.split(" ");if(0===t.length||""===r||i.length)throw new Error('expected "<stream id> [<track id>]"');return void 0===r?{streamId:t}:{streamId:t,trackId:r}})(m(t,"a=msid:<stream id> [<track id>]")))},ssrc:{scopes:E,parse:(e,t)=>((e,t)=>{var r;const[i,s]=f(t);if(!s)throw new Error('expected "<ssrc-id> <attribute>[:<value>]"');const n=b(i),o=s.indexOf(":"),a=-1===o?s:s.slice(0,o),d=-1===o?null:s.slice(o+1),c=null!==(r=e.ssrcs)&&void 0!==r?r:e.ssrcs=[];let m=c.find((e=>e.id===n));return m||(m={id:n,attributes:{}},c.push(m)),void 0===p(m.attributes,a)&&(l(m.attributes,a,d),!0)})(e,m(t,"a=ssrc:<ssrc-id> <attribute>[:<value>]"))},"ssrc-group":{scopes:E,parse:(e,t)=>g(e,"ssrcGroups",(e=>{const[t,...r]=e.split(" ");if(0===t.length)throw new Error('expected "<semantics> <ssrc-id> ..."');return{semantics:t,ssrcs:r.map(b)}})(m(t,"a=ssrc-group:<semantics> <ssrc-id> ...")))},rid:{scopes:E,parse:(e,t)=>g(e,"rids",(e=>{const[t,r]=f(e),[s,n]=r?f(r):[void 0,void 0];if(!x.test(t)||"send"!==s&&"recv"!==s||""===n)throw new Error('expected "<rid-id> <send|recv> [<params>]"');const o={id:t,direction:s};if(void 0!==n){const e=w(n);if("pt"in e){if(!n.startsWith("pt=")||null===e.pt)throw new Error('"pt=" must be the first rid parameter');o.payloadTypes=e.pt.split(",").map((e=>i(e,"payload type"))),delete e.pt}Object.keys(e).length&&(o.restrictions=e)}return o})(m(t,"a=rid:<rid-id> <send|recv>")))},simulcast:{scopes:E,parse:(e,t)=>h(e,"simulcast",(e=>{const t=e.split(" ");if(2!==t.length&&4!==t.length)throw new Error('expected "send|recv <streams> [send|recv <streams>]"');const r={};for(let e=0;e<t.length;e+=2){const i=t[e];if("send"!==i&&"recv"!==i||r[i])throw new Error('expected "send|recv <streams> [send|recv <streams>]"');r[i]=t[e+1].split(";").map((e=>e.split(",").map((e=>{const t=e.startsWith("~"),r=t?e.slice(1):e;if(!x.test(r))throw new Error(`invalid simulcast rid "${e}"`);return t?{rid:r,paused:t}:{rid:r}}))))}return r})(m(t,"a=simulcast:<send|recv> <streams>")),"simulcast")},"extmap-allow-mixed":{scopes:k,parse:(e,t)=>h(e,"extmapAllowMixed",u(t),"extmap-allow-mixed")},"rtcp-mux":{scopes:E,parse:(e,t)=>h(e,"rtcpMux",u(t),"rtcp-mux")},"rtcp-rsize":{scopes:E,parse:(e,t)=>h(e,"rtcpRsize",u(t),"rtcp-rsize")},rtcp:{scopes:E,parse:(e,t)=>{const r=m(t,"a=rtcp:<port> [<nettype> <addrtype> <address>]").split(" ");if(1!==r.length&&4!==r.length)throw new Error('expected "a=rtcp:<port> [<nettype> <addrtype> <address>]"');const[s,n,o,a]=r;h(e,"rtcp",1===r.length?{port:i(s,"port")}:{port:i(s,"port"),netType:n,addrType:o,address:a},"rtcp")}},"sctp-port":{scopes:E,parse:(e,t)=>h(e,"sctpPort",i(m(t,"a=sctp-port:<port>"),"sctp-port"),"sctp-port")},"max-message-size":{scopes:E,parse:(e,t)=>h(e,"maxMessageSize",i(m(t,"a=max-message-size:<size>"),"max-message-size"),"max-message-size")},rtpmap:{scopes:E,parse:(e,t)=>{const[r,s]=f(m(t,"a=rtpmap:<payload type> <encoding>")),n=e.codecs?v(e,r):void 0;if(!n||void 0!==n.encodingName)return!1;((e,t)=>{const[r,s,n,...o]=t.split("/");if(!r||void 0===s||o.length)throw new Error('expected "<payload type> <encoding name>/<clock rate>[/<encoding parameters>]"');e.encodingName=r,e.clockRate=i(s,"clock rate"),void 0!==n&&(e.encodingParameters=i(n,"encoding parameters"))})(n,null!=s?s:"")}},fmtp:{scopes:E,parse:(e,t)=>{const[r,i]=f(m(t,"a=fmtp:<format> <parameters>")),s=e.codecs?v(e,r):void 0;if(!s||void 0!==s.parameters||void 0===i)return!1;s.parameters=w(i)}},"rtcp-fb":{scopes:E,parse:(e,t)=>{const[r,i]=f(m(t,"a=rtcp-fb:<payload type> <feedback>"));if(!i||!e.codecs)return!1;if("*"===r)return void g(e,"rtcpFeedbackWildcard",i);const s=v(e,r);if(!s)return!1;g(s,"rtcpFeedback",i)}}},z=(e,t,r)=>{const i=r.indexOf(":"),s=-1===i?r:r.slice(0,i),n=-1===i?void 0:r.slice(i+1);if(0===s.length)throw new Error("empty attribute name");const o=p(F,s);o&&o.scopes.includes(t)&&!1!==o.parse(e,n)||g(e,"attributes",void 0===n?{name:s}:{name:s,value:n})},I={v:0,o:1,s:2,i:3,u:4,e:5,p:6,c:7,b:8,t:9,r:10,z:11,k:12,a:13},O=new Set(["e","p","b","a"]),C={m:0,i:1,c:2,b:3,k:4,a:5},R=new Set(["b","a"]),A={session:"v o s i u e p c b t r z k a",media:"m i c b k a"},N=r=>{const s=r.split(/\r?\n/);""===s[s.length-1]&&s.pop();const p={media:[]};let l=null,m=null,u=-1;s.forEach(((r,s)=>{var f,v,h,g,$,w,y;const b=t=>{throw new e(t,s+1,r)};0===r.length&&b("empty line"),(r.length<2||"="!==r[1])&&b('expected "<type>=<value>"');const x=r[0],k=r.slice(2);if(0===s&&"v"!==x&&b('a session description must start with "v="'),"m"===x)u=0;else{const e=l?"media":"session",t=l?R:O,r=(l?C:I)[x];void 0===r&&b(x in I?`"${x}=" is not allowed in a media description`:`unknown line type "${x}"`),l||"t"!==x?l||"r"!==x&&"z"!==x?r<u?b(`line is out of order (${e} order: ${A[e]})`):r!==u||t.has(x)||b(l&&"c"===x?'multiple "c=" lines in one media description are not supported':`duplicate "${x}=" line`):u!==I.t&&u!==I.r&&b(`"${x}=" must follow "t=" or "r="`):u>I.z&&b(`"t=" must precede "k=" and "a=" (order: ${A.session})`),u=r}try{if(l){switch(x){case"i":l.information=k;break;case"c":l.connectionInformation=c(k);break;case"b":(null!==(f=l.bandwidths)&&void 0!==f?f:l.bandwidths=[]).push(d(k));break;case"k":l.encryptionKey=o(k);break;case"a":z(l,"media",k)}if("m"!==x)return}switch(x){case"v":"0"!==k&&b(`unsupported protocol version "${k}"`),p.protocolVersion=0;break;case"o":p.origin=(e=>{const r=e.split(" ");if(6!==r.length)throw new Error('expected "<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>"');const[i,s,n,o,a,d]=r;if(!t.test(s))throw new Error(`sess-id "${s}" is not numeric`);if(!t.test(n))throw new Error(`sess-version "${n}" is not numeric`);return{username:i,sessId:s,sessVersion:n,netType:o,addrType:a,unicastAddress:d}})(k);break;case"s":p.sessionName=k;break;case"i":p.sessionInformation=k;break;case"u":p.uri=k;break;case"e":(null!==(v=p.emailAddresses)&&void 0!==v?v:p.emailAddresses=[]).push(k);break;case"p":(null!==(h=p.phoneNumbers)&&void 0!==h?h:p.phoneNumbers=[]).push(k);break;case"c":p.connectionInformation=c(k);break;case"b":(null!==(g=p.bandwidths)&&void 0!==g?g:p.bandwidths=[]).push(d(k));break;case"t":m=(e=>{const t=e.split(" ");if(2!==t.length)throw new Error('expected "<start-time> <stop-time>"');return{startTime:i(t[0],"start time"),stopTime:i(t[1],"stop time")}})(k),(null!==($=p.timing)&&void 0!==$?$:p.timing=[]).push(m);break;case"r":(null!==(w=(y=m).repeat)&&void 0!==w?w:y.repeat=[]).push((e=>{const t=e.split(" ");if(t.length<3)throw new Error('expected "<repeat interval> <active duration> <offset>..."');const[r,i,...s]=t.map((e=>n(e)));return{repeatInterval:r,activeDuration:i,offsets:s}})(k));break;case"z":m.zoneAdjustments=(e=>{const t=e.split(" ");if(t.length<2||t.length%2!=0)throw new Error('expected pairs of "<adjustment time> <offset>"');const r=[];for(let e=0;e<t.length;e+=2)r.push({adjustmentTime:i(t[e],"adjustment time"),offset:n(t[e+1],!0)});return r})(k);break;case"k":p.encryptionKey=o(k);break;case"a":z(p,"session",k);break;case"m":l=(e=>{const t=e.split(" ");if(t.length<4)throw new Error('expected "<media> <port> <proto> <fmt> ..."');const[r,s,n,...o]=t,[d,c,...p]=s.split("/");if(p.length)throw new Error(`invalid port "${s}"`);const l={type:r,port:i(d,"port"),proto:n};return void 0!==c&&(l.numberOfPorts=i(c,"number of ports")),a(n)?l.codecs=o.map((e=>{const t=i(e,"payload type");if(t>127)throw new Error(`payload type ${t} is out of range 0-127`);return{payloadType:t}})):l.formats=o,l})(k),p.media.push(l)}}catch(t){if(t instanceof e)throw t;b(t.message)}}));for(const[t,r]of[["protocolVersion","v"],["origin","o"],["sessionName","s"],["timing","t"]])if(void 0===p[t])throw new e(`missing mandatory "${r}=" line`,0,"");return p},S={parse:N,isValid:(e,t=!1)=>{try{return N(e),!0}catch(e){return t&&console.error(e),!1}}},U=e=>`c=${e.netType} ${e.addrType} ${e.connectionAddress}`,M=e=>`b=${e.bwtype}:${e.bandwidth}`,q=e=>`k=${e.method}${void 0!==e.encryptionKey?`:${e.encryptionKey}`:""}`,K=e=>Object.entries(e).map((([e,t])=>null===t?e:`${e}=${t}`)).join(";"),V=e=>{var t,r;return[`t=${e.startTime} ${e.stopTime}`,...(null!==(t=e.repeat)&&void 0!==t?t:[]).map((e=>`r=${[e.repeatInterval,e.activeDuration,...e.offsets].join(" ")}`)),...(null===(r=e.zoneAdjustments)||void 0===r?void 0:r.length)?[`z=${e.zoneAdjustments.map((e=>`${e.adjustmentTime} ${e.offset}`)).join(" ")}`]:[]]},D=e=>{var t;return[...void 0!==e.encodingName?[`a=rtpmap:${e.payloadType} ${e.encodingName}/${e.clockRate}${void 0!==e.encodingParameters?`/${e.encodingParameters}`:""}`]:[],...(null!==(t=e.rtcpFeedback)&&void 0!==t?t:[]).map((t=>`a=rtcp-fb:${e.payloadType} ${t}`)),...void 0!==e.parameters?[`a=fmtp:${e.payloadType} ${K(e.parameters)}`]:[]]},L=e=>{const t=[...e.payloadTypes?[`pt=${e.payloadTypes.join(",")}`]:[],...e.restrictions&&Object.keys(e.restrictions).length?[K(e.restrictions)]:[]].join(";");return`a=rid:${e.id} ${e.direction}${t?` ${t}`:""}`},W=(e,t)=>e?[`a=${t}`]:[],G=e=>{var t;return[...void 0!==e.iceUfrag?[`a=ice-ufrag:${e.iceUfrag}`]:[],...void 0!==e.icePwd?[`a=ice-pwd:${e.icePwd}`]:[],...e.iceOptions?[`a=ice-options:${e.iceOptions.join(" ")}`]:[],...(null!==(t=e.fingerprints)&&void 0!==t?t:[]).map((e=>`a=fingerprint:${e.hashFunction} ${e.fingerprint}`)),...void 0!==e.setup?[`a=setup:${e.setup}`]:[]]},B=e=>{var t;return[...(null!==(t=e.extmap)&&void 0!==t?t:[]).map((e=>`a=extmap:${e.id}${e.direction?`/${e.direction}`:""} ${e.uri}${void 0!==e.attributes?` ${e.attributes}`:""}`)),...W(e.extmapAllowMixed,"extmap-allow-mixed")]},Z=e=>{var t;return(null!==(t=e.attributes)&&void 0!==t?t:[]).map((e=>void 0===e.value?`a=${e.name}`:`a=${e.name}:${e.value}`))},_=e=>{var t,r,i,s,n,o,a,d;const c=e.codecs?e.codecs.map((e=>String(e.payloadType))):e.formats;if(!c||0===c.length)throw new Error(`media section "${e.type}" has neither codecs nor formats`);return[`m=${e.type} ${e.port}${void 0!==e.numberOfPorts?`/${e.numberOfPorts}`:""} ${e.proto} ${c.join(" ")}`,...void 0!==e.information?[`i=${e.information}`]:[],...e.connectionInformation?[U(e.connectionInformation)]:[],...(null!==(t=e.bandwidths)&&void 0!==t?t:[]).map(M),...e.encryptionKey?[q(e.encryptionKey)]:[],...e.rtcp?[`a=rtcp:${e.rtcp.port}${void 0!==e.rtcp.netType?` ${e.rtcp.netType} ${e.rtcp.addrType} ${e.rtcp.address}`:""}`]:[],...G(e),...void 0!==e.mid?[`a=mid:${e.mid}`]:[],...B(e),...e.direction?[`a=${e.direction}`]:[],...(null!==(r=e.msid)&&void 0!==r?r:[]).map((e=>`a=msid:${e.streamId}${void 0!==e.trackId?` ${e.trackId}`:""}`)),...W(e.rtcpMux,"rtcp-mux"),...W(e.rtcpRsize,"rtcp-rsize"),...(null!==(i=e.codecs)&&void 0!==i?i:[]).flatMap(D),...(null!==(s=e.rtcpFeedbackWildcard)&&void 0!==s?s:[]).map((e=>`a=rtcp-fb:* ${e}`)),...void 0!==e.sctpPort?[`a=sctp-port:${e.sctpPort}`]:[],...void 0!==e.maxMessageSize?[`a=max-message-size:${e.maxMessageSize}`]:[],...(null!==(n=e.rids)&&void 0!==n?n:[]).map(L),...e.simulcast?[(p=e.simulcast,`a=simulcast:${["send","recv"].filter((e=>p[e])).map((e=>`${e} ${p[e].map((e=>e.map((e=>`${e.paused?"~":""}${e.rid}`)).join(","))).join(";")}`)).join(" ")}`)]:[],...(null!==(o=e.ssrcGroups)&&void 0!==o?o:[]).map((e=>`a=ssrc-group:${[e.semantics,...e.ssrcs].join(" ")}`)),...(null!==(a=e.ssrcs)&&void 0!==a?a:[]).flatMap((e=>Object.entries(e.attributes).map((([t,r])=>`a=ssrc:${e.id} ${t}${null===r?"":`:${r}`}`)))),...(null!==(d=e.candidates)&&void 0!==d?d:[]).map((e=>`a=candidate:${(e=>{var t;return[e.foundation,e.component,e.transport,e.priority,e.address,e.port,"typ",e.type,...void 0!==e.relatedAddress?["raddr",e.relatedAddress]:[],...void 0!==e.relatedPort?["rport",e.relatedPort]:[],...void 0!==e.tcpType?["tcptype",e.tcpType]:[],...Object.entries(null!==(t=e.extensions)&&void 0!==t?t:{}).flat()].join(" ")})(e)}`)),...W(e.endOfCandidates,"end-of-candidates"),...Z(e)];var p},H={write:e=>{var t,r,i,s,n,o,a,d;const{origin:c}=e;if(!c)throw new Error("origin (o=) is required");if(!(null===(t=e.timing)||void 0===t?void 0:t.length))throw new Error("at least one timing (t=) entry is required");return[`v=${null!==(r=e.protocolVersion)&&void 0!==r?r:0}`,`o=${c.username} ${c.sessId} ${c.sessVersion} ${c.netType} ${c.addrType} ${c.unicastAddress}`,`s=${null!==(i=e.sessionName)&&void 0!==i?i:"-"}`,...void 0!==e.sessionInformation?[`i=${e.sessionInformation}`]:[],...void 0!==e.uri?[`u=${e.uri}`]:[],...(null!==(s=e.emailAddresses)&&void 0!==s?s:[]).map((e=>`e=${e}`)),...(null!==(n=e.phoneNumbers)&&void 0!==n?n:[]).map((e=>`p=${e}`)),...e.connectionInformation?[U(e.connectionInformation)]:[],...(null!==(o=e.bandwidths)&&void 0!==o?o:[]).map(M),...e.timing.flatMap(V),...e.encryptionKey?[q(e.encryptionKey)]:[],...(null!==(a=e.group)&&void 0!==a?a:[]).map((e=>`a=group:${[e.semantics,...e.mids].join(" ")}`)),...W(e.iceLite,"ice-lite"),...G(e),...B(e),...e.direction?[`a=${e.direction}`]:[],...W(e.endOfCandidates,"end-of-candidates"),...Z(e),...(null!==(d=e.media)&&void 0!==d?d:[]).flatMap(_)].join("\r\n")+"\r\n"}},J={md2:16,md5:16,"sha-1":20,"sha-224":28,"sha-256":32,"sha-384":48,"sha-512":64},Q=/^[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2})*$/,X={validate:(t,r={})=>{var i,s,n,o;let d;if("string"==typeof t)try{d=S.parse(t)}catch(t){if(t instanceof e)return[{severity:"error",code:"parse-error",message:t.message,lineNumber:t.lineNumber}];throw t}else d=t;const c=[],l=(e,t,r)=>c.push(void 0===r?{severity:"error",code:e,message:t}:{severity:"error",code:e,message:t,mediaIndex:r}),m=(e,t,r)=>c.push(void 0===r?{severity:"warning",code:e,message:t}:{severity:"warning",code:e,message:t,mediaIndex:r}),u=null!==(i=d.media)&&void 0!==i?i:[];0!==d.protocolVersion&&l("invalid-version",`v= must be 0, got ${d.protocolVersion}`),d.origin||l("missing-origin","o= is required"),void 0===d.sessionName?l("missing-session-name","s= is required"):""===d.sessionName&&m("empty-session-name",'s= must not be empty; use "s= " or "s=-" (RFC 8866 §5.3)'),(null===(s=d.timing)||void 0===s?void 0:s.length)||l("missing-timing","at least one t= line is required"),d.connectionInformation||u.forEach(((e,t)=>{e.connectionInformation||l("missing-connection","c= is required at session level or in every media description (RFC 8866 §5.7)",t)}));const f=new Set;u.forEach(((e,t)=>{void 0!==e.mid&&(f.has(e.mid)&&l("duplicate-mid",`a=mid:${e.mid} is used by more than one media description`,t),f.add(e.mid))}));for(const e of null!==(n=d.group)&&void 0!==n?n:[])for(const t of e.mids)f.has(t)||l("group-unknown-mid",`a=group:${e.semantics} references unknown mid "${t}"`);(null===(o=d.group)||void 0===o?void 0:o.some((e=>"BUNDLE"===e.semantics)))&&u.forEach(((e,t)=>{void 0===e.mid&&l("bundle-missing-mid","every media description needs a=mid when BUNDLE is used (RFC 9143)",t)}));const v=void 0!==d.iceUfrag||void 0!==d.icePwd||d.iceLite||u.some((e=>{var t;return void 0!==e.iceUfrag||void 0!==e.icePwd||(null===(t=e.candidates)||void 0===t?void 0:t.length)})),h=(e,t,r)=>{void 0!==e&&(e.length<4||e.length>256)&&l("ice-ufrag-length",`ice-ufrag must be 4-256 characters, got ${e.length} (RFC 8839 §5.4)`,r),void 0!==t&&(t.length<22||t.length>256)&&l("ice-pwd-length",`ice-pwd must be 22-256 characters, got ${t.length} (RFC 8839 §5.4)`,r)};h(d.iceUfrag,d.icePwd);const g=(e,t)=>{var r;for(const{hashFunction:i,fingerprint:s}of null!==(r=e.fingerprints)&&void 0!==r?r:[]){const e=p(J,i.toLowerCase());Q.test(s)?void 0!==e&&s.split(":").length!==e?l("invalid-fingerprint",`a=fingerprint:${i} has ${s.split(":").length} bytes, expected ${e} (RFC 8122 §5)`,t):s!==s.toUpperCase()&&m("fingerprint-not-uppercase",`a=fingerprint:${i} must use upper-case hex (RFC 8122 §5)`,t):l("invalid-fingerprint",`a=fingerprint:${i} must be colon-separated hex pairs (RFC 8122 §5)`,t)}};return g(d),u.forEach(((e,t)=>{var i,s,n,o,c,p,u,f,$,w,y,b,x,k,E,T;const j=0!==e.port;if(h(e.iceUfrag,e.icePwd,t),g(e,t),v&&j&&(void 0===(null!==(i=e.iceUfrag)&&void 0!==i?i:d.iceUfrag)&&l("missing-ice-ufrag","a=ice-ufrag is required (RFC 8839 §5.4)",t),void 0===(null!==(s=e.icePwd)&&void 0!==s?s:d.icePwd)&&l("missing-ice-pwd","a=ice-pwd is required (RFC 8839 §5.4)",t)),(e=>/(^|\/)(D?TLS)(\/|$)/.test(e.proto))(e)&&j){(null===(n=e.fingerprints)||void 0===n?void 0:n.length)||(null===(o=d.fingerprints)||void 0===o?void 0:o.length)||l("missing-fingerprint",`a=fingerprint is required for ${e.proto} (RFC 8842 §5.2, §5.3)`,t);const i=null!==(c=e.setup)&&void 0!==c?c:d.setup;void 0===i?l("missing-setup",`a=setup is required for ${e.proto} (RFC 8842 §5.2, §5.3)`,t):"answer"===r.type&&"actpass"===i&&l("setup-actpass-in-answer","an answer must use a=setup:active or a=setup:passive (RFC 5763 §5)",t)}if(a(e.proto)){for(const r of null!==(p=e.codecs)&&void 0!==p?p:[])r.payloadType>34&&void 0===r.encodingName&&l("missing-rtpmap",`payload type ${r.payloadType} is dynamic and needs a=rtpmap (RFC 8866 §6.6)`,t);for(const r of null!==(u=e.attributes)&&void 0!==u?u:[])"rtpmap"!==r.name&&"fmtp"!==r.name&&"rtcp-fb"!==r.name||m("unknown-payload-type",`a=${r.name}:${r.value} refers to a payload type that is not in the m= line, or repeats`,t)}const P=new Set;for(const r of null!==(f=e.extmap)&&void 0!==f?f:[])(r.id<1||15===r.id||r.id>255)&&l("extmap-invalid-id",`extmap id ${r.id} must be 1-14 or 16-255 (RFC 8285)`,t),P.has(r.id)&&l("extmap-duplicate-id",`extmap id ${r.id} is used more than once`,t),P.add(r.id);for(const r of["send","recv"])for(const i of(null!==(w=null===($=e.simulcast)||void 0===$?void 0:$[r])&&void 0!==w?w:[]).flat())(null===(y=e.rids)||void 0===y?void 0:y.some((e=>e.id===i.rid&&e.direction===r)))||l("simulcast-unknown-rid",`a=simulcast ${r} references "${i.rid}" without a matching a=rid:${i.rid} ${r} (RFC 8853 §5.1)`,t);for(const r of null!==(b=e.rids)&&void 0!==b?b:[])for(const i of null!==(x=r.payloadTypes)&&void 0!==x?x:[])(null===(k=e.codecs)||void 0===k?void 0:k.some((e=>e.payloadType===i)))||l("rid-unknown-payload-type",`a=rid:${r.id} references payload type ${i} that is not in the m= line (RFC 8851)`,t);for(const r of null!==(E=e.ssrcGroups)&&void 0!==E?E:[])for(const i of r.ssrcs)(null===(T=e.ssrcs)||void 0===T?void 0:T.some((e=>e.id===i)))||m("ssrc-group-unknown-ssrc",`a=ssrc-group:${r.semantics} references ssrc ${i} without an a=ssrc line`,t)})),c}},Y="1.2.0";export{e as SdpParseError,S as sdpReader,X as sdpValidator,H as sdpWriter,Y as version};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irbisadm/simple-sdp",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Parse, modify, write and validate SDP (RFC 8866) for WebRTC and SIP. Typed ICE, DTLS, BUNDLE, simulcast, codecs. Zero dependencies.",
6
6
  "author": {
@@ -22,7 +22,13 @@
22
22
  "types": "./types/index.d.ts",
23
23
  "import": "./dist/index.mjs",
24
24
  "default": "./dist/index.mjs"
25
- }
25
+ },
26
+ "./wasm": {
27
+ "types": "./wasm/dist/index.d.ts",
28
+ "import": "./wasm/dist/index.mjs",
29
+ "default": "./wasm/dist/index.mjs"
30
+ },
31
+ "./wasm/simple-sdp.wasm": "./wasm/dist/simple-sdp.wasm"
26
32
  },
27
33
  "sideEffects": false,
28
34
  "keywords": [
@@ -37,11 +43,16 @@
37
43
  "typescript",
38
44
  "parser",
39
45
  "simulcast",
40
- "ice"
46
+ "ice",
47
+ "wasm"
41
48
  ],
42
49
  "files": [
43
50
  "./dist/index.mjs",
44
51
  "./types/index.d.ts",
52
+ "./wasm/dist/index.mjs",
53
+ "./wasm/dist/index.d.ts",
54
+ "./wasm/dist/simple-sdp.mjs",
55
+ "./wasm/dist/simple-sdp.wasm",
45
56
  "./readme.md",
46
57
  "./CHANGELOG.md"
47
58
  ],
@@ -51,6 +62,7 @@
51
62
  "@rollup/plugin-typescript": "^11.0.0",
52
63
  "@types/jest": "^29.5.0",
53
64
  "@types/node": "^18.14.0",
65
+ "assemblyscript": "0.28.20",
54
66
  "jest": "^29.5.0",
55
67
  "rollup": "^3.17.1",
56
68
  "rollup-plugin-dts": "^5.2.0",
@@ -64,6 +76,10 @@
64
76
  "scripts": {
65
77
  "build": "rollup -c",
66
78
  "test": "jest",
67
- "typecheck": "tsc --noEmit"
79
+ "typecheck": "tsc --noEmit",
80
+ "asbuild": "asc --config asconfig.json --target release && node scripts/sync-wasm.mjs",
81
+ "asbuild:debug": "asc --config asconfig.json --target debug",
82
+ "test:wasm": "jest -c jest.wasm.config.js && node test-wasm/npm-entry.mjs",
83
+ "prepack": "yarn asbuild && yarn build"
68
84
  }
69
85
  }
package/readme.md CHANGED
@@ -186,6 +186,79 @@ Every fixture parses, round-trips through the writer and validates without issue
186
186
 
187
187
  Safari is not covered by fixtures yet.
188
188
 
189
+ ## WebAssembly, iOS and Android
190
+
191
+ [`assembly/`](assembly) is a port of the library to [AssemblyScript](https://www.assemblyscript.org/).
192
+ It compiles to a WebAssembly module (≈90 KB, ≈32 KB gzipped) with no imports, so the same
193
+ parser, writer and validator run in any wasm runtime: browsers and Node.js, iOS through a
194
+ wasm interpreter (no JIT, as the App Store requires), Android and the JVM as bytecode
195
+ compiled from the module at build time.
196
+
197
+ The port behaves exactly like the TypeScript code. `yarn test:wasm` runs the test suites of
198
+ this package against the wasm build, plus a differential test that compares both
199
+ implementations on all fixtures and on thousands of mutations of them: same model, same
200
+ written text, same issues, same error messages.
201
+
202
+ | Platform | Get it | Runtime |
203
+ |---|---|---|
204
+ | iOS 18+, macOS 15+ | SwiftPM: `.package(url: "https://github.com/irbisadm/simple-sdp.git", from: "1.2.0")`, product `SimpleSdp` ([`platforms/apple`](platforms/apple)) | [WasmKit](https://github.com/swiftwasm/WasmKit) interpreter |
205
+ | Android, JVM 11+ | JitPack: `maven("https://jitpack.io")`, `implementation("com.github.irbisadm:simple-sdp:v1.2.0")` ([`platforms/kotlin`](platforms/kotlin)) | [Chicory](https://chicory.dev), compiled to bytecode at build time |
206
+ | Browser, Node.js | npm: `import {sdpReader} from '@irbisadm/simple-sdp/wasm'` — the API of the main entry point | the JS engine |
207
+ | Other wasm hosts | `simple-sdp.wasm` from [GitHub Releases](https://github.com/irbisadm/simple-sdp/releases) or `@irbisadm/simple-sdp/wasm/simple-sdp.wasm` | any |
208
+
209
+ For JavaScript the TypeScript build is the faster choice (see the timings below); the
210
+ `/wasm` entry point exists for parity checks and for environments that prefer one shared
211
+ implementation. It is an ES module with top-level await that loads `simple-sdp.wasm` through
212
+ `new URL("simple-sdp.wasm", import.meta.url)`: Node.js 17+ and Vite handle it as is.
213
+
214
+ The model crosses the wasm boundary as JSON with the shape of the TypeScript `Sdp` interface.
215
+
216
+ ```swift
217
+ let sdp = try SimpleSdp()
218
+ let json = try sdp.parseJSON(offer) // Data: the model, key order kept
219
+ let issues = try sdp.validate(json: json, type: .offer)
220
+ let text = try sdp.write(json: json)
221
+ let model = try sdp.parse(offer) // [String: Any] (does not keep key order)
222
+ ```
223
+
224
+ ```kotlin
225
+ val sdp = SimpleSdp()
226
+ val model = sdp.parse(offer) // kotlinx.serialization JsonObject
227
+ val issues = sdp.validate(model, SdpType.OFFER)
228
+ val text = sdp.write(model)
229
+ ```
230
+
231
+ ### Module interface
232
+
233
+ The module exports a C-style interface on UTF-8 buffers for hosts without AssemblyScript
234
+ bindings:
235
+
236
+ | Export | |
237
+ |---|---|
238
+ | `sdp_alloc(len) → ptr` | allocate an input buffer; write the UTF-8 bytes at `ptr` |
239
+ | `sdp_parse(ptr, len) → res` | `{"sdp": model}` or `{"error": {message, reason, lineNumber, line}}` |
240
+ | `sdp_write(ptr, len) → res` | model JSON in; `{"sdp": "text"}` or `{"error": {message}}` |
241
+ | `sdp_validate(ptr, len, type) → res` | SDP text in, `type` 0 = none, 1 = offer, 2 = answer; `{"issues": [...]}` |
242
+ | `sdp_validate_model(ptr, len, type) → res` | model JSON in; `{"issues": [...]}` or `{"error": {message}}` |
243
+ | `sdp_version() → res` | version string |
244
+ | `sdp_len(res) → len` | byte length of a buffer |
245
+ | `sdp_free(ptr)` | release an input buffer or a result |
246
+
247
+ A module instance is single-threaded; the Swift and Kotlin wrappers serialize calls.
248
+ The module uses the minimal AssemblyScript runtime: memory is reclaimed at the end of every
249
+ call, so a result stays valid until the next call on the same instance.
250
+
251
+ Approximate time to parse / write an 18 KB Chrome offer on x86-64, JSON transfer included:
252
+ wasm in Node.js 1.3 / 1.2 ms (the TypeScript build: 0.35 / 0.25 ms), JVM with the compiled
253
+ module 6 / 9 ms, WasmKit (release build) ≈10 ms each.
254
+
255
+ The generated ESM bindings (`wasm/build/simple-sdp.js`, published as
256
+ `wasm/dist/simple-sdp.mjs`) take strings: `parse(text)`, `write(json)`, `validate(text, type)`,
257
+ `validateModel(json, type)`.
258
+
259
+ Build: `yarn asbuild` compiles the module and copies it into `platforms/` (the copies are
260
+ committed so that SwiftPM and Gradle builds do not need Node.js).
261
+
189
262
  ## License
190
263
 
191
264
  MIT
package/types/index.d.ts CHANGED
@@ -332,6 +332,6 @@ declare class SdpParseError extends Error {
332
332
  constructor(reason: string, lineNumber: number, line: string);
333
333
  }
334
334
 
335
- declare const version = "1.0.1";
335
+ declare const version = "1.2.0";
336
336
 
337
337
  export { Sdp, SdpBandwidthInformation, SdpCandidate, SdpCodec, SdpCommonAttributes, SdpConnectionInformation, SdpDirection, SdpEncryptionKey, SdpExtmap, SdpFingerprint, SdpGenericAttribute, SdpGroup, SdpIssue, SdpIssueCode, SdpMediaSection, SdpMsid, SdpOrigin, SdpPair, SdpParseError, SdpRepeatTimes, SdpRid, SdpRtcp, SdpSetup, SdpSimulcast, SdpSimulcastRid, SdpSimulcastStreams, SdpSsrc, SdpSsrcGroup, SdpTimeZoneAdjustment, SdpTiming, SdpValidateOptions, sdpReader, sdpValidator, sdpWriter, version };
@@ -0,0 +1,2 @@
1
+ // The same API and types as the main entry point.
2
+ export * from '../../types/index';
@@ -0,0 +1,51 @@
1
+ // @irbisadm/simple-sdp/wasm — the API of the main entry point, run by the WebAssembly build
2
+ // (assembly/). Published as wasm/dist/index.mjs next to simple-sdp.mjs and simple-sdp.wasm.
3
+ import {parse as rawParse, write as rawWrite, validate as rawValidate, validateModel as rawValidateModel, VERSION}
4
+ from './simple-sdp.mjs';
5
+ import {SdpParseError} from '../../dist/index.mjs';
6
+
7
+ export {SdpParseError};
8
+
9
+ const parse = (sdp) => {
10
+ const result = JSON.parse(rawParse(sdp));
11
+ if (result.error) {
12
+ throw new SdpParseError(result.error.reason, result.error.lineNumber, result.error.line);
13
+ }
14
+ return result.sdp;
15
+ };
16
+
17
+ const isValid = (sdp, writeLog = false) => {
18
+ try {
19
+ parse(sdp);
20
+ return true;
21
+ } catch (e) {
22
+ if (writeLog) {
23
+ console.error(e);
24
+ }
25
+ return false;
26
+ }
27
+ };
28
+
29
+ const write = (sdp) => {
30
+ const result = JSON.parse(rawWrite(JSON.stringify(sdp)));
31
+ if (result.error) {
32
+ throw new Error(result.error.message);
33
+ }
34
+ return result.sdp;
35
+ };
36
+
37
+ const validate = (input, options = {}) => {
38
+ const type = options.type ?? null;
39
+ const result = JSON.parse(typeof input === 'string'
40
+ ? rawValidate(input, type)
41
+ : rawValidateModel(JSON.stringify(input), type));
42
+ if (result.error) {
43
+ throw new Error(result.error.message);
44
+ }
45
+ return result.issues;
46
+ };
47
+
48
+ export const sdpReader = {parse, isValid};
49
+ export const sdpWriter = {write};
50
+ export const sdpValidator = {validate};
51
+ export const version = VERSION.value;
@@ -0,0 +1,144 @@
1
+ async function instantiate(module, imports = {}) {
2
+ const { exports } = await WebAssembly.instantiate(module, imports);
3
+ const memory = exports.memory || imports.env.memory;
4
+ const adaptedExports = Object.setPrototypeOf({
5
+ VERSION: {
6
+ // assembly/index/VERSION: ~lib/string/String
7
+ valueOf() { return this.value; },
8
+ get value() {
9
+ return __liftString(exports.VERSION.value >>> 0);
10
+ }
11
+ },
12
+ isValid(sdp) {
13
+ // assembly/index/isValid(~lib/string/String) => bool
14
+ sdp = __lowerString(sdp) || __notnull();
15
+ return exports.isValid(sdp) != 0;
16
+ },
17
+ parse(sdp) {
18
+ // assembly/index/parse(~lib/string/String) => ~lib/string/String
19
+ sdp = __lowerString(sdp) || __notnull();
20
+ return __liftString(exports.parse(sdp) >>> 0);
21
+ },
22
+ write(model) {
23
+ // assembly/index/write(~lib/string/String) => ~lib/string/String
24
+ model = __lowerString(model) || __notnull();
25
+ return __liftString(exports.write(model) >>> 0);
26
+ },
27
+ validate(sdp, type) {
28
+ // assembly/index/validate(~lib/string/String, ~lib/string/String | null?) => ~lib/string/String
29
+ sdp = __retain(__lowerString(sdp) || __notnull());
30
+ type = __lowerString(type);
31
+ try {
32
+ exports.__setArgumentsLength(arguments.length);
33
+ return __liftString(exports.validate(sdp, type) >>> 0);
34
+ } finally {
35
+ __release(sdp);
36
+ }
37
+ },
38
+ validateModel(model, type) {
39
+ // assembly/index/validateModel(~lib/string/String, ~lib/string/String | null?) => ~lib/string/String
40
+ model = __retain(__lowerString(model) || __notnull());
41
+ type = __lowerString(type);
42
+ try {
43
+ exports.__setArgumentsLength(arguments.length);
44
+ return __liftString(exports.validateModel(model, type) >>> 0);
45
+ } finally {
46
+ __release(model);
47
+ }
48
+ },
49
+ sdp_alloc(len) {
50
+ // assembly/index/sdp_alloc(i32) => usize
51
+ return exports.sdp_alloc(len) >>> 0;
52
+ },
53
+ sdp_parse(ptr, len) {
54
+ // assembly/index/sdp_parse(usize, i32) => usize
55
+ return exports.sdp_parse(ptr, len) >>> 0;
56
+ },
57
+ sdp_write(ptr, len) {
58
+ // assembly/index/sdp_write(usize, i32) => usize
59
+ return exports.sdp_write(ptr, len) >>> 0;
60
+ },
61
+ sdp_validate(ptr, len, type) {
62
+ // assembly/index/sdp_validate(usize, i32, i32) => usize
63
+ return exports.sdp_validate(ptr, len, type) >>> 0;
64
+ },
65
+ sdp_validate_model(ptr, len, type) {
66
+ // assembly/index/sdp_validate_model(usize, i32, i32) => usize
67
+ return exports.sdp_validate_model(ptr, len, type) >>> 0;
68
+ },
69
+ sdp_version() {
70
+ // assembly/index/sdp_version() => usize
71
+ return exports.sdp_version() >>> 0;
72
+ },
73
+ }, exports);
74
+ function __liftString(pointer) {
75
+ if (!pointer) return null;
76
+ const
77
+ end = pointer + new Uint32Array(memory.buffer)[pointer - 4 >>> 2] >>> 1,
78
+ memoryU16 = new Uint16Array(memory.buffer);
79
+ let
80
+ start = pointer >>> 1,
81
+ string = "";
82
+ while (end - start > 1024) string += String.fromCharCode(...memoryU16.subarray(start, start += 1024));
83
+ return string + String.fromCharCode(...memoryU16.subarray(start, end));
84
+ }
85
+ function __lowerString(value) {
86
+ if (value == null) return 0;
87
+ const
88
+ length = value.length,
89
+ pointer = exports.__new(length << 1, 2) >>> 0,
90
+ memoryU16 = new Uint16Array(memory.buffer);
91
+ for (let i = 0; i < length; ++i) memoryU16[(pointer >>> 1) + i] = value.charCodeAt(i);
92
+ return pointer;
93
+ }
94
+ const refcounts = new Map();
95
+ function __retain(pointer) {
96
+ if (pointer) {
97
+ const refcount = refcounts.get(pointer);
98
+ if (refcount) refcounts.set(pointer, refcount + 1);
99
+ else refcounts.set(exports.__pin(pointer), 1);
100
+ }
101
+ return pointer;
102
+ }
103
+ function __release(pointer) {
104
+ if (pointer) {
105
+ const refcount = refcounts.get(pointer);
106
+ if (refcount === 1) exports.__unpin(pointer), refcounts.delete(pointer);
107
+ else if (refcount) refcounts.set(pointer, refcount - 1);
108
+ else throw Error(`invalid refcount '${refcount}' for reference '${pointer}'`);
109
+ }
110
+ }
111
+ function __notnull() {
112
+ throw TypeError("value must not be null");
113
+ }
114
+ return adaptedExports;
115
+ }
116
+ export const {
117
+ memory,
118
+ __new,
119
+ __pin,
120
+ __unpin,
121
+ __collect,
122
+ __rtti_base,
123
+ VERSION,
124
+ isValid,
125
+ parse,
126
+ write,
127
+ validate,
128
+ validateModel,
129
+ sdp_alloc,
130
+ sdp_free,
131
+ sdp_len,
132
+ sdp_parse,
133
+ sdp_write,
134
+ sdp_validate,
135
+ sdp_validate_model,
136
+ sdp_version,
137
+ } = await (async url => instantiate(
138
+ await (async () => {
139
+ const isNodeOrBun = typeof process != "undefined" && process.versions != null && (process.versions.node != null || process.versions.bun != null);
140
+ if (isNodeOrBun) { return globalThis.WebAssembly.compile(await (await import("node:fs/promises")).readFile(url)); }
141
+ else { return await globalThis.WebAssembly.compileStreaming(globalThis.fetch(url)); }
142
+ })(), {
143
+ }
144
+ ))(new URL("simple-sdp.wasm", import.meta.url));
Binary file