@irbisadm/simple-sdp 1.0.0 → 1.1.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.0] — unreleased
6
+ ## [1.1.0] — 2026-11-01
7
+
8
+ ### Added
9
+ - AssemblyScript port (`assembly/`): the parser, writer and validator compiled to a
10
+ WebAssembly module with no imports (`yarn asbuild`). Results are JSON with the shape of
11
+ the TypeScript model; a C-style interface on UTF-8 buffers (`sdp_parse`, `sdp_write`,
12
+ `sdp_validate`, `sdp_validate_model`, …) serves non-JS hosts. `yarn test:wasm` runs the
13
+ test suites against the module, plus a differential test against the TypeScript code.
14
+ - Swift package `SimpleSdp` for iOS 18+ / macOS 15+ (WasmKit), `Package.swift` in the root.
15
+ - Kotlin library `dev.irbisadm:simple-sdp` for Android and the JVM (Chicory; the module is
16
+ compiled to bytecode at build time), `platforms/kotlin`.
17
+
18
+ ### Fixed
19
+ - Attributes named like `Object.prototype` members (`a=constructor`, `a=toString`,
20
+ `a=__proto__`, …) failed to parse; an ssrc attribute with such a name was dropped, a
21
+ `__proto__` key of fmtp/ssrc/candidate records changed the object prototype, and
22
+ `a=fingerprint:constructor …` got a validator message with "function Object()".
23
+
24
+ ## [1.0.1] — 2026-11-01
25
+
26
+ ### Fixed
27
+ - Validator accepted malformed `a=fingerprint` values such as `sha-256 AA:BB`.
28
+ New issue `invalid-fingerprint` (error) checks that `a=fingerprint` is colon-separated
29
+ hex pairs with the digest length of its hash function (md2/md5 16, sha-1 20, sha-224 28,
30
+ sha-256 32, sha-384 48, sha-512 64 bytes); `fingerprint-not-uppercase` (warning).
31
+ RFC 8122 §5.
32
+ - `package.json`: `repository` in the normalized object form (no `npm publish` warning).
33
+
34
+ ## [1.0.0] — 2026-11-01
7
35
 
8
36
  The model of 0.2.0 is now the stable 1.x API. No breaking changes since 0.2.0.
9
37
 
@@ -15,7 +43,7 @@ The model of 0.2.0 is now the stable 1.x API. No breaking changes since 0.2.0.
15
43
  `SdpIssueCode` and `SdpValidateOptions` (rules: README, "Validation").
16
44
  - `package.json`: an `exports` map with `types` and `sideEffects: false`.
17
45
 
18
- ## [0.2.0] — unreleased
46
+ ## [0.2.0] — not published
19
47
 
20
48
  Parser rewrite: strict RFC 8866, a lossless model, typed WebRTC attributes. **Breaking.**
