github.com/pion/webrtc/v4@v4.0.1/sdp.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  //go:build !js
     5  // +build !js
     6  
     7  package webrtc
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"net/url"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  	"sync/atomic"
    17  
    18  	"github.com/pion/ice/v4"
    19  	"github.com/pion/logging"
    20  	"github.com/pion/sdp/v3"
    21  )
    22  
    23  // trackDetails represents any media source that can be represented in a SDP
    24  // This isn't keyed by SSRC because it also needs to support rid based sources
    25  type trackDetails struct {
    26  	mid        string
    27  	kind       RTPCodecType
    28  	streamID   string
    29  	id         string
    30  	ssrcs      []SSRC
    31  	repairSsrc *SSRC
    32  	rids       []string
    33  }
    34  
    35  func trackDetailsForSSRC(trackDetails []trackDetails, ssrc SSRC) *trackDetails {
    36  	for i := range trackDetails {
    37  		for j := range trackDetails[i].ssrcs {
    38  			if trackDetails[i].ssrcs[j] == ssrc {
    39  				return &trackDetails[i]
    40  			}
    41  		}
    42  	}
    43  	return nil
    44  }
    45  
    46  func trackDetailsForRID(trackDetails []trackDetails, mid, rid string) *trackDetails {
    47  	for i := range trackDetails {
    48  		if trackDetails[i].mid != mid {
    49  			continue
    50  		}
    51  
    52  		for j := range trackDetails[i].rids {
    53  			if trackDetails[i].rids[j] == rid {
    54  				return &trackDetails[i]
    55  			}
    56  		}
    57  	}
    58  	return nil
    59  }
    60  
    61  func filterTrackWithSSRC(incomingTracks []trackDetails, ssrc SSRC) []trackDetails {
    62  	filtered := []trackDetails{}
    63  	doesTrackHaveSSRC := func(t trackDetails) bool {
    64  		for i := range t.ssrcs {
    65  			if t.ssrcs[i] == ssrc {
    66  				return true
    67  			}
    68  		}
    69  
    70  		return false
    71  	}
    72  
    73  	for i := range incomingTracks {
    74  		if !doesTrackHaveSSRC(incomingTracks[i]) {
    75  			filtered = append(filtered, incomingTracks[i])
    76  		}
    77  	}
    78  
    79  	return filtered
    80  }
    81  
    82  // extract all trackDetails from an SDP.
    83  func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) (incomingTracks []trackDetails) { // nolint:gocognit
    84  	for _, media := range s.MediaDescriptions {
    85  		tracksInMediaSection := []trackDetails{}
    86  		rtxRepairFlows := map[uint64]uint64{}
    87  
    88  		// Plan B can have multiple tracks in a single media section
    89  		streamID := ""
    90  		trackID := ""
    91  
    92  		// If media section is recvonly or inactive skip
    93  		if _, ok := media.Attribute(sdp.AttrKeyRecvOnly); ok {
    94  			continue
    95  		} else if _, ok := media.Attribute(sdp.AttrKeyInactive); ok {
    96  			continue
    97  		}
    98  
    99  		midValue := getMidValue(media)
   100  		if midValue == "" {
   101  			continue
   102  		}
   103  
   104  		codecType := NewRTPCodecType(media.MediaName.Media)
   105  		if codecType == 0 {
   106  			continue
   107  		}
   108  
   109  		for _, attr := range media.Attributes {
   110  			switch attr.Key {
   111  			case sdp.AttrKeySSRCGroup:
   112  				split := strings.Split(attr.Value, " ")
   113  				if split[0] == sdp.SemanticTokenFlowIdentification {
   114  					// Add rtx ssrcs to blacklist, to avoid adding them as tracks
   115  					// Essentially lines like `a=ssrc-group:FID 2231627014 632943048` are processed by this section
   116  					// as this declares that the second SSRC (632943048) is a rtx repair flow (RFC4588) for the first
   117  					// (2231627014) as specified in RFC5576
   118  					if len(split) == 3 {
   119  						baseSsrc, err := strconv.ParseUint(split[1], 10, 32)
   120  						if err != nil {
   121  							log.Warnf("Failed to parse SSRC: %v", err)
   122  							continue
   123  						}
   124  						rtxRepairFlow, err := strconv.ParseUint(split[2], 10, 32)
   125  						if err != nil {
   126  							log.Warnf("Failed to parse SSRC: %v", err)
   127  							continue
   128  						}
   129  						rtxRepairFlows[rtxRepairFlow] = baseSsrc
   130  						tracksInMediaSection = filterTrackWithSSRC(tracksInMediaSection, SSRC(rtxRepairFlow)) // Remove if rtx was added as track before
   131  						for i := range tracksInMediaSection {
   132  							if tracksInMediaSection[i].ssrcs[0] == SSRC(baseSsrc) {
   133  								repairSsrc := SSRC(rtxRepairFlow)
   134  								tracksInMediaSection[i].repairSsrc = &repairSsrc
   135  							}
   136  						}
   137  					}
   138  				}
   139  
   140  			// Handle `a=msid:<stream_id> <track_label>` for Unified plan. The first value is the same as MediaStream.id
   141  			// in the browser and can be used to figure out which tracks belong to the same stream. The browser should
   142  			// figure this out automatically when an ontrack event is emitted on RTCPeerConnection.
   143  			case sdp.AttrKeyMsid:
   144  				split := strings.Split(attr.Value, " ")
   145  				if len(split) == 2 {
   146  					streamID = split[0]
   147  					trackID = split[1]
   148  				}
   149  
   150  			case sdp.AttrKeySSRC:
   151  				split := strings.Split(attr.Value, " ")
   152  				ssrc, err := strconv.ParseUint(split[0], 10, 32)
   153  				if err != nil {
   154  					log.Warnf("Failed to parse SSRC: %v", err)
   155  					continue
   156  				}
   157  
   158  				if _, ok := rtxRepairFlows[ssrc]; ok {
   159  					continue // This ssrc is a RTX repair flow, ignore
   160  				}
   161  
   162  				if len(split) == 3 && strings.HasPrefix(split[1], "msid:") {
   163  					streamID = split[1][len("msid:"):]
   164  					trackID = split[2]
   165  				}
   166  
   167  				isNewTrack := true
   168  				trackDetails := &trackDetails{}
   169  				for i := range tracksInMediaSection {
   170  					for j := range tracksInMediaSection[i].ssrcs {
   171  						if tracksInMediaSection[i].ssrcs[j] == SSRC(ssrc) {
   172  							trackDetails = &tracksInMediaSection[i]
   173  							isNewTrack = false
   174  						}
   175  					}
   176  				}
   177  
   178  				trackDetails.mid = midValue
   179  				trackDetails.kind = codecType
   180  				trackDetails.streamID = streamID
   181  				trackDetails.id = trackID
   182  				trackDetails.ssrcs = []SSRC{SSRC(ssrc)}
   183  
   184  				for r, baseSsrc := range rtxRepairFlows {
   185  					if baseSsrc == ssrc {
   186  						repairSsrc := SSRC(r)
   187  						trackDetails.repairSsrc = &repairSsrc
   188  					}
   189  				}
   190  
   191  				if isNewTrack {
   192  					tracksInMediaSection = append(tracksInMediaSection, *trackDetails)
   193  				}
   194  			}
   195  		}
   196  
   197  		if rids := getRids(media); len(rids) != 0 && trackID != "" && streamID != "" {
   198  			simulcastTrack := trackDetails{
   199  				mid:      midValue,
   200  				kind:     codecType,
   201  				streamID: streamID,
   202  				id:       trackID,
   203  				rids:     []string{},
   204  			}
   205  			for _, rid := range rids {
   206  				simulcastTrack.rids = append(simulcastTrack.rids, rid.id)
   207  			}
   208  
   209  			tracksInMediaSection = []trackDetails{simulcastTrack}
   210  		}
   211  
   212  		incomingTracks = append(incomingTracks, tracksInMediaSection...)
   213  	}
   214  
   215  	return incomingTracks
   216  }
   217  
   218  func trackDetailsToRTPReceiveParameters(t *trackDetails) RTPReceiveParameters {
   219  	encodingSize := len(t.ssrcs)
   220  	if len(t.rids) >= encodingSize {
   221  		encodingSize = len(t.rids)
   222  	}
   223  
   224  	encodings := make([]RTPDecodingParameters, encodingSize)
   225  	for i := range encodings {
   226  		if len(t.rids) > i {
   227  			encodings[i].RID = t.rids[i]
   228  		}
   229  		if len(t.ssrcs) > i {
   230  			encodings[i].SSRC = t.ssrcs[i]
   231  		}
   232  
   233  		if t.repairSsrc != nil {
   234  			encodings[i].RTX.SSRC = *t.repairSsrc
   235  		}
   236  	}
   237  
   238  	return RTPReceiveParameters{Encodings: encodings}
   239  }
   240  
   241  func getRids(media *sdp.MediaDescription) []*simulcastRid {
   242  	rids := []*simulcastRid{}
   243  	var simulcastAttr string
   244  	for _, attr := range media.Attributes {
   245  		if attr.Key == sdpAttributeRid {
   246  			split := strings.Split(attr.Value, " ")
   247  			rids = append(rids, &simulcastRid{id: split[0], attrValue: attr.Value})
   248  		} else if attr.Key == sdpAttributeSimulcast {
   249  			simulcastAttr = attr.Value
   250  		}
   251  	}
   252  	// process paused stream like "a=simulcast:send 1;~2;~3"
   253  	if simulcastAttr != "" {
   254  		if space := strings.Index(simulcastAttr, " "); space > 0 {
   255  			simulcastAttr = simulcastAttr[space+1:]
   256  		}
   257  		ridStates := strings.Split(simulcastAttr, ";")
   258  		for _, ridState := range ridStates {
   259  			if ridState[:1] == "~" {
   260  				ridID := ridState[1:]
   261  				for _, rid := range rids {
   262  					if rid.id == ridID {
   263  						rid.paused = true
   264  						break
   265  					}
   266  				}
   267  			}
   268  		}
   269  	}
   270  	return rids
   271  }
   272  
   273  func addCandidatesToMediaDescriptions(candidates []ICECandidate, m *sdp.MediaDescription, iceGatheringState ICEGatheringState) error {
   274  	appendCandidateIfNew := func(c ice.Candidate, attributes []sdp.Attribute) {
   275  		marshaled := c.Marshal()
   276  		for _, a := range attributes {
   277  			if marshaled == a.Value {
   278  				return
   279  			}
   280  		}
   281  
   282  		m.WithValueAttribute("candidate", marshaled)
   283  	}
   284  
   285  	for _, c := range candidates {
   286  		candidate, err := c.toICE()
   287  		if err != nil {
   288  			return err
   289  		}
   290  
   291  		candidate.SetComponent(1)
   292  		appendCandidateIfNew(candidate, m.Attributes)
   293  
   294  		candidate.SetComponent(2)
   295  		appendCandidateIfNew(candidate, m.Attributes)
   296  	}
   297  
   298  	if iceGatheringState != ICEGatheringStateComplete {
   299  		return nil
   300  	}
   301  	for _, a := range m.Attributes {
   302  		if a.Key == "end-of-candidates" {
   303  			return nil
   304  		}
   305  	}
   306  
   307  	m.WithPropertyAttribute("end-of-candidates")
   308  	return nil
   309  }
   310  
   311  func addDataMediaSection(d *sdp.SessionDescription, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState) error {
   312  	media := (&sdp.MediaDescription{
   313  		MediaName: sdp.MediaName{
   314  			Media:   mediaSectionApplication,
   315  			Port:    sdp.RangedPort{Value: 9},
   316  			Protos:  []string{"UDP", "DTLS", "SCTP"},
   317  			Formats: []string{"webrtc-datachannel"},
   318  		},
   319  		ConnectionInformation: &sdp.ConnectionInformation{
   320  			NetworkType: "IN",
   321  			AddressType: "IP4",
   322  			Address: &sdp.Address{
   323  				Address: "0.0.0.0",
   324  			},
   325  		},
   326  	}).
   327  		WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()).
   328  		WithValueAttribute(sdp.AttrKeyMID, midValue).
   329  		WithPropertyAttribute(RTPTransceiverDirectionSendrecv.String()).
   330  		WithPropertyAttribute("sctp-port:5000").
   331  		WithICECredentials(iceParams.UsernameFragment, iceParams.Password)
   332  
   333  	for _, f := range dtlsFingerprints {
   334  		media = media.WithFingerprint(f.Algorithm, strings.ToUpper(f.Value))
   335  	}
   336  
   337  	if shouldAddCandidates {
   338  		if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil {
   339  			return err
   340  		}
   341  	}
   342  
   343  	d.WithMedia(media)
   344  	return nil
   345  }
   346  
   347  func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGatherer, iceGatheringState ICEGatheringState) *SessionDescription {
   348  	if sessionDescription == nil || i == nil {
   349  		return sessionDescription
   350  	}
   351  
   352  	candidates, err := i.GetLocalCandidates()
   353  	if err != nil {
   354  		return sessionDescription
   355  	}
   356  
   357  	parsed := sessionDescription.parsed
   358  	if len(parsed.MediaDescriptions) > 0 {
   359  		m := parsed.MediaDescriptions[0]
   360  		if err = addCandidatesToMediaDescriptions(candidates, m, iceGatheringState); err != nil {
   361  			return sessionDescription
   362  		}
   363  	}
   364  
   365  	sdp, err := parsed.Marshal()
   366  	if err != nil {
   367  		return sessionDescription
   368  	}
   369  
   370  	return &SessionDescription{
   371  		SDP:    string(sdp),
   372  		Type:   sessionDescription.Type,
   373  		parsed: parsed,
   374  	}
   375  }
   376  
   377  // nolint: gocognit
   378  func addSenderSDP(
   379  	mediaSection mediaSection,
   380  	isPlanB bool,
   381  	media *sdp.MediaDescription,
   382  ) {
   383  	for _, mt := range mediaSection.transceivers {
   384  		sender := mt.Sender()
   385  		if sender == nil {
   386  			continue
   387  		}
   388  
   389  		track := sender.Track()
   390  		if track == nil {
   391  			continue
   392  		}
   393  
   394  		sendParameters := sender.GetParameters()
   395  		for _, encoding := range sendParameters.Encodings {
   396  			if encoding.RTX.SSRC != 0 {
   397  				media = media.WithValueAttribute("ssrc-group", fmt.Sprintf("FID %d %d", encoding.SSRC, encoding.RTX.SSRC))
   398  			}
   399  			if encoding.FEC.SSRC != 0 {
   400  				media = media.WithValueAttribute("ssrc-group", fmt.Sprintf("FEC-FR %d %d", encoding.SSRC, encoding.FEC.SSRC))
   401  			}
   402  
   403  			media = media.WithMediaSource(uint32(encoding.SSRC), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
   404  
   405  			if !isPlanB {
   406  				if encoding.RTX.SSRC != 0 {
   407  					media = media.WithMediaSource(uint32(encoding.RTX.SSRC), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
   408  				}
   409  				if encoding.FEC.SSRC != 0 {
   410  					media = media.WithMediaSource(uint32(encoding.FEC.SSRC), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
   411  				}
   412  
   413  				media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID())
   414  			}
   415  		}
   416  
   417  		if len(sendParameters.Encodings) > 1 {
   418  			sendRids := make([]string, 0, len(sendParameters.Encodings))
   419  
   420  			for _, encoding := range sendParameters.Encodings {
   421  				media.WithValueAttribute(sdpAttributeRid, encoding.RID+" send")
   422  				sendRids = append(sendRids, encoding.RID)
   423  			}
   424  			// Simulcast
   425  			media.WithValueAttribute(sdpAttributeSimulcast, "send "+strings.Join(sendRids, ";"))
   426  		}
   427  
   428  		if !isPlanB {
   429  			break
   430  		}
   431  	}
   432  }
   433  
   434  func addTransceiverSDP(
   435  	d *sdp.SessionDescription,
   436  	isPlanB bool,
   437  	shouldAddCandidates bool,
   438  	dtlsFingerprints []DTLSFingerprint,
   439  	mediaEngine *MediaEngine,
   440  	midValue string,
   441  	iceParams ICEParameters,
   442  	candidates []ICECandidate,
   443  	dtlsRole sdp.ConnectionRole,
   444  	iceGatheringState ICEGatheringState,
   445  	mediaSection mediaSection,
   446  ) (bool, error) {
   447  	transceivers := mediaSection.transceivers
   448  	if len(transceivers) < 1 {
   449  		return false, errSDPZeroTransceivers
   450  	}
   451  	// Use the first transceiver to generate the section attributes
   452  	t := transceivers[0]
   453  	media := sdp.NewJSEPMediaDescription(t.kind.String(), []string{}).
   454  		WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()).
   455  		WithValueAttribute(sdp.AttrKeyMID, midValue).
   456  		WithICECredentials(iceParams.UsernameFragment, iceParams.Password).
   457  		WithPropertyAttribute(sdp.AttrKeyRTCPMux).
   458  		WithPropertyAttribute(sdp.AttrKeyRTCPRsize)
   459  
   460  	codecs := t.getCodecs()
   461  	for _, codec := range codecs {
   462  		name := strings.TrimPrefix(codec.MimeType, "audio/")
   463  		name = strings.TrimPrefix(name, "video/")
   464  		media.WithCodec(uint8(codec.PayloadType), name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine)
   465  
   466  		for _, feedback := range codec.RTPCodecCapability.RTCPFeedback {
   467  			media.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", codec.PayloadType, feedback.Type, feedback.Parameter))
   468  		}
   469  	}
   470  	if len(codecs) == 0 {
   471  		// If we are sender and we have no codecs throw an error early
   472  		if t.Sender() != nil {
   473  			return false, ErrSenderWithNoCodecs
   474  		}
   475  
   476  		// Explicitly reject track if we don't have the codec
   477  		// We need to include connection information even if we're rejecting a track, otherwise Firefox will fail to
   478  		// parse the SDP with an error like:
   479  		// SIPCC Failed to parse SDP: SDP Parse Error on line 50:  c= connection line not specified for every media level, validation failed.
   480  		// In addition this makes our SDP compliant with RFC 4566 Section 5.7: https://datatracker.ietf.org/doc/html/rfc4566#section-5.7
   481  		d.WithMedia(&sdp.MediaDescription{
   482  			MediaName: sdp.MediaName{
   483  				Media:   t.kind.String(),
   484  				Port:    sdp.RangedPort{Value: 0},
   485  				Protos:  []string{"UDP", "TLS", "RTP", "SAVPF"},
   486  				Formats: []string{"0"},
   487  			},
   488  			ConnectionInformation: &sdp.ConnectionInformation{
   489  				NetworkType: "IN",
   490  				AddressType: "IP4",
   491  				Address: &sdp.Address{
   492  					Address: "0.0.0.0",
   493  				},
   494  			},
   495  		})
   496  		return false, nil
   497  	}
   498  
   499  	directions := []RTPTransceiverDirection{}
   500  	if t.Sender() != nil {
   501  		directions = append(directions, RTPTransceiverDirectionSendonly)
   502  	}
   503  	if t.Receiver() != nil {
   504  		directions = append(directions, RTPTransceiverDirectionRecvonly)
   505  	}
   506  
   507  	parameters := mediaEngine.getRTPParametersByKind(t.kind, directions)
   508  	for _, rtpExtension := range parameters.HeaderExtensions {
   509  		if mediaSection.matchExtensions != nil {
   510  			if _, enabled := mediaSection.matchExtensions[rtpExtension.URI]; !enabled {
   511  				continue
   512  			}
   513  		}
   514  		extURL, err := url.Parse(rtpExtension.URI)
   515  		if err != nil {
   516  			return false, err
   517  		}
   518  		media.WithExtMap(sdp.ExtMap{Value: rtpExtension.ID, URI: extURL})
   519  	}
   520  
   521  	if len(mediaSection.rids) > 0 {
   522  		recvRids := make([]string, 0, len(mediaSection.rids))
   523  
   524  		for _, rid := range mediaSection.rids {
   525  			ridID := rid.id
   526  			media.WithValueAttribute(sdpAttributeRid, ridID+" recv")
   527  			if rid.paused {
   528  				ridID = "~" + ridID
   529  			}
   530  			recvRids = append(recvRids, ridID)
   531  		}
   532  		// Simulcast
   533  		media.WithValueAttribute(sdpAttributeSimulcast, "recv "+strings.Join(recvRids, ";"))
   534  	}
   535  
   536  	addSenderSDP(mediaSection, isPlanB, media)
   537  
   538  	media = media.WithPropertyAttribute(t.Direction().String())
   539  
   540  	for _, fingerprint := range dtlsFingerprints {
   541  		media = media.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value))
   542  	}
   543  
   544  	if shouldAddCandidates {
   545  		if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil {
   546  			return false, err
   547  		}
   548  	}
   549  
   550  	d.WithMedia(media)
   551  
   552  	return true, nil
   553  }
   554  
   555  type simulcastRid struct {
   556  	id        string
   557  	attrValue string
   558  	paused    bool
   559  }
   560  
   561  type mediaSection struct {
   562  	id              string
   563  	transceivers    []*RTPTransceiver
   564  	data            bool
   565  	matchExtensions map[string]int
   566  	rids            []*simulcastRid
   567  }
   568  
   569  func bundleMatchFromRemote(matchBundleGroup *string) func(mid string) bool {
   570  	if matchBundleGroup == nil {
   571  		return func(string) bool {
   572  			return true
   573  		}
   574  	}
   575  	bundleTags := strings.Split(*matchBundleGroup, " ")
   576  	return func(midValue string) bool {
   577  		for _, tag := range bundleTags {
   578  			if tag == midValue {
   579  				return true
   580  			}
   581  		}
   582  		return false
   583  	}
   584  }
   585  
   586  // populateSDP serializes a PeerConnections state into an SDP
   587  func populateSDP(
   588  	d *sdp.SessionDescription,
   589  	isPlanB bool,
   590  	dtlsFingerprints []DTLSFingerprint,
   591  	mediaDescriptionFingerprint bool,
   592  	isICELite bool,
   593  	isExtmapAllowMixed bool,
   594  	mediaEngine *MediaEngine,
   595  	connectionRole sdp.ConnectionRole,
   596  	candidates []ICECandidate,
   597  	iceParams ICEParameters,
   598  	mediaSections []mediaSection,
   599  	iceGatheringState ICEGatheringState,
   600  	matchBundleGroup *string,
   601  ) (*sdp.SessionDescription, error) {
   602  	var err error
   603  	mediaDtlsFingerprints := []DTLSFingerprint{}
   604  
   605  	if mediaDescriptionFingerprint {
   606  		mediaDtlsFingerprints = dtlsFingerprints
   607  	}
   608  
   609  	bundleValue := "BUNDLE"
   610  	bundleCount := 0
   611  
   612  	bundleMatch := bundleMatchFromRemote(matchBundleGroup)
   613  	appendBundle := func(midValue string) {
   614  		bundleValue += " " + midValue
   615  		bundleCount++
   616  	}
   617  
   618  	for i, m := range mediaSections {
   619  		if m.data && len(m.transceivers) != 0 {
   620  			return nil, errSDPMediaSectionMediaDataChanInvalid
   621  		} else if !isPlanB && len(m.transceivers) > 1 {
   622  			return nil, errSDPMediaSectionMultipleTrackInvalid
   623  		}
   624  
   625  		shouldAddID := true
   626  		shouldAddCandidates := i == 0
   627  		if m.data {
   628  			if err = addDataMediaSection(d, shouldAddCandidates, mediaDtlsFingerprints, m.id, iceParams, candidates, connectionRole, iceGatheringState); err != nil {
   629  				return nil, err
   630  			}
   631  		} else {
   632  			shouldAddID, err = addTransceiverSDP(d, isPlanB, shouldAddCandidates, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, m)
   633  			if err != nil {
   634  				return nil, err
   635  			}
   636  		}
   637  
   638  		if shouldAddID {
   639  			if bundleMatch(m.id) {
   640  				appendBundle(m.id)
   641  			} else {
   642  				d.MediaDescriptions[len(d.MediaDescriptions)-1].MediaName.Port = sdp.RangedPort{Value: 0}
   643  			}
   644  		}
   645  	}
   646  
   647  	if !mediaDescriptionFingerprint {
   648  		for _, fingerprint := range dtlsFingerprints {
   649  			d.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value))
   650  		}
   651  	}
   652  
   653  	if isICELite {
   654  		// RFC 5245 S15.3
   655  		d = d.WithValueAttribute(sdp.AttrKeyICELite, "")
   656  	}
   657  
   658  	if isExtmapAllowMixed {
   659  		d = d.WithPropertyAttribute(sdp.AttrKeyExtMapAllowMixed)
   660  	}
   661  
   662  	if bundleCount > 0 {
   663  		d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue)
   664  	}
   665  	return d, nil
   666  }
   667  
   668  func getMidValue(media *sdp.MediaDescription) string {
   669  	for _, attr := range media.Attributes {
   670  		if attr.Key == "mid" {
   671  			return attr.Value
   672  		}
   673  	}
   674  	return ""
   675  }
   676  
   677  // SessionDescription contains a MediaSection with Multiple SSRCs, it is Plan-B
   678  func descriptionIsPlanB(desc *SessionDescription, log logging.LeveledLogger) bool {
   679  	if desc == nil || desc.parsed == nil {
   680  		return false
   681  	}
   682  
   683  	// Store all MIDs that already contain a track
   684  	midWithTrack := map[string]bool{}
   685  
   686  	for _, trackDetail := range trackDetailsFromSDP(log, desc.parsed) {
   687  		if _, ok := midWithTrack[trackDetail.mid]; ok {
   688  			return true
   689  		}
   690  		midWithTrack[trackDetail.mid] = true
   691  	}
   692  
   693  	return false
   694  }
   695  
   696  // SessionDescription contains a MediaSection with name `audio`, `video` or `data`
   697  // If only one SSRC is set we can't know if it is Plan-B or Unified. If users have
   698  // set fallback mode assume it is Plan-B
   699  func descriptionPossiblyPlanB(desc *SessionDescription) bool {
   700  	if desc == nil || desc.parsed == nil {
   701  		return false
   702  	}
   703  
   704  	detectionRegex := regexp.MustCompile(`(?i)^(audio|video|data)$`)
   705  	for _, media := range desc.parsed.MediaDescriptions {
   706  		if len(detectionRegex.FindStringSubmatch(getMidValue(media))) == 2 {
   707  			return true
   708  		}
   709  	}
   710  	return false
   711  }
   712  
   713  func getPeerDirection(media *sdp.MediaDescription) RTPTransceiverDirection {
   714  	for _, a := range media.Attributes {
   715  		if direction := NewRTPTransceiverDirection(a.Key); direction != RTPTransceiverDirectionUnknown {
   716  			return direction
   717  		}
   718  	}
   719  	return RTPTransceiverDirectionUnknown
   720  }
   721  
   722  func extractFingerprint(desc *sdp.SessionDescription) (string, string, error) {
   723  	fingerprints := []string{}
   724  
   725  	if fingerprint, haveFingerprint := desc.Attribute("fingerprint"); haveFingerprint {
   726  		fingerprints = append(fingerprints, fingerprint)
   727  	}
   728  
   729  	for _, m := range desc.MediaDescriptions {
   730  		if fingerprint, haveFingerprint := m.Attribute("fingerprint"); haveFingerprint {
   731  			fingerprints = append(fingerprints, fingerprint)
   732  		}
   733  	}
   734  
   735  	if len(fingerprints) < 1 {
   736  		return "", "", ErrSessionDescriptionNoFingerprint
   737  	}
   738  
   739  	for _, m := range fingerprints {
   740  		if m != fingerprints[0] {
   741  			return "", "", ErrSessionDescriptionConflictingFingerprints
   742  		}
   743  	}
   744  
   745  	parts := strings.Split(fingerprints[0], " ")
   746  	if len(parts) != 2 {
   747  		return "", "", ErrSessionDescriptionInvalidFingerprint
   748  	}
   749  	return parts[1], parts[0], nil
   750  }
   751  
   752  func extractICEDetails(desc *sdp.SessionDescription, log logging.LeveledLogger) (string, string, []ICECandidate, error) { // nolint:gocognit
   753  	candidates := []ICECandidate{}
   754  	remotePwds := []string{}
   755  	remoteUfrags := []string{}
   756  
   757  	if ufrag, haveUfrag := desc.Attribute("ice-ufrag"); haveUfrag {
   758  		remoteUfrags = append(remoteUfrags, ufrag)
   759  	}
   760  	if pwd, havePwd := desc.Attribute("ice-pwd"); havePwd {
   761  		remotePwds = append(remotePwds, pwd)
   762  	}
   763  
   764  	for _, m := range desc.MediaDescriptions {
   765  		if ufrag, haveUfrag := m.Attribute("ice-ufrag"); haveUfrag {
   766  			remoteUfrags = append(remoteUfrags, ufrag)
   767  		}
   768  		if pwd, havePwd := m.Attribute("ice-pwd"); havePwd {
   769  			remotePwds = append(remotePwds, pwd)
   770  		}
   771  
   772  		for _, a := range m.Attributes {
   773  			if a.IsICECandidate() {
   774  				c, err := ice.UnmarshalCandidate(a.Value)
   775  				if err != nil {
   776  					if errors.Is(err, ice.ErrUnknownCandidateTyp) || errors.Is(err, ice.ErrDetermineNetworkType) {
   777  						log.Warnf("Discarding remote candidate: %s", err)
   778  						continue
   779  					}
   780  					return "", "", nil, err
   781  				}
   782  
   783  				candidate, err := newICECandidateFromICE(c)
   784  				if err != nil {
   785  					return "", "", nil, err
   786  				}
   787  
   788  				candidates = append(candidates, candidate)
   789  			}
   790  		}
   791  	}
   792  
   793  	if len(remoteUfrags) == 0 {
   794  		return "", "", nil, ErrSessionDescriptionMissingIceUfrag
   795  	} else if len(remotePwds) == 0 {
   796  		return "", "", nil, ErrSessionDescriptionMissingIcePwd
   797  	}
   798  
   799  	for _, m := range remoteUfrags {
   800  		if m != remoteUfrags[0] {
   801  			return "", "", nil, ErrSessionDescriptionConflictingIceUfrag
   802  		}
   803  	}
   804  
   805  	for _, m := range remotePwds {
   806  		if m != remotePwds[0] {
   807  			return "", "", nil, ErrSessionDescriptionConflictingIcePwd
   808  		}
   809  	}
   810  
   811  	return remoteUfrags[0], remotePwds[0], candidates, nil
   812  }
   813  
   814  func haveApplicationMediaSection(desc *sdp.SessionDescription) bool {
   815  	for _, m := range desc.MediaDescriptions {
   816  		if m.MediaName.Media == mediaSectionApplication {
   817  			return true
   818  		}
   819  	}
   820  
   821  	return false
   822  }
   823  
   824  func getByMid(searchMid string, desc *SessionDescription) *sdp.MediaDescription {
   825  	for _, m := range desc.parsed.MediaDescriptions {
   826  		if mid, ok := m.Attribute(sdp.AttrKeyMID); ok && mid == searchMid {
   827  			return m
   828  		}
   829  	}
   830  	return nil
   831  }
   832  
   833  // haveDataChannel return MediaDescription with MediaName equal application
   834  func haveDataChannel(desc *SessionDescription) *sdp.MediaDescription {
   835  	for _, d := range desc.parsed.MediaDescriptions {
   836  		if d.MediaName.Media == mediaSectionApplication {
   837  			return d
   838  		}
   839  	}
   840  	return nil
   841  }
   842  
   843  func codecsFromMediaDescription(m *sdp.MediaDescription) (out []RTPCodecParameters, err error) {
   844  	s := &sdp.SessionDescription{
   845  		MediaDescriptions: []*sdp.MediaDescription{m},
   846  	}
   847  
   848  	for _, payloadStr := range m.MediaName.Formats {
   849  		payloadType, err := strconv.ParseUint(payloadStr, 10, 8)
   850  		if err != nil {
   851  			return nil, err
   852  		}
   853  
   854  		codec, err := s.GetCodecForPayloadType(uint8(payloadType))
   855  		if err != nil {
   856  			if payloadType == 0 {
   857  				continue
   858  			}
   859  			return nil, err
   860  		}
   861  
   862  		channels := uint16(0)
   863  		val, err := strconv.ParseUint(codec.EncodingParameters, 10, 16)
   864  		if err == nil {
   865  			channels = uint16(val)
   866  		}
   867  
   868  		feedback := []RTCPFeedback{}
   869  		for _, raw := range codec.RTCPFeedback {
   870  			split := strings.Split(raw, " ")
   871  			entry := RTCPFeedback{Type: split[0]}
   872  			if len(split) == 2 {
   873  				entry.Parameter = split[1]
   874  			}
   875  
   876  			feedback = append(feedback, entry)
   877  		}
   878  
   879  		out = append(out, RTPCodecParameters{
   880  			RTPCodecCapability: RTPCodecCapability{m.MediaName.Media + "/" + codec.Name, codec.ClockRate, channels, codec.Fmtp, feedback},
   881  			PayloadType:        PayloadType(payloadType),
   882  		})
   883  	}
   884  
   885  	return out, nil
   886  }
   887  
   888  func rtpExtensionsFromMediaDescription(m *sdp.MediaDescription) (map[string]int, error) {
   889  	out := map[string]int{}
   890  
   891  	for _, a := range m.Attributes {
   892  		if a.Key == sdp.AttrKeyExtMap {
   893  			e := sdp.ExtMap{}
   894  			if err := e.Unmarshal(a.String()); err != nil {
   895  				return nil, err
   896  			}
   897  
   898  			out[e.URI.String()] = e.Value
   899  		}
   900  	}
   901  
   902  	return out, nil
   903  }
   904  
   905  // updateSDPOrigin saves sdp.Origin in PeerConnection when creating 1st local SDP;
   906  // for subsequent calling, it updates Origin for SessionDescription from saved one
   907  // and increments session version by one.
   908  // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-25#section-5.2.2
   909  func updateSDPOrigin(origin *sdp.Origin, d *sdp.SessionDescription) {
   910  	if atomic.CompareAndSwapUint64(&origin.SessionVersion, 0, d.Origin.SessionVersion) { // store
   911  		atomic.StoreUint64(&origin.SessionID, d.Origin.SessionID)
   912  	} else { // load
   913  		for { // awaiting for saving session id
   914  			d.Origin.SessionID = atomic.LoadUint64(&origin.SessionID)
   915  			if d.Origin.SessionID != 0 {
   916  				break
   917  			}
   918  		}
   919  		d.Origin.SessionVersion = atomic.AddUint64(&origin.SessionVersion, 1)
   920  	}
   921  }
   922  
   923  func isIceLiteSet(desc *sdp.SessionDescription) bool {
   924  	for _, a := range desc.Attributes {
   925  		if strings.TrimSpace(a.Key) == sdp.AttrKeyICELite {
   926  			return true
   927  		}
   928  	}
   929  
   930  	return false
   931  }
   932  
   933  func isExtMapAllowMixedSet(desc *sdp.SessionDescription) bool {
   934  	for _, a := range desc.Attributes {
   935  		if strings.TrimSpace(a.Key) == sdp.AttrKeyExtMapAllowMixed {
   936  			return true
   937  		}
   938  	}
   939  
   940  	return false
   941  }