// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

//go:build !js
// +build !js

package webrtc

import (
	
	
	
	
	
	
	

	
	
	
)

// trackDetails represents any media source that can be represented in a SDP
// This isn't keyed by SSRC because it also needs to support rid based sources.
type trackDetails struct {
	mid      string
	kind     RTPCodecType
	streamID string
	id       string
	ssrcs    []SSRC
	rtxSsrc  *SSRC
	fecSsrc  *SSRC
	rids     []string
}

func trackDetailsForSSRC( []trackDetails,  SSRC) *trackDetails {
	for  := range  {
		for  := range [].ssrcs {
			if [].ssrcs[] ==  {
				return &[]
			}
		}
	}

	return nil
}

func trackDetailsForRID( []trackDetails, ,  string) *trackDetails {
	for  := range  {
		if [].mid !=  {
			continue
		}

		for  := range [].rids {
			if [].rids[] ==  {
				return &[]
			}
		}
	}

	return nil
}

func filterTrackWithSSRC( []trackDetails,  SSRC) []trackDetails {
	 := []trackDetails{}
	 := func( trackDetails) bool {
		for  := range .ssrcs {
			if .ssrcs[] ==  {
				return true
			}
		}

		return false
	}

	for  := range  {
		if !([]) {
			 = append(, [])
		}
	}

	return 
}

// extract all trackDetails from an SDP.
//
//nolint:gocognit,gocyclo,cyclop
func trackDetailsFromSDP(
	 logging.LeveledLogger,
	 *sdp.SessionDescription,
) ( []trackDetails) {
	for ,  := range .MediaDescriptions {
		 := []trackDetails{}
		 := map[uint64]uint64{}
		 := map[uint64]uint64{}

		// Plan B can have multiple tracks in a single media section
		 := ""
		 := ""

		// If media section is recvonly or inactive skip
		if ,  := .Attribute(sdp.AttrKeyRecvOnly);  {
			continue
		} else if ,  := .Attribute(sdp.AttrKeyInactive);  {
			continue
		}

		 := getMidValue()
		if  == "" {
			continue
		}

		 := NewRTPCodecType(.MediaName.Media)
		if  == 0 {
			continue
		}

		for ,  := range .Attributes {
			switch .Key {
			case sdp.AttrKeySSRCGroup:
				 := strings.Split(.Value, " ")
				if [0] == sdp.SemanticTokenFlowIdentification { //nolint:nestif
					// Add rtx ssrcs to blacklist, to avoid adding them as tracks
					// Essentially lines like `a=ssrc-group:FID 2231627014 632943048` are processed by this section
					// as this declares that the second SSRC (632943048) is a rtx repair flow (RFC4588) for the first
					// (2231627014) as specified in RFC5576
					if len() == 3 {
						,  := strconv.ParseUint([1], 10, 32)
						if  != nil {
							.Warnf("Failed to parse SSRC: %v", )

							continue
						}
						,  := strconv.ParseUint([2], 10, 32)
						if  != nil {
							.Warnf("Failed to parse SSRC: %v", )

							continue
						}
						[] = 
						 = filterTrackWithSSRC(
							,
							SSRC(),
						) // Remove if rtx was added as track before
						for  := range  {
							if [].ssrcs[0] == SSRC() {
								 := SSRC()
								[].rtxSsrc = &
							}
						}
					}
				} else if [0] == sdp.SemanticTokenForwardErrorCorrectionFramework {
					// Similar to above, lines like `a=ssrc-group:FEC-FR aaaaa bbbbb`
					// means for video ssrc aaaaa, there's a FEC track bbbbb
					if len() == 3 {
						,  := strconv.ParseUint([1], 10, 32)
						if  != nil {
							.Warnf("Failed to parse SSRC: %v", )

							continue
						}
						,  := strconv.ParseUint([2], 10, 32)
						if  != nil {
							.Warnf("Failed to parse SSRC: %v", )

							continue
						}
						[] = 
						 = filterTrackWithSSRC(
							,
							SSRC(),
						) // Remove if fec was added as track before
						for  := range  {
							if [].ssrcs[0] == SSRC() {
								 := SSRC()
								[].fecSsrc = &
							}
						}
					}
				}

			// Handle `a=msid:<stream_id> <track_label>` for Unified plan. The first value is the same as MediaStream.id
			// in the browser and can be used to figure out which tracks belong to the same stream. The browser should
			// figure this out automatically when an ontrack event is emitted on RTCPeerConnection.
			case sdp.AttrKeyMsid:
				 := strings.Split(.Value, " ")
				if len() == 2 {
					 = [0]
					 = [1]
				}

			case sdp.AttrKeySSRC:
				 := strings.Split(.Value, " ")
				,  := strconv.ParseUint([0], 10, 32)
				if  != nil {
					.Warnf("Failed to parse SSRC: %v", )

					continue
				}

				if ,  := [];  {
					continue // This ssrc is a RTX repair flow, ignore
				}
				if ,  := [];  {
					continue // This ssrc is a FEC repair flow, ignore
				}

				if len() == 3 && strings.HasPrefix([1], "msid:") {
					 = [1][len("msid:"):]
					 = [2]
				}

				 := true
				 := &trackDetails{}
				for  := range  {
					for  := range [].ssrcs {
						if [].ssrcs[] == SSRC() {
							 = &[]
							 = false
						}
					}
				}

				.mid = 
				.kind = 
				.streamID = 
				.id = 
				.ssrcs = []SSRC{SSRC()}

				for ,  := range  {
					if  ==  {
						 := SSRC() //nolint:gosec // G115
						.rtxSsrc = &
					}
				}
				for ,  := range  {
					if  ==  {
						 := SSRC() //nolint:gosec // G115
						.fecSsrc = &
					}
				}

				if  {
					 = append(, *)
				}
			}
		}

		if  := getRids(); len() != 0 &&  != "" &&  != "" {
			 := trackDetails{
				mid:      ,
				kind:     ,
				streamID: ,
				id:       ,
				rids:     []string{},
			}
			for ,  := range  {
				.rids = append(.rids, .id)
			}

			 = []trackDetails{}
		}

		 = append(, ...)
	}

	return 
}

