1// +build !js
2
3package webrtc
4
5import (
6	"fmt"
7	"net/url"
8	"regexp"
9	"strconv"
10	"strings"
11
12	"github.com/pion/ice/v2"
13	"github.com/pion/logging"
14	"github.com/pion/sdp/v3"
15)
16
17// trackDetails represents any media source that can be represented in a SDP
18// This isn't keyed by SSRC because it also needs to support rid based sources
19type trackDetails struct {
20	mid      string
21	kind     RTPCodecType
22	streamID string
23	id       string
24	ssrc     SSRC
25	rids     []string
26}
27
28func trackDetailsForSSRC(trackDetails []trackDetails, ssrc SSRC) *trackDetails {
29	for i := range trackDetails {
30		if trackDetails[i].ssrc == ssrc {
31			return &trackDetails[i]
32		}
33	}
34	return nil
35}
36
37func filterTrackWithSSRC(incomingTracks []trackDetails, ssrc SSRC) []trackDetails {
38	filtered := []trackDetails{}
39	for i := range incomingTracks {
40		if incomingTracks[i].ssrc != ssrc {
41			filtered = append(filtered, incomingTracks[i])
42		}
43	}
44	return filtered
45}
46
47// extract all trackDetails from an SDP.
48func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) []trackDetails { // nolint:gocognit
49	incomingTracks := []trackDetails{}
50	rtxRepairFlows := map[uint32]bool{}
51
52	for _, media := range s.MediaDescriptions {
53		// Plan B can have multiple tracks in a signle media section
54		streamID := ""
55		trackID := ""
56
57		// If media section is recvonly or inactive skip
58		if _, ok := media.Attribute(sdp.AttrKeyRecvOnly); ok {
59			continue
60		} else if _, ok := media.Attribute(sdp.AttrKeyInactive); ok {
61			continue
62		}
63
64		midValue := getMidValue(media)
65		if midValue == "" {
66			continue
67		}
68
69		codecType := NewRTPCodecType(media.MediaName.Media)
70		if codecType == 0 {
71			continue
72		}
73
74		for _, attr := range media.Attributes {
75			switch attr.Key {
76			case sdp.AttrKeySSRCGroup:
77				split := strings.Split(attr.Value, " ")
78				if split[0] == sdp.SemanticTokenFlowIdentification {
79					// Add rtx ssrcs to blacklist, to avoid adding them as tracks
80					// Essentially lines like `a=ssrc-group:FID 2231627014 632943048` are processed by this section
81					// as this declares that the second SSRC (632943048) is a rtx repair flow (RFC4588) for the first
82					// (2231627014) as specified in RFC5576
83					if len(split) == 3 {
84						_, err := strconv.ParseUint(split[1], 10, 32)
85						if err != nil {
86							log.Warnf("Failed to parse SSRC: %v", err)
87							continue
88						}
89						rtxRepairFlow, err := strconv.ParseUint(split[2], 10, 32)
90						if err != nil {
91							log.Warnf("Failed to parse SSRC: %v", err)
92							continue
93						}
94						rtxRepairFlows[uint32(rtxRepairFlow)] = true
95						incomingTracks = filterTrackWithSSRC(incomingTracks, SSRC(rtxRepairFlow)) // Remove if rtx was added as track before
96					}
97				}
98
99			// Handle `a=msid:<stream_id> <track_label>` for Unified plan. The first value is the same as MediaStream.id
100			// in the browser and can be used to figure out which tracks belong to the same stream. The browser should
101			// figure this out automatically when an ontrack event is emitted on RTCPeerConnection.
102			case sdp.AttrKeyMsid:
103				split := strings.Split(attr.Value, " ")
104				if len(split) == 2 {
105					streamID = split[0]
106					trackID = split[1]
107				}
108
109			case sdp.AttrKeySSRC:
110				split := strings.Split(attr.Value, " ")
111				ssrc, err := strconv.ParseUint(split[0], 10, 32)
112				if err != nil {
113					log.Warnf("Failed to parse SSRC: %v", err)
114					continue
115				}
116
117				if rtxRepairFlow := rtxRepairFlows[uint32(ssrc)]; rtxRepairFlow {
118					continue // This ssrc is a RTX repair flow, ignore
119				}
120
121				if len(split) == 3 && strings.HasPrefix(split[1], "msid:") {
122					streamID = split[1][len("msid:"):]
123					trackID = split[2]
124				}
125
126				isNewTrack := true
127				trackDetails := &trackDetails{}
128				for i := range incomingTracks {
129					if incomingTracks[i].ssrc == SSRC(ssrc) {
130						trackDetails = &incomingTracks[i]
131						isNewTrack = false
132					}
133				}
134
135				trackDetails.mid = midValue
136				trackDetails.kind = codecType
137				trackDetails.streamID = streamID
138				trackDetails.id = trackID
139				trackDetails.ssrc = SSRC(ssrc)
140
141				if isNewTrack {
142					incomingTracks = append(incomingTracks, *trackDetails)
143				}
144			}
145		}
146
147		if rids := getRids(media); len(rids) != 0 && trackID != "" && streamID != "" {
148			newTrack := trackDetails{
149				mid:      midValue,
150				kind:     codecType,
151				streamID: streamID,
152				id:       trackID,
153				rids:     []string{},
154			}
155			for rid := range rids {
156				newTrack.rids = append(newTrack.rids, rid)
157			}
158
159			incomingTracks = append(incomingTracks, newTrack)
160		}
161	}
162	return incomingTracks
163}
164
165func getRids(media *sdp.MediaDescription) map[string]string {
166	rids := map[string]string{}
167	for _, attr := range media.Attributes {
168		if attr.Key == "rid" {
169			split := strings.Split(attr.Value, " ")
170			rids[split[0]] = attr.Value
171		}
172	}
173	return rids
174}
175
176func addCandidatesToMediaDescriptions(candidates []ICECandidate, m *sdp.MediaDescription, iceGatheringState ICEGatheringState) error {
177	appendCandidateIfNew := func(c ice.Candidate, attributes []sdp.Attribute) {
178		marshaled := c.Marshal()
179		for _, a := range attributes {
180			if marshaled == a.Value {
181				return
182			}
183		}
184
185		m.WithValueAttribute("candidate", marshaled)
186	}
187
188	for _, c := range candidates {
189		candidate, err := c.toICE()
190		if err != nil {
191			return err
192		}
193
194		candidate.SetComponent(1)
195		appendCandidateIfNew(candidate, m.Attributes)
196
197		candidate.SetComponent(2)
198		appendCandidateIfNew(candidate, m.Attributes)
199	}
200
201	if iceGatheringState != ICEGatheringStateComplete {
202		return nil
203	}
204	for _, a := range m.Attributes {
205		if a.Key == "end-of-candidates" {
206			return nil
207		}
208	}
209
210	m.WithPropertyAttribute("end-of-candidates")
211	return nil
212}
213
214func addDataMediaSection(d *sdp.SessionDescription, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState) error {
215	media := (&sdp.MediaDescription{
216		MediaName: sdp.MediaName{
217			Media:   mediaSectionApplication,
218			Port:    sdp.RangedPort{Value: 9},
219			Protos:  []string{"UDP", "DTLS", "SCTP"},
220			Formats: []string{"webrtc-datachannel"},
221		},
222		ConnectionInformation: &sdp.ConnectionInformation{
223			NetworkType: "IN",
224			AddressType: "IP4",
225			Address: &sdp.Address{
226				Address: "0.0.0.0",
227			},
228		},
229	}).
230		WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()).
231		WithValueAttribute(sdp.AttrKeyMID, midValue).
232		WithPropertyAttribute(RTPTransceiverDirectionSendrecv.String()).
233		WithPropertyAttribute("sctp-port:5000").
234		WithICECredentials(iceParams.UsernameFragment, iceParams.Password)
235
236	for _, f := range dtlsFingerprints {
237		media = media.WithFingerprint(f.Algorithm, strings.ToUpper(f.Value))
238	}
239
240	if shouldAddCandidates {
241		if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil {
242			return err
243		}
244	}
245
246	d.WithMedia(media)
247	return nil
248}
249
250func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGatherer, iceGatheringState ICEGatheringState) *SessionDescription {
251	if sessionDescription == nil || i == nil {
252		return sessionDescription
253	}
254
255	candidates, err := i.GetLocalCandidates()
256	if err != nil {
257		return sessionDescription
258	}
259
260	parsed := sessionDescription.parsed
261	if len(parsed.MediaDescriptions) > 0 {
262		m := parsed.MediaDescriptions[0]
263		if err = addCandidatesToMediaDescriptions(candidates, m, iceGatheringState); err != nil {
264			return sessionDescription
265		}
266	}
267
268	sdp, err := parsed.Marshal()
269	if err != nil {
270		return sessionDescription
271	}
272
273	return &SessionDescription{
274		SDP:  string(sdp),
275		Type: sessionDescription.Type,
276	}
277}
278
279func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, mediaEngine *MediaEngine, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState, mediaSection mediaSection) (bool, error) {
280	transceivers := mediaSection.transceivers
281	if len(transceivers) < 1 {
282		return false, errSDPZeroTransceivers
283	}
284	// Use the first transceiver to generate the section attributes
285	t := transceivers[0]
286	media := sdp.NewJSEPMediaDescription(t.kind.String(), []string{}).
287		WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()).
288		WithValueAttribute(sdp.AttrKeyMID, midValue).
289		WithICECredentials(iceParams.UsernameFragment, iceParams.Password).
290		WithPropertyAttribute(sdp.AttrKeyRTCPMux).
291		WithPropertyAttribute(sdp.AttrKeyRTCPRsize)
292
293	codecs := mediaEngine.getCodecsByKind(t.kind)
294	for _, codec := range codecs {
295		name := strings.TrimPrefix(codec.MimeType, "audio/")
296		name = strings.TrimPrefix(name, "video/")
297		media.WithCodec(uint8(codec.PayloadType), name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine)
298
299		for _, feedback := range codec.RTPCodecCapability.RTCPFeedback {
300			media.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", codec.PayloadType, feedback.Type, feedback.Parameter))
301		}
302	}
303	if len(codecs) == 0 {
304		// Explicitly reject track if we don't have the codec
305		d.WithMedia(&sdp.MediaDescription{
306			MediaName: sdp.MediaName{
307				Media:   t.kind.String(),
308				Port:    sdp.RangedPort{Value: 0},
309				Protos:  []string{"UDP", "TLS", "RTP", "SAVPF"},
310				Formats: []string{"0"},
311			},
312		})
313		return false, nil
314	}
315
316	directions := []RTPTransceiverDirection{}
317	if t.Sender() != nil {
318		directions = append(directions, RTPTransceiverDirectionSendonly)
319	}
320	if t.Receiver() != nil {
321		directions = append(directions, RTPTransceiverDirectionRecvonly)
322	}
323
324	parameters := mediaEngine.getRTPParametersByKind(t.kind, directions)
325	for _, rtpExtension := range parameters.HeaderExtensions {
326		extURL, err := url.Parse(rtpExtension.URI)
327		if err != nil {
328			return false, err
329		}
330		media.WithExtMap(sdp.ExtMap{Value: rtpExtension.ID, URI: extURL})
331	}
332
333	if len(mediaSection.ridMap) > 0 {
334		recvRids := make([]string, 0, len(mediaSection.ridMap))
335
336		for rid := range mediaSection.ridMap {
337			media.WithValueAttribute("rid", rid+" recv")
338			recvRids = append(recvRids, rid)
339		}
340		// Simulcast
341		media.WithValueAttribute("simulcast", "recv "+strings.Join(recvRids, ";"))
342	}
343
344	for _, mt := range transceivers {
345		if mt.Sender() != nil && mt.Sender().Track() != nil {
346			track := mt.Sender().Track()
347			media = media.WithMediaSource(uint32(mt.Sender().ssrc), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
348			if !isPlanB {
349				media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID())
350				break
351			}
352		}
353	}
354
355	media = media.WithPropertyAttribute(t.Direction().String())
356
357	for _, fingerprint := range dtlsFingerprints {
358		media = media.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value))
359	}
360
361	if shouldAddCandidates {
362		if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil {
363			return false, err
364		}
365	}
366
367	d.WithMedia(media)
368
369	return true, nil
370}
371
372type mediaSection struct {
373	id           string
374	transceivers []*RTPTransceiver
375	data         bool
376	ridMap       map[string]string
377}
378
379// populateSDP serializes a PeerConnections state into an SDP
380func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaDescriptionFingerprint bool, isICELite bool, mediaEngine *MediaEngine, connectionRole sdp.ConnectionRole, candidates []ICECandidate, iceParams ICEParameters, mediaSections []mediaSection, iceGatheringState ICEGatheringState) (*sdp.SessionDescription, error) {
381	var err error
382	mediaDtlsFingerprints := []DTLSFingerprint{}
383
384	if mediaDescriptionFingerprint {
385		mediaDtlsFingerprints = dtlsFingerprints
386	}
387
388	bundleValue := "BUNDLE"
389	bundleCount := 0
390	appendBundle := func(midValue string) {
391		bundleValue += " " + midValue
392		bundleCount++
393	}
394
395	for i, m := range mediaSections {
396		if m.data && len(m.transceivers) != 0 {
397			return nil, errSDPMediaSectionMediaDataChanInvalid
398		} else if !isPlanB && len(m.transceivers) > 1 {
399			return nil, errSDPMediaSectionMultipleTrackInvalid
400		}
401
402		shouldAddID := true
403		shouldAddCanidates := i == 0
404		if m.data {
405			if err = addDataMediaSection(d, shouldAddCanidates, mediaDtlsFingerprints, m.id, iceParams, candidates, connectionRole, iceGatheringState); err != nil {
406				return nil, err
407			}
408		} else {
409			shouldAddID, err = addTransceiverSDP(d, isPlanB, shouldAddCanidates, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, m)
410			if err != nil {
411				return nil, err
412			}
413		}
414
415		if shouldAddID {
416			appendBundle(m.id)
417		}
418	}
419
420	if !mediaDescriptionFingerprint {
421		for _, fingerprint := range dtlsFingerprints {
422			d.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value))
423		}
424	}
425
426	if isICELite {
427		// RFC 5245 S15.3
428		d = d.WithValueAttribute(sdp.AttrKeyICELite, sdp.AttrKeyICELite)
429	}
430
431	return d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue), nil
432}
433
434func getMidValue(media *sdp.MediaDescription) string {
435	for _, attr := range media.Attributes {
436		if attr.Key == "mid" {
437			return attr.Value
438		}
439	}
440	return ""
441}
442
443func descriptionIsPlanB(desc *SessionDescription) bool {
444	if desc == nil || desc.parsed == nil {
445		return false
446	}
447
448	detectionRegex := regexp.MustCompile(`(?i)^(audio|video|data)$`)
449	for _, media := range desc.parsed.MediaDescriptions {
450		if len(detectionRegex.FindStringSubmatch(getMidValue(media))) == 2 {
451			return true
452		}
453	}
454	return false
455}
456
457func getPeerDirection(media *sdp.MediaDescription) RTPTransceiverDirection {
458	for _, a := range media.Attributes {
459		if direction := NewRTPTransceiverDirection(a.Key); direction != RTPTransceiverDirection(Unknown) {
460			return direction
461		}
462	}
463	return RTPTransceiverDirection(Unknown)
464}
465
466func extractFingerprint(desc *sdp.SessionDescription) (string, string, error) {
467	fingerprints := []string{}
468
469	if fingerprint, haveFingerprint := desc.Attribute("fingerprint"); haveFingerprint {
470		fingerprints = append(fingerprints, fingerprint)
471	}
472
473	for _, m := range desc.MediaDescriptions {
474		if fingerprint, haveFingerprint := m.Attribute("fingerprint"); haveFingerprint {
475			fingerprints = append(fingerprints, fingerprint)
476		}
477	}
478
479	if len(fingerprints) < 1 {
480		return "", "", ErrSessionDescriptionNoFingerprint
481	}
482
483	for _, m := range fingerprints {
484		if m != fingerprints[0] {
485			return "", "", ErrSessionDescriptionConflictingFingerprints
486		}
487	}
488
489	parts := strings.Split(fingerprints[0], " ")
490	if len(parts) != 2 {
491		return "", "", ErrSessionDescriptionInvalidFingerprint
492	}
493	return parts[1], parts[0], nil
494}
495
496func extractICEDetails(desc *sdp.SessionDescription) (string, string, []ICECandidate, error) {
497	candidates := []ICECandidate{}
498	remotePwds := []string{}
499	remoteUfrags := []string{}
500
501	if ufrag, haveUfrag := desc.Attribute("ice-ufrag"); haveUfrag {
502		remoteUfrags = append(remoteUfrags, ufrag)
503	}
504	if pwd, havePwd := desc.Attribute("ice-pwd"); havePwd {
505		remotePwds = append(remotePwds, pwd)
506	}
507
508	for _, m := range desc.MediaDescriptions {
509		if ufrag, haveUfrag := m.Attribute("ice-ufrag"); haveUfrag {
510			remoteUfrags = append(remoteUfrags, ufrag)
511		}
512		if pwd, havePwd := m.Attribute("ice-pwd"); havePwd {
513			remotePwds = append(remotePwds, pwd)
514		}
515
516		for _, a := range m.Attributes {
517			if a.IsICECandidate() {
518				c, err := ice.UnmarshalCandidate(a.Value)
519				if err != nil {
520					return "", "", nil, err
521				}
522
523				candidate, err := newICECandidateFromICE(c)
524				if err != nil {
525					return "", "", nil, err
526				}
527
528				candidates = append(candidates, candidate)
529			}
530		}
531	}
532
533	if len(remoteUfrags) == 0 {
534		return "", "", nil, ErrSessionDescriptionMissingIceUfrag
535	} else if len(remotePwds) == 0 {
536		return "", "", nil, ErrSessionDescriptionMissingIcePwd
537	}
538
539	for _, m := range remoteUfrags {
540		if m != remoteUfrags[0] {
541			return "", "", nil, ErrSessionDescriptionConflictingIceUfrag
542		}
543	}
544
545	for _, m := range remotePwds {
546		if m != remotePwds[0] {
547			return "", "", nil, ErrSessionDescriptionConflictingIcePwd
548		}
549	}
550
551	return remoteUfrags[0], remotePwds[0], candidates, nil
552}
553
554func haveApplicationMediaSection(desc *sdp.SessionDescription) bool {
555	for _, m := range desc.MediaDescriptions {
556		if m.MediaName.Media == mediaSectionApplication {
557			return true
558		}
559	}
560
561	return false
562}
563
564func getByMid(searchMid string, desc *SessionDescription) *sdp.MediaDescription {
565	for _, m := range desc.parsed.MediaDescriptions {
566		if mid, ok := m.Attribute(sdp.AttrKeyMID); ok && mid == searchMid {
567			return m
568		}
569	}
570	return nil
571}
572
573// haveDataChannel return MediaDescription with MediaName equal application
574func haveDataChannel(desc *SessionDescription) *sdp.MediaDescription {
575	for _, d := range desc.parsed.MediaDescriptions {
576		if d.MediaName.Media == mediaSectionApplication {
577			return d
578		}
579	}
580	return nil
581}
582
583func codecsFromMediaDescription(m *sdp.MediaDescription) (out []RTPCodecParameters, err error) {
584	s := &sdp.SessionDescription{
585		MediaDescriptions: []*sdp.MediaDescription{m},
586	}
587
588	for _, payloadStr := range m.MediaName.Formats {
589		payloadType, err := strconv.Atoi(payloadStr)
590		if err != nil {
591			return nil, err
592		}
593
594		codec, err := s.GetCodecForPayloadType(uint8(payloadType))
595		if err != nil {
596			if payloadType == 0 {
597				continue
598			}
599			return nil, err
600		}
601
602		channels := uint16(0)
603		val, err := strconv.Atoi(codec.EncodingParameters)
604		if err == nil {
605			channels = uint16(val)
606		}
607
608		feedback := []RTCPFeedback{}
609		for _, raw := range codec.RTCPFeedback {
610			split := strings.Split(raw, " ")
611			entry := RTCPFeedback{Type: split[0]}
612			if len(split) == 2 {
613				entry.Parameter = split[1]
614			}
615
616			feedback = append(feedback, entry)
617		}
618
619		out = append(out, RTPCodecParameters{
620			RTPCodecCapability: RTPCodecCapability{m.MediaName.Media + "/" + codec.Name, codec.ClockRate, channels, codec.Fmtp, feedback},
621			PayloadType:        PayloadType(payloadType),
622		})
623	}
624
625	return out, nil
626}
627
628func rtpExtensionsFromMediaDescription(m *sdp.MediaDescription) (map[string]int, error) {
629	out := map[string]int{}
630
631	for _, a := range m.Attributes {
632		if a.Key == sdp.AttrKeyExtMap {
633			e := sdp.ExtMap{}
634			if err := e.Unmarshal(a.String()); err != nil {
635				return nil, err
636			}
637
638			out[e.URI.String()] = e.Value
639		}
640	}
641
642	return out, nil
643}
644