Ctrl K
ComunidadGuías…

sdpfrag/sdpfrag.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Package sdpfrag is an incomplete implementation of RFC 8840 Section 9
package sdpfrag

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"

	"github.com/pion/sdp/v3"
	"github.com/pion/webrtc/v4"
)

// An SDPFrag contains the ICE-related subset of a session description.
type SDPFrag struct {
	UsernameFragment, Password string
	Candidates                 []webrtc.ICECandidateInit
	MediaDescriptions          []MediaDescription
}

type MediaDescription struct {
	MLine                      string
	Mid                        string
	UsernameFragment, Password string
	Candidates                 []webrtc.ICECandidateInit
}

func (s *SDPFrag) Unmarshal(value []byte) error {
	scanner := bufio.NewScanner(bytes.NewReader(value))

	var mediaDescription *MediaDescription

	for scanner.Scan() {
		l := scanner.Bytes()
		if bytes.HasPrefix(l, []byte("a=ice-ufrag:")) {
			ufrag := string(l[len("a=ice-ufrag:"):])
			if mediaDescription == nil {
				s.UsernameFragment = ufrag
			} else {
				mediaDescription.UsernameFragment = ufrag
			}
		} else if bytes.HasPrefix(l, []byte("a=ice-pwd:")) {
			pwd := string(l[len("a=ice-pwd:"):])
			if mediaDescription == nil {
				s.Password = pwd
			} else {
				mediaDescription.Password = pwd
			}
		} else if bytes.HasPrefix(l, []byte("m=")) {
			if mediaDescription != nil {
				s.MediaDescriptions = append(
					s.MediaDescriptions,
					*mediaDescription,
				)
			}
			mediaDescription = &MediaDescription{
				MLine: string(l[len("m="):]),
			}
		} else if bytes.HasPrefix(l, []byte("a=mid:")) {
			if mediaDescription == nil {
				return errors.New("unexpected mid")
			}
			mediaDescription.Mid = string(l[len("a=mid:"):])
		} else if bytes.HasPrefix(l, []byte("a=candidate:")) {
			init := webrtc.ICECandidateInit{
				Candidate: string(l[len("a=candidate:"):]),
			}
			if len(s.UsernameFragment) > 0 {
				ufrag := s.UsernameFragment
				init.UsernameFragment = &ufrag
			}
			if mediaDescription != nil {
				i := uint16(len(s.MediaDescriptions))
				init.SDPMLineIndex = &i
				m := mediaDescription.Mid
				init.SDPMid = &m
				mediaDescription.Candidates = append(
					mediaDescription.Candidates,
					init,
				)
			} else {
				s.Candidates = append(s.Candidates, init)
			}
		}
	}
	if mediaDescription != nil {
		s.MediaDescriptions = append(
			s.MediaDescriptions,
			*mediaDescription,
		)
	}
	return nil
}

func (s *SDPFrag) Marshal() ([]byte, error) {
	w := new(bytes.Buffer)

	if s.UsernameFragment != "" {
		fmt.Fprintf(w, "a=ice-ufrag:%v\r\n", s.UsernameFragment)
	}
	if s.Password != "" {
		fmt.Fprintf(w, "a=ice-pwd:%v\r\n", s.Password)
	}
	for _, c := range s.Candidates {
		fmt.Fprintf(w, "a=%v\r\n", c.Candidate)
	}
	for _, m := range s.MediaDescriptions {
		fmt.Fprintf(w, "m=%v\r\n", m.MLine)
		fmt.Fprintf(w, "a=mid:%v\r\n", m.Mid)
		if m.UsernameFragment != "" {
			fmt.Fprintf(w, "a=ice-ufrag:%v\r\n", m.UsernameFragment)
		}
		if m.Password != "" {
			fmt.Fprintf(w, "a=ice-pwd:%v\r\n", m.Password)
		}
		for _, c := range m.Candidates {
			fmt.Fprintf(w, "a=candidate:%v\r\n", c.Candidate)
		}
	}

	return w.Bytes(), nil
}

func patch(ufrag, pwd string, old []sdp.Attribute, cs []webrtc.ICECandidateInit, override bool) []sdp.Attribute {
	var as []sdp.Attribute
	for _, a := range old {
		if a.Key == "ice-ufrag" {
			if ufrag == "" {
				continue
			}
			as = append(as, sdp.Attribute{
				Key:   "ice-ufrag",
				Value: ufrag,
			})
		} else if a.Key == "ice-pwd" {
			if pwd == "" {
				continue
			}
			as = append(as, sdp.Attribute{
				Key:   "ice-pwd",
				Value: pwd,
			})
		} else if !override || a.Key != "candidate" {
			as = append(as, a)
		}
	}
	for _, c := range cs {
		as = append(as, sdp.Attribute{
			Key:   "candidate",
			Value: c.Candidate,
		})
	}
	return as
}