func trackDetailsToRTPReceiveParameters( *trackDetails) RTPReceiveParameters {
	 := len(.ssrcs)
	if len(.rids) >=  {
		 = len(.rids)
	}

	 := make([]RTPDecodingParameters, )
	for  := range  {
		if len(.rids) >  {
			[].RID = .rids[]
		}
		if len(.ssrcs) >  {
			[].SSRC = .ssrcs[]
		}

		if .rtxSsrc != nil {
			[].RTX.SSRC = *.rtxSsrc
		}

		if .fecSsrc != nil {
			[].FEC.SSRC = *.fecSsrc
		}
	}

	return RTPReceiveParameters{Encodings: }
}

func getRids( *sdp.MediaDescription) []*simulcastRid {
	 := []*simulcastRid{}
	var  string
	for ,  := range .Attributes {
		if .Key == sdpAttributeRid {
			 := strings.Split(.Value, " ")
			 = append(, &simulcastRid{id: [0], attrValue: .Value})
		} else if .Key == sdpAttributeSimulcast {
			 = .Value
		}
	}
	// process paused stream like "a=simulcast:send 1;~2;~3"
	if  != "" {
		if  := strings.Index(, " ");  > 0 {
			 = [+1:]
		}
		 := strings.Split(, ";")
		for ,  := range  {
			if [:1] == "~" {
				 := [1:]
				for ,  := range  {
					if .id ==  {
						.paused = true

						break
					}
				}
			}
		}
	}

	return 
}

func addCandidatesToMediaDescriptions(
	 []ICECandidate,
	 *sdp.MediaDescription,
	 ICEGatheringState,
) error {
	 := func( ice.Candidate,  []sdp.Attribute) {
		 := .Marshal()
		for ,  := range  {
			if  == .Value {
				return
			}
		}

		.WithValueAttribute("candidate", )
	}

	for ,  := range  {
		,  := .ToICE()
		if  != nil {
			return 
		}

		.SetComponent(1)
		(, .Attributes)

		.SetComponent(2)
		(, .Attributes)
	}

	if  != ICEGatheringStateComplete {
		return nil
	}
	for ,  := range .Attributes {
		if .Key == "end-of-candidates" {
			return nil
		}
	}

	.WithPropertyAttribute("end-of-candidates")

	return nil
}

