telegabber/xmpp/jingle/sdp.go
2026-05-25 06:27:20 -07:00

141 lines
2.5 KiB
Go

package jingle
import (
"fmt"
"strings"
)
const sdpLineDivider = "\r\n"
// one SDP "a=" attribute line
type kv struct {
k, v string
}
// one m= block + its connection line and ordered attrs
type mediaSection struct {
media string
port string
protocol string
formats []string
connection string
attrs []kv
}
type sdpDoc struct {
sessionAttrs []kv
media []mediaSection
}
// preserves attribute order (SDP repeats keys; order matters for
// rtpmap/candidate)
func parseSDP(s string) (*sdpDoc, error) {
doc := &sdpDoc{}
var cur *mediaSection
for _, raw := range strings.Split(s, "\n") {
line := strings.TrimRight(raw, "\r")
if len(line) < 2 || line[1] != '=' {
continue
}
key := line[0]
value := line[2:]
switch key {
case 'm':
if cur != nil {
doc.media = append(doc.media, *cur)
}
parts := strings.Split(value, " ")
if len(parts) < 3 {
return nil, fmt.Errorf("malformed m= line: %q", value)
}
ms := mediaSection{
media: parts[0],
port: parts[1],
protocol: parts[2],
}
if len(parts) > 3 {
ms.formats = parts[3:]
}
cur = &ms
case 'c':
if cur != nil {
cur.connection = value
}
case 'a':
k, v := splitAttr(value)
if cur == nil {
doc.sessionAttrs = append(doc.sessionAttrs, kv{k, v})
} else {
cur.attrs = append(cur.attrs, kv{k, v})
}
}
}
if cur != nil {
doc.media = append(doc.media, *cur)
}
return doc, nil
}
// value is everything after the first colon; later colons are preserved
func splitAttr(body string) (string, string) {
if i := strings.IndexByte(body, ':'); i >= 0 {
return body[:i], body[i+1:]
}
return body, ""
}
func mmGetAll(mm []kv, key string) []string {
var out []string
for _, p := range mm {
if p.k == key {
out = append(out, p.v)
}
}
return out
}
func mmFirst(mm []kv, key, def string) string {
for _, p := range mm {
if p.k == key {
return p.v
}
}
return def
}
func mmHas(mm []kv, key string) bool {
for _, p := range mm {
if p.k == key {
return true
}
}
return false
}
type sdpBuilder struct {
lines []string
}
func (b *sdpBuilder) line(s string) {
b.lines = append(b.lines, s)
}
// empty value emits "a=key" (no colon)
func (b *sdpBuilder) attr(k, v string) {
if v == "" {
b.lines = append(b.lines, "a="+k)
} else {
b.lines = append(b.lines, "a="+k+":"+v)
}
}
func (b *sdpBuilder) appendAttrs(mm []kv) {
for _, p := range mm {
b.attr(p.k, p.v)
}
}
func (b *sdpBuilder) String() string {
return strings.Join(b.lines, sdpLineDivider) + sdpLineDivider
}