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

167 lines
4 KiB
Go

package jingle
import (
"errors"
"fmt"
"strings"
)
func appendTransportAttrs(t *Transport, attrs *[]kv) error {
if t == nil {
return errors.New("content missing transport")
}
if t.Ufrag == "" {
return errors.New("transport missing ufrag")
}
if t.Pwd == "" {
return errors.New("transport missing pwd")
}
*attrs = append(*attrs, kv{"ice-ufrag", t.Ufrag})
*attrs = append(*attrs, kv{"ice-pwd", t.Pwd})
var iceOptions []string
if t.Trickle != nil {
iceOptions = append(iceOptions, "trickle")
}
if t.Renomination != nil {
iceOptions = append(iceOptions, "renomination")
}
if len(iceOptions) == 0 {
iceOptions = defaultICEOptions
}
*attrs = append(*attrs, kv{"ice-options", strings.Join(iceOptions, " ")})
fp := t.Fingerprint
if fp == nil {
return errors.New("transport missing DTLS fingerprint")
}
if fp.Hash == "" || fp.Text == "" {
return errors.New("fingerprint missing hash or body")
}
if strings.ContainsAny(fp.Hash, " \t\r\n") {
return fmt.Errorf("fingerprint hash contains whitespace: %q", fp.Hash)
}
*attrs = append(*attrs, kv{"fingerprint", fp.Hash + " " + fp.Text})
if fp.Setup != "" {
*attrs = append(*attrs, kv{"setup", fp.Setup})
}
return nil
}
// falls back to session-level fingerprint/setup when the media block omits
// them (matches Conversations SessionDescription)
func buildJingleTransport(m *mediaSection, sessAttrs []kv) (*Transport, error) {
t := &Transport{
Ufrag: mmFirst(m.attrs, "ice-ufrag", ""),
Pwd: mmFirst(m.attrs, "ice-pwd", ""),
}
fpRaw := mmFirst(m.attrs, "fingerprint", mmFirst(sessAttrs, "fingerprint", ""))
setupVal := mmFirst(m.attrs, "setup", mmFirst(sessAttrs, "setup", ""))
if fpRaw != "" && setupVal != "" {
sp := strings.SplitN(fpRaw, " ", 2)
if len(sp) >= 2 {
t.Fingerprint = &Fingerprint{
Hash: sp[0],
Setup: setupVal,
Text: sp[1],
}
}
}
if iceOptions := mmFirst(m.attrs, "ice-options", ""); iceOptions != "" {
for _, opt := range strings.Fields(iceOptions) {
if !wellKnownICEOptions[opt] {
continue
}
switch opt {
case "trickle":
t.Trickle = &ICEOptionTrickle{}
case "renomination":
t.Renomination = &ICEOptionRenomination{}
}
}
}
for _, val := range mmGetAll(m.attrs, "candidate") {
cand, err := candidateFromSDP(val)
if err != nil {
return nil, err
}
if cand != nil {
t.Candidates = append(t.Candidates, *cand)
}
}
return t, nil
}
func candidateToSDP(c *Candidate) (string, error) {
for _, kv := range []struct{ name, val string }{
{"foundation", c.Foundation},
{"component", c.Component},
{"protocol", c.Protocol},
{"priority", c.Priority},
{"ip", c.IP},
{"port", c.Port},
} {
if kv.val == "" {
return "", fmt.Errorf("candidate missing %s", kv.name)
}
}
proto := strings.ToLower(c.Protocol)
if proto != "udp" {
return "", fmt.Errorf("'%s' is not a supported protocol", proto)
}
var extra []string
if c.Type != "" {
extra = append(extra, "typ", c.Type)
}
if c.RelAddr != "" {
extra = append(extra, "raddr", c.RelAddr)
}
if c.RelPort != "" {
extra = append(extra, "rport", c.RelPort)
}
if c.Generation != "" {
extra = append(extra, "generation", c.Generation)
}
line := c.Foundation + " " + c.Component + " " + proto + " " + c.Priority + " " + c.IP + " " + c.Port
if len(extra) > 0 {
line += " " + strings.Join(extra, " ")
}
return line, nil
}
func candidateFromSDP(value string) (*Candidate, error) {
parts := strings.Fields(value)
if len(parts) < 6 {
return nil, nil
}
cand := &Candidate{
Foundation: parts[0],
Component: parts[1],
Protocol: strings.ToLower(parts[2]),
Priority: parts[3],
IP: parts[4],
Port: parts[5],
}
if cand.Protocol != "udp" {
return nil, fmt.Errorf("'%s' is not a supported protocol", cand.Protocol)
}
extra := parts[6:]
for i := 0; i+1 < len(extra); i += 2 {
k := extra[i]
v := extra[i+1]
switch k {
case "typ":
cand.Type = v
case "raddr":
cand.RelAddr = v
case "rport":
cand.RelPort = v
case "generation":
cand.Generation = v
}
}
return cand, nil
}