func addDataMediaSection(
	 *sdp.SessionDescription,
	 bool,
	 []DTLSFingerprint,
	 string,
	 ICEParameters,
	 []ICECandidate,
	 sdp.ConnectionRole,
	 ICEGatheringState,
	 uint32,
) error {
	 := (&sdp.MediaDescription{
		MediaName: sdp.MediaName{
			Media:   mediaSectionApplication,
			Port:    sdp.RangedPort{Value: 9},
			Protos:  []string{"UDP", "DTLS", "SCTP"},
			Formats: []string{"webrtc-datachannel"},
		},
		ConnectionInformation: &sdp.ConnectionInformation{
			NetworkType: "IN",
			AddressType: "IP4",
			Address: &sdp.Address{
				Address: "0.0.0.0",
			},
		},
	}).
		WithValueAttribute(sdp.AttrKeyConnectionSetup, .String()).
		WithValueAttribute(sdp.AttrKeyMID, ).
		WithPropertyAttribute(RTPTransceiverDirectionSendrecv.String()).
		WithPropertyAttribute("sctp-port:5000").
		WithValueAttribute("max-message-size", fmt.Sprintf("%d", )).
		WithICECredentials(.UsernameFragment, .Password)

	for ,  := range  {
		 = .WithFingerprint(.Algorithm, strings.ToUpper(.Value))
	}

	if  {
		if  := addCandidatesToMediaDescriptions(, , );  != nil {
			return 
		}
	}

	.WithMedia()

	return nil
}

func populateLocalCandidates(
	 *SessionDescription,
	 *ICEGatherer,
	 ICEGatheringState,
) *SessionDescription {
	if  == nil ||  == nil {
		return 
	}

	,  := .GetLocalCandidates()
	if  != nil {
		return 
	}

	 := .parsed
	if len(.MediaDescriptions) > 0 {
		 := .MediaDescriptions[0]
		if  = addCandidatesToMediaDescriptions(, , );  != nil {
			return 
		}
	}

	,  := .Marshal()
	if  != nil {
		return 
	}

	return &SessionDescription{
		SDP:    string(),
		Type:   .Type,
		parsed: ,
	}
}

//nolint:gocognit,cyclop
func addSenderSDP(
	 mediaSection,
	 bool,
	 *sdp.MediaDescription,
) {
	for ,  := range .transceivers {
		 := .Sender()
		if  == nil {
			continue
		}

		 := .Track()
		if  == nil {
			continue
		}

		 := .GetParameters()
		for ,  := range .Encodings {
			if .RTX.SSRC != 0 {
				 = .WithValueAttribute(
					"ssrc-group",
					fmt.Sprintf(
						"%s %d %d",
						sdp.SemanticTokenFlowIdentification,
						.SSRC,
						.RTX.SSRC,
					),
				)
			}
			if .FEC.SSRC != 0 {
				 = .WithValueAttribute(
					"ssrc-group",
					fmt.Sprintf(
						"%s %d %d",
						sdp.SemanticTokenForwardErrorCorrectionFramework,
						.SSRC,
						.FEC.SSRC,
					),
				)
			}

			 = .WithMediaSource(
				uint32(.SSRC),
				.StreamID(), /* cname */
				.StreamID(), /* streamLabel */
				.ID(),
			)

			if ! {
				if .RTX.SSRC != 0 {
					 = .WithMediaSource(
						uint32(.RTX.SSRC),
						.StreamID(), /* cname */
						.StreamID(), /* streamLabel */
						.ID(),
					)
				}
				if .FEC.SSRC != 0 {
					 = .WithMediaSource(
						uint32(.FEC.SSRC),
						.StreamID(), /* cname */
						.StreamID(), /* streamLabel */
						.ID(),
					)
				}

				 = .WithPropertyAttribute("msid:" + .StreamID() + " " + .ID())
			}
		}

		if len(.Encodings) > 1 {
			 := make([]string, 0, len(.Encodings))

			for ,  := range .Encodings {
				.WithValueAttribute(sdpAttributeRid, .RID+" send")
				 = append(, .RID)
			}
			// Simulcast
			.WithValueAttribute(sdpAttributeSimulcast, "send "+strings.Join(, ";"))
		}

		if ! {
			break
		}
	}
}

//nolint:cyclop
func addTransceiverSDP(
	 *sdp.SessionDescription,
	 bool,
	 bool,
	 []DTLSFingerprint,
	 *MediaEngine,
	 string,
	 ICEParameters,
	 []ICECandidate,
	 sdp.ConnectionRole,
	 ICEGatheringState,
	 mediaSection,
) (bool, error) {
	 := .transceivers
	if len() < 1 {
		return false, errSDPZeroTransceivers
	}
	// Use the first transceiver to generate the section attributes
	 := [0]
	 := sdp.NewJSEPMediaDescription(.kind.String(), []string{}).
		WithValueAttribute(sdp.AttrKeyConnectionSetup, .String()).
		WithValueAttribute(sdp.AttrKeyMID, ).
		WithICECredentials(.UsernameFragment, .Password).
		WithPropertyAttribute(sdp.AttrKeyRTCPMux).
		WithPropertyAttribute(sdp.AttrKeyRTCPRsize)

	 := .getCodecs()
	for ,  := range  {
		 := strings.TrimPrefix(.MimeType, "audio/")
		 = strings.TrimPrefix(, "video/")
		.WithCodec(uint8(.PayloadType), , .ClockRate, .Channels, .SDPFmtpLine)

		for ,  := range .RTPCodecCapability.RTCPFeedback {
			.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", .PayloadType, .Type, .Parameter))
		}
	}
	if len() == 0 {
		// If we are sender and we have no codecs throw an error early
		if .Sender() != nil {
			return false, ErrSenderWithNoCodecs
		}

		// Explicitly reject track if we don't have the codec
		// We need to include connection information even if we're rejecting a track, otherwise Firefox will fail to
		// parse the SDP with an error like:
		// SIPCC Failed to parse SDP: SDP Parse Error on line 50:  c= connection line not specified for every media level,
		// validation failed.
		// In addition this makes our SDP compliant with RFC 4566 Section 5.7:
		// https://datatracker.ietf.org/doc/html/rfc4566#section-5.7
		.WithMedia(&sdp.MediaDescription{
			MediaName: sdp.MediaName{
				Media:   .kind.String(),
				Port:    sdp.RangedPort{Value: 0},
				Protos:  []string{"UDP", "TLS", "RTP", "SAVPF"},
				Formats: []string{"0"},
			},
			ConnectionInformation: &sdp.ConnectionInformation{
				NetworkType: "IN",
				AddressType: "IP4",
				Address: &sdp.Address{
					Address: "0.0.0.0",
				},
			},
		})

		return false, nil
	}

	 := []RTPTransceiverDirection{}
	if .Sender() != nil {
		 = append(, RTPTransceiverDirectionSendonly)
	}
	if .Receiver() != nil {
		 = append(, RTPTransceiverDirectionRecvonly)
	}

	 := .getRTPParametersByKind(.kind, )
	for ,  := range .HeaderExtensions {
		if .matchExtensions != nil {
			if ,  := .matchExtensions[.URI]; ! {
				continue
			}
		}
		,  := url.Parse(.URI)
		if  != nil {
			return false, 
		}
		.WithExtMap(sdp.ExtMap{Value: .ID, URI: })
	}

	if len(.rids) > 0 {
		 := make([]string, 0, len(.rids))

		for ,  := range .rids {
			 := .id
			.WithValueAttribute(sdpAttributeRid, +" recv")
			if .paused {
				 = "~" + 
			}
			 = append(, )
		}
		// Simulcast
		.WithValueAttribute(sdpAttributeSimulcast, "recv "+strings.Join(, ";"))
	}

	addSenderSDP(, , )

	 = .WithPropertyAttribute(.Direction().String())

	for ,  := range  {
		 = .WithFingerprint(.Algorithm, strings.ToUpper(.Value))
	}

	if  {
		if  := addCandidatesToMediaDescriptions(, , );  != nil {
			return false, 
		}
	}

	.WithMedia()

	return true, nil
}

type simulcastRid struct {
	id        string
	attrValue string
	paused    bool
}

type mediaSection struct {
	id              string
	transceivers    []*RTPTransceiver
	data            bool
	matchExtensions map[string]int
	rids            []*simulcastRid
}

func bundleMatchFromRemote( *string) func( string) bool {
	if  == nil {
		return func(string) bool {
			return true
		}
	}
	 := strings.Split(*, " ")

	return func( string) bool {
		for ,  := range  {
			if  ==  {
				return true
			}
		}

		return false
	}
}

// populateSDP serializes a PeerConnections state into an SDP.
//
//nolint:cyclop
func populateSDP(
	 *sdp.SessionDescription,
	 bool,
	 []DTLSFingerprint,
	 bool,
	 bool,
	 bool,
	 *MediaEngine,
	 sdp.ConnectionRole,
	 []ICECandidate,
	 ICEParameters,
	 []mediaSection,
	 ICEGatheringState,
	 *string,
	 uint32,
) (*sdp.SessionDescription, error) {
	var  error
	 := []DTLSFingerprint{}

	if  {
		 = 
	}

	 := "BUNDLE"
	 := 0

	 := bundleMatchFromRemote()
	 := func( string) {
		 += " " + 
		++
	}

	for ,  := range  {
		if .data && len(.transceivers) != 0 {
			return nil, errSDPMediaSectionMediaDataChanInvalid
		} else if ! && len(.transceivers) > 1 {
			return nil, errSDPMediaSectionMultipleTrackInvalid
		}

		 := true
		 :=  == 0
		if .data {
			if  = addDataMediaSection(
				,
				,
				,
				.id,
				,
				,
				,
				,
				,
			);  != nil {
				return nil, 
			}
		} else {
			,  = addTransceiverSDP(
				,
				,
				,
				,
				,
				.id,
				,
				,
				,
				,
				,
			)
			if  != nil {
				return nil, 
			}
		}

		if  {
			if (.id) {
				(.id)
			} else {
				.MediaDescriptions[len(.MediaDescriptions)-1].MediaName.Port = sdp.RangedPort{Value: 0}
			}
		}
	}

	if ! {
		for ,  := range  {
			.WithFingerprint(.Algorithm, strings.ToUpper(.Value))
		}
	}

	if  {
		// RFC 5245 S15.3
		 = .WithValueAttribute(sdp.AttrKeyICELite, "")
	}

	if  {
		 = .WithPropertyAttribute(sdp.AttrKeyExtMapAllowMixed)
	}

	if  > 0 {
		 = .WithValueAttribute(sdp.AttrKeyGroup, )
	}

	return , nil
}

func getMidValue( *sdp.MediaDescription) string {
	for ,  := range .Attributes {
		if .Key == "mid" {
			return .Value
		}
	}

	return ""
}

// SessionDescription contains a MediaSection with Multiple SSRCs, it is Plan-B.
func descriptionIsPlanB( *SessionDescription,  logging.LeveledLogger) bool {
	if  == nil || .parsed == nil {
		return false
	}

	// Store all MIDs that already contain a track
	 := map[string]bool{}

	for ,  := range trackDetailsFromSDP(, .parsed) {
		if ,  := [.mid];  {
			return true
		}
		[.mid] = true
	}

	return false
}

// SessionDescription contains a MediaSection with name `audio`, `video` or `data`
// If only one SSRC is set we can't know if it is Plan-B or Unified. If users have
// set fallback mode assume it is Plan-B.
func descriptionPossiblyPlanB( *SessionDescription) bool {
	if  == nil || .parsed == nil {
		return false
	}

	 := regexp.MustCompile(`(?i)^(audio|video|data)$`)
	for ,  := range .parsed.MediaDescriptions {
		if len(.FindStringSubmatch(getMidValue())) == 2 {
			return true
		}
	}

	return false
}

func getPeerDirection( *sdp.MediaDescription) RTPTransceiverDirection {
	for ,  := range .Attributes {
		if  := NewRTPTransceiverDirection(.Key);  != RTPTransceiverDirectionUnknown {
			return 
		}
	}

	return RTPTransceiverDirectionUnknown
}

func extractBundleID( *sdp.SessionDescription) string {
	,  := .Attribute(sdp.AttrKeyGroup)

	 := strings.Contains(, "BUNDLE")

	if ! {
		return ""
	}

	 := strings.Split(, " ")

	if len() < 2 {
		return ""
	}

	return [1]
}

func extractFingerprint( *sdp.SessionDescription) (string, string, error) { //nolint:gocognit,cyclop
	 := ""

	// Fingerprint on session level has highest priority
	if ,  := .Attribute("fingerprint");  {
		 = 
	}

	if  == "" { //nolint:nestif
		 := extractBundleID()
		if  != "" {
			// Locate the fingerprint of the bundled media section
			for ,  := range .MediaDescriptions {
				if ,  := .Attribute("mid");  {
					if  ==  &&  == "" {
						if ,  := .Attribute("fingerprint");  {
							 = 
						}
					}
				}
			}
		} else {
			// Take the fingerprint from the first media section which has one.
			// Note: According to Bundle spec each media section would have it's own transport
			//       with it's own cert and fingerprint each, so we would need to return a list.
			for ,  := range .MediaDescriptions {
				,  := .Attribute("fingerprint")
				if  &&  == "" {
					 = 
				}
			}
		}
	}

	if  == "" {
		return "", "", ErrSessionDescriptionNoFingerprint
	}

	 := strings.Split(, " ")
	if len() != 2 {
		return "", "", ErrSessionDescriptionInvalidFingerprint
	}

	return [1], [0], nil
}

// identifiedMediaDescription contains a MediaDescription with sdpMid and sdpMLineIndex.
type identifiedMediaDescription struct {
	MediaDescription *sdp.MediaDescription
	SDPMid           string
	SDPMLineIndex    uint16
}

func extractICEDetailsFromMedia(
	 *identifiedMediaDescription,
	 logging.LeveledLogger,
) (string, string, []ICECandidate, error) {
	 := ""
	 := ""
	 := []ICECandidate{}
	 := .MediaDescription

	if ,  := .Attribute("ice-ufrag");  {
		 = 
	}
	if ,  := .Attribute("ice-pwd");  {
		 = 
	}
	for ,  := range .Attributes {
		if .IsICECandidate() {
			,  := ice.UnmarshalCandidate(.Value)
			if  != nil {
				if errors.Is(, ice.ErrUnknownCandidateTyp) || errors.Is(, ice.ErrDetermineNetworkType) {
					.Warnf("Discarding remote candidate: %s", )

					continue
				}

				return "", "", nil, 
			}

			,  := newICECandidateFromICE(, .SDPMid, .SDPMLineIndex)
			if  != nil {
				return "", "", nil, 
			}

			 = append(, )
		}
	}

	return , , , nil
}

type sdpICEDetails struct {
	Ufrag      string
	Password   string
	Candidates []ICECandidate
}

func extractICEDetails(
	 *sdp.SessionDescription,
	 logging.LeveledLogger,
) (*sdpICEDetails, error) { // nolint:gocognit
	 := &sdpICEDetails{
		Candidates: []ICECandidate{},
	}

	// Ufrag and Pw are allow at session level and thus have highest prio
	if ,  := .Attribute("ice-ufrag");  {
		.Ufrag = 
	}
	if ,  := .Attribute("ice-pwd");  {
		.Password = 
	}

	,  := selectCandidateMediaSection()
	if  {
		, , ,  := extractICEDetailsFromMedia(, )
		if  != nil {
			return nil, 
		}

		if .Ufrag == "" &&  != "" {
			.Ufrag = 
			.Password = 
		}

		.Candidates = 
	}

	if .Ufrag == "" {
		return nil, ErrSessionDescriptionMissingIceUfrag
	} else if .Password == "" {
		return nil, ErrSessionDescriptionMissingIcePwd
	}

	return , nil
}

// Select the first media section or the first bundle section
// Currently Pion uses the first media section to gather candidates.
// https://github.com/pion/webrtc/pull/2950
func selectCandidateMediaSection( *sdp.SessionDescription) (
	 *identifiedMediaDescription,
	 bool,
) {
	 := extractBundleID()

	for ,  := range .MediaDescriptions {
		 := getMidValue()
		// If bundled, only take ICE detail from bundle master section
		if  != "" {
			if  ==  {
				return &identifiedMediaDescription{
					MediaDescription: ,
					SDPMid:           ,
					SDPMLineIndex:    uint16(), //nolint:gosec // G115
				}, true
			}
		} else {
			// For not-bundled, take ICE details from the first media section
			return &identifiedMediaDescription{
				MediaDescription: ,
				SDPMid:           ,
				SDPMLineIndex:    uint16(), //nolint:gosec // G115
			}, true
		}
	}

	return nil, false
}

func getByMid( string,  *SessionDescription) *sdp.MediaDescription {
	for ,  := range .parsed.MediaDescriptions {
		if ,  := .Attribute(sdp.AttrKeyMID);  &&  ==  {
			return 
		}
	}

	return nil
}

// haveDataChannel return MediaDescription with MediaName equal application.
func haveDataChannel( *SessionDescription) *sdp.MediaDescription {
	for ,  := range .parsed.MediaDescriptions {
		if .MediaName.Media == mediaSectionApplication {
			return 
		}
	}

	return nil
}

func codecsFromMediaDescription( *sdp.MediaDescription) ( []RTPCodecParameters,  error) {
	 := &sdp.SessionDescription{
		MediaDescriptions: []*sdp.MediaDescription{},
	}

	for ,  := range .MediaName.Formats {
		,  := strconv.ParseUint(, 10, 8)
		if  != nil {
			return nil, 
		}

		,  := .GetCodecForPayloadType(uint8())
		if  != nil {
			if  == 0 {
				continue
			}

			return nil, 
		}

		 := uint16(0)
		,  := strconv.ParseUint(.EncodingParameters, 10, 16)
		if  == nil {
			 = uint16()
		}

		 := []RTCPFeedback{}
		for ,  := range .RTCPFeedback {
			 := strings.Split(, " ")
			 := RTCPFeedback{Type: [0]}
			if len() == 2 {
				.Parameter = [1]
			}

			 = append(, )
		}

		 = append(, RTPCodecParameters{
			RTPCodecCapability: RTPCodecCapability{
				.MediaName.Media + "/" + .Name,
				.ClockRate,
				,
				.Fmtp,
				,
			},
			PayloadType: PayloadType(),
		})
	}

	return , nil
}

func rtpExtensionsFromMediaDescription( *sdp.MediaDescription) (map[string]int, error) {
	 := map[string]int{}

	for ,  := range .Attributes {
		if .Key == sdp.AttrKeyExtMap {
			 := sdp.ExtMap{}
			if  := .Unmarshal(.String());  != nil {
				return nil, 
			}

			[.URI.String()] = .Value
		}
	}

	return , nil
}

// updateSDPOrigin saves sdp.Origin in PeerConnection when creating 1st local SDP;
// for subsequent calling, it updates Origin for SessionDescription from saved one
// and increments session version by one.
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-25#section-5.2.2
func updateSDPOrigin( *sdp.Origin,  *sdp.SessionDescription) {
	if atomic.CompareAndSwapUint64(&.SessionVersion, 0, .Origin.SessionVersion) { // store
		atomic.StoreUint64(&.SessionID, .Origin.SessionID)
	} else { // load
		for { // awaiting for saving session id
			.Origin.SessionID = atomic.LoadUint64(&.SessionID)
			if .Origin.SessionID != 0 {
				break
			}
		}
		.Origin.SessionVersion = atomic.AddUint64(&.SessionVersion, 1)
	}
}

func isIceLiteSet( *sdp.SessionDescription) bool {
	for ,  := range .Attributes {
		if strings.TrimSpace(.Key) == sdp.AttrKeyICELite {
			return true
		}
	}

	return false
}

func isExtMapAllowMixedSet( *sdp.SessionDescription) bool {
	for ,  := range .Attributes {
		if strings.TrimSpace(.Key) == sdp.AttrKeyExtMapAllowMixed {
			return true
		}
	}

	return false
}

func getMaxMessageSize( *sdp.MediaDescription) uint32 {
	for ,  := range .Attributes {
		if strings.TrimSpace(.Key) == "max-message-size" {
			if ,  := strconv.ParseUint(.Value, 10, 32);  == nil {
				return uint32()
			}
		}
	}

	return 0
}