21
49
 
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},w='"<foundation> <component> <transport> <priority> <address> <port> typ <type> [raddr <addr>] [rport <port>] *(<ext-name> <ext-value>)"',$=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 ${w}`);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 ${w}`);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=$(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($)}})(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)}}},P=(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})},z={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},I=new Set(["e","p","b","a"]),F={m:0,i:1,c:2,b:3,k:4,a:5},O=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"},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?O:I,r=(l?F:z)[x];void 0===r&&b(x in z?`"${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!==z.t&&u!==z.r&&b(`"${x}=" must follow "t=" or "r="`):u>z.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":P(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!==(w=p.timing)&&void 0!==w?w:p.timing=[]).push(m);break;case"r":(null!==($=(y=m).repeat)&&void 0!==$?$: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":P(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},C={parse:N,isValid:(e,t=!1)=>{try{return N(e),!0}catch(e){return t&&console.error(e),!1}}},A=e=>`c=${e.netType} ${e.addrType} ${e.connectionAddress}`,S=e=>`b=${e.bwtype}:${e.bandwidth}`,M=e=>`k=${e.method}${void 0!==e.encryptionKey?`:${e.encryptionKey}`:""}`,U=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} ${U(e.parameters)}`]:[]]},V=e=>{const t=[...e.payloadTypes?[`pt=${e.payloadTypes.join(",")}`]:[],...e.restrictions&&Object.keys(e.restrictions).length?[U(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?[A(e.connectionInformation)]:[],...(null!==(t=e.bandwidths)&&void 0!==t?t:[]).map(S),...e.encryptionKey?[M(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?[A(e.connectionInformation)]:[],...(null!==(o=e.bandwidths)&&void 0!==o?o:[]).map(S),...e.timing.flatMap(q),...e.encryptionKey?[M(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"}},_={validate:(t,r={})=>{var i,s,n,o;let d;if("string"==typeof t)try{d=C.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)};return v(d.iceUfrag,d.icePwd),m.forEach(((e,t)=>{var i,s,n,o,c,m,u,h,g,w,$,y,b,x,k,E;const T=0!==e.port;if(v(e.iceUfrag,e.icePwd,t),f&&T&&(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)&&T){(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 j=new Set;for(const r of null!==(h=e.extmap)&&void 0!==h?h:[])(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),j.has(r.id)&&p("extmap-duplicate-id",`extmap id ${r.id} is used more than once`,t),j.add(r.id);for(const r of["send","recv"])for(const i of(null!==(w=null===(g=e.simulcast)||void 0===g?void 0:g[r])&&void 0!==w?w:[]).flat())(null===($=e.rids)||void 0===$?void 0:$.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!==(y=e.rids)&&void 0!==y?y:[])for(const i of null!==(b=r.payloadTypes)&&void 0!==b?b:[])(null===(x=e.codecs)||void 0===x?void 0:x.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!==(k=e.ssrcGroups)&&void 0!==k?k:[])for(const i of r.ssrcs)(null===(E=e.ssrcs)||void 0===E?void 0:E.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}},H="1.0.0";export{e as SdpParseError,C as sdpReader,_ as sdpValidator,Z as sdpWriter,H 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.1.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.0",
3
+ "version": "1.1.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": {
@@ -9,7 +9,10 @@
9
9
  "url": "https://www.linkedin.com/in/irbisadm/"
10
10
  },
11
11
  "private": false,
12
- "repository": "https://github.com/irbisadm/simple-sdp",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/irbisadm/simple-sdp.git"
15
+ },
13
16
  "bugs": "https://github.com/irbisadm/simple-sdp/issues",
14
17
  "module": "./dist/index.mjs",
15
18
  "main": "./dist/index.mjs",
@@ -48,6 +51,7 @@
48
51
  "@rollup/plugin-typescript": "^11.0.0",
49
52
  "@types/jest": "^29.5.0",
50
53
  "@types/node": "^18.14.0",
54
+ "assemblyscript": "0.28.20",
51
55
  "jest": "^29.5.0",
52
56
  "rollup": "^3.17.1",
53
57
  "rollup-plugin-dts": "^5.2.0",
@@ -61,7 +65,9 @@
61
65
  "scripts": {
62
66
  "build": "rollup -c",
63
67
  "test": "jest",
64
- "typecheck": "tsc --noEmit"
65
- },
66
- "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
68
+ "typecheck": "tsc --noEmit",
69
+ "asbuild": "asc --config asconfig.json --target release && node scripts/sync-wasm.mjs",
70
+ "asbuild:debug": "asc --config asconfig.json --target debug",
71
+ "test:wasm": "jest -c jest.wasm.config.js"
72
+ }
67
73
  }
package/readme.md CHANGED
@@ -16,7 +16,8 @@ TypeScript, no runtime dependencies, works in Node.js and browsers.
16
16
  - **Lossless model**: each line maps to a typed field or, for attributes the library
17
17
  does not interpret, to an ordered `attributes` entry with the exact original value.
18
18
  - **`sdpWriter.write`**: serializes the model back to RFC 8866 text (CRLF).
19
- `write(parse(x))` keeps every line of `x` (see [Writing](#writing)).
19
+ `write(parse(x))` keeps every line of `x`; attribute order and time units are
20
+ normalized (see [Writing](#writing)).
20
21
  - **`sdpValidator.validate`**: reports RFC and WebRTC rule violations as a list of issues.
21
22
  - **Typed WebRTC attributes**: ICE, DTLS, BUNDLE, codecs, simulcast, SSRCs (see below).
22
23
 
@@ -144,6 +145,8 @@ are covered as well.
144
145
  | `unknown-payload-type` | warning | `rtpmap`/`fmtp`/`rtcp-fb` for a payload type not in `m=` |
145
146
  | `missing-fingerprint`, `missing-setup` | error | DTLS transports (`UDP/TLS/RTP/SAVPF`, `UDP/DTLS/SCTP`, …) (RFC 8842 §5.2, §5.3) |
146
147
  | `setup-actpass-in-answer` | error | with `type: 'answer'` (RFC 5763 §5) |
148
+ | `invalid-fingerprint` | error | colon-separated hex pairs; digest length matches the hash (`sha-256` → 32 bytes, …) (RFC 8122 §5) |
149
+ | `fingerprint-not-uppercase` | warning | hex digits must be upper case (RFC 8122 §5) |
147
150
  | `missing-ice-ufrag`, `missing-ice-pwd` | error | if ICE is used, every active media has credentials (RFC 8839 §5.4) |
148
151
  | `ice-ufrag-length`, `ice-pwd-length` | error | 4–256 / 22–256 characters (RFC 8839 §5.4) |
149
152
  | `extmap-invalid-id`, `extmap-duplicate-id` | error | ids 1–14 or 16–255, unique per section (RFC 8285) |
@@ -183,6 +186,72 @@ Every fixture parses, round-trips through the writer and validates without issue
183
186
 
184
187
  Safari is not covered by fixtures yet.
185
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 | Package | Runtime |
203
+ |---|---|---|
204
+ | iOS 18+, macOS 15+ | Swift package `SimpleSdp` ([`Package.swift`](Package.swift), [`platforms/apple`](platforms/apple)) | [WasmKit](https://github.com/swiftwasm/WasmKit) interpreter |
205
+ | Android, JVM 11+ | Kotlin library `dev.irbisadm:simple-sdp` ([`platforms/kotlin`](platforms/kotlin)) | [Chicory](https://chicory.dev), compiled to bytecode at build time |
206
+ | Browser, Node.js, other wasm hosts | `wasm/build/simple-sdp.wasm` (`yarn asbuild`) | any |
207
+
208
+ The model crosses the wasm boundary as JSON with the shape of the TypeScript `Sdp` interface.
209
+
210
+ ```swift
211
+ let sdp = try SimpleSdp()
212
+ let json = try sdp.parseJSON(offer) // Data: the model, key order kept
213
+ let issues = try sdp.validate(json: json, type: .offer)
214
+ let text = try sdp.write(json: json)
215
+ let model = try sdp.parse(offer) // [String: Any] (does not keep key order)
216
+ ```
217
+
218
+ ```kotlin
219
+ val sdp = SimpleSdp()
220
+ val model = sdp.parse(offer) // kotlinx.serialization JsonObject
221
+ val issues = sdp.validate(model, SdpType.OFFER)
222
+ val text = sdp.write(model)
223
+ ```
224
+
225
+ ### Module interface
226
+
227
+ The module exports a C-style interface on UTF-8 buffers for hosts without AssemblyScript
228
+ bindings:
229
+
230
+ | Export | |
231
+ |---|---|
232
+ | `sdp_alloc(len) → ptr` | allocate an input buffer; write the UTF-8 bytes at `ptr` |
233
+ | `sdp_parse(ptr, len) → res` | `{"sdp": model}` or `{"error": {message, reason, lineNumber, line}}` |
234
+ | `sdp_write(ptr, len) → res` | model JSON in; `{"sdp": "text"}` or `{"error": {message}}` |
235
+ | `sdp_validate(ptr, len, type) → res` | SDP text in, `type` 0 = none, 1 = offer, 2 = answer; `{"issues": [...]}` |
236
+ | `sdp_validate_model(ptr, len, type) → res` | model JSON in; `{"issues": [...]}` or `{"error": {message}}` |
237
+ | `sdp_version() → res` | version string |
238
+ | `sdp_len(res) → len` | byte length of a buffer |
239
+ | `sdp_free(ptr)` | release an input buffer or a result |
240
+
241
+ A module instance is single-threaded; the Swift and Kotlin wrappers serialize calls.
242
+ The module uses the minimal AssemblyScript runtime: memory is reclaimed at the end of every
243
+ call, so a result stays valid until the next call on the same instance.
244
+
245
+ Approximate time to parse / write an 18 KB Chrome offer on x86-64, JSON transfer included:
246
+ wasm in Node.js 1.3 / 1.2 ms (the TypeScript build: 0.35 / 0.25 ms), JVM with the compiled
247
+ module 6 / 9 ms, WasmKit (release build) ≈10 ms each.
248
+
249
+ JavaScript hosts can use the generated ESM bindings (`wasm/build/simple-sdp.js`) with
250
+ string arguments: `parse(text)`, `write(json)`, `validate(text, type)`, `validateModel(json, type)`.
251
+
252
+ Build: `yarn asbuild` compiles the module and copies it into `platforms/` (the copies are
253
+ committed so that SwiftPM and Gradle builds do not need Node.js).
254
+
186
255
  ## License
187
256
 
188
257
  MIT
package/types/index.d.ts CHANGED
@@ -299,7 +299,7 @@ declare const sdpWriter: {
299
299
  write: (sdp: Sdp) => string;
300
300
  };
301
301
 
302
- type SdpIssueCode = 'parse-error' | 'invalid-version' | 'missing-origin' | 'missing-session-name' | 'empty-session-name' | 'missing-timing' | 'missing-connection' | 'duplicate-mid' | 'bundle-missing-mid' | 'group-unknown-mid' | 'missing-rtpmap' | 'unknown-payload-type' | 'missing-fingerprint' | 'missing-setup' | 'setup-actpass-in-answer' | 'missing-ice-ufrag' | 'missing-ice-pwd' | 'ice-ufrag-length' | 'ice-pwd-length' | 'extmap-invalid-id' | 'extmap-duplicate-id' | 'simulcast-unknown-rid' | 'rid-unknown-payload-type' | 'ssrc-group-unknown-ssrc';
302
+ type SdpIssueCode = 'parse-error' | 'invalid-version' | 'missing-origin' | 'missing-session-name' | 'empty-session-name' | 'missing-timing' | 'missing-connection' | 'duplicate-mid' | 'bundle-missing-mid' | 'group-unknown-mid' | 'missing-rtpmap' | 'unknown-payload-type' | 'missing-fingerprint' | 'invalid-fingerprint' | 'fingerprint-not-uppercase' | 'missing-setup' | 'setup-actpass-in-answer' | 'missing-ice-ufrag' | 'missing-ice-pwd' | 'ice-ufrag-length' | 'ice-pwd-length' | 'extmap-invalid-id' | 'extmap-duplicate-id' | 'simulcast-unknown-rid' | 'rid-unknown-payload-type' | 'ssrc-group-unknown-ssrc';
303
303
  interface SdpIssue {
304
304
  severity: 'error' | 'warning';
305
305
  code: SdpIssueCode;
@@ -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.0";
335
+ declare const version = "1.1.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 };