// SDPUFragPwd returns the ICE ufrag and password of an SDP.
// In principle, the ufrag/pwd should be at the same place in the
// frag as in the original SDP, RFC 8840 Section 9.  However, we
// only do bundle, and some implementations put the data at random
// places, so we walk the whole structure to find the ufrag/pwd pair.
func SDPUFragPwd(s sdp.SessionDescription) (string, string) {
	ufrag, _ := s.Attribute("ice-ufrag")
	pwd, _ := s.Attribute("ice-pwd")
	if ufrag != "" {
		return ufrag, pwd
	}
	for _, m := range s.MediaDescriptions {
		ufrag, _ := m.Attribute("ice-ufrag")
		pwd, _ := m.Attribute("ice-pwd")
		if ufrag != "" {
			return ufrag, pwd
		}
	}
	return "", ""
}

// UFragPwd returns the ICE ufrag and password of a fragment.
// See the description of [SDPUFragPwd] for details.
func (f *SDPFrag) UFragPwd() (string, string) {
	ufrag := f.UsernameFragment
	pwd := f.Password
	if ufrag != "" {
		return ufrag, pwd
	}
	for _, m := range f.MediaDescriptions {
		ufrag := m.UsernameFragment
		pwd := m.Password
		if ufrag != "" {
			return ufrag, pwd
		}
	}
	return "", ""
}

func (f *SDPFrag) AllCandidates() []webrtc.ICECandidateInit {
	var cs []webrtc.ICECandidateInit
	cs = append(cs, f.Candidates...)
	for _, m := range f.MediaDescriptions {
		cs = append(cs, m.Candidates...)
	}
	return cs
}

// PatchSDP applies an [SDPFrag] to an [sdp.SessionDescription],
// either adding or replacing candidates depending on the ufrag/password
// values.
func PatchSDP(s sdp.SessionDescription, f SDPFrag) (sdp.SessionDescription, bool) {
	ufrag, pwd := SDPUFragPwd(s)
	ufrag1, pwd1 := f.UFragPwd()

	override := (ufrag != ufrag1) || (pwd != pwd1)

	s.Attributes = patch(ufrag1, pwd1, s.Attributes, f.Candidates, override)

	old := s.MediaDescriptions
	s.MediaDescriptions = nil
	for i, m := range old {
		var mm MediaDescription
		if i < len(f.MediaDescriptions) {
			mm = f.MediaDescriptions[i]
		}
		m.Attributes = patch(
			ufrag1, pwd1, m.Attributes, mm.Candidates, override,
		)
		s.MediaDescriptions = append(s.MediaDescriptions, m)
	}
	return s, override
}

func getCandidates(as []sdp.Attribute, mid *string, index *uint16, ufrag string) []webrtc.ICECandidateInit {
	var cs []webrtc.ICECandidateInit
	for _, a := range as {
		if a.Key != "candidate" {
			continue
		}
		c := webrtc.ICECandidateInit{
			Candidate:     a.Value,
			SDPMid:        mid,
			SDPMLineIndex: index,
		}
		if ufrag != "" {
			c.UsernameFragment = &ufrag
		}
		cs = append(cs, c)
	}
	return cs

}

// FromSDP extracts the ICE information from an [sdp.SessionDescription]
func FromSDP(s sdp.SessionDescription) SDPFrag {
	var f SDPFrag
	ufrag, _ := s.Attribute("ice-ufrag")
	f.UsernameFragment = ufrag
	pwd, _ := s.Attribute("ice-pwd")
	f.Password = pwd
	f.Candidates = getCandidates(s.Attributes, nil, nil, ufrag)

	for index, m := range s.MediaDescriptions {
		var mm MediaDescription
		mm.MLine = m.MediaName.String()
		mid, _ := m.Attribute("mid")
		mm.Mid = mid
		ufrag, _ := m.Attribute("ice-ufrag")
		mm.UsernameFragment = ufrag
		pwd, _ := m.Attribute("ice-pwd")
		mm.Password = pwd
		i := uint16(index)
		mm.Candidates = getCandidates(
			m.Attributes, &mid, &i, ufrag,
		)
		f.MediaDescriptions = append(f.MediaDescriptions, mm)
	}
	return f
}