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

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

package webrtc

import (
	
	
	
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
	
)

// PeerConnection represents a WebRTC connection that establishes a
// peer-to-peer communications with another PeerConnection instance in a
// browser, or to another endpoint implementing the required protocols.
type PeerConnection struct {
	statsID string
	mu      sync.RWMutex

	sdpOrigin sdp.Origin

	// ops is an operations queue which will ensure the enqueued actions are
	// executed in order. It is used for asynchronously, but serially processing
	// remote and local descriptions
	ops *operations

	configuration Configuration

	currentLocalDescription  *SessionDescription
	pendingLocalDescription  *SessionDescription
	currentRemoteDescription *SessionDescription
	pendingRemoteDescription *SessionDescription
	signalingState           SignalingState
	iceConnectionState       atomic.Value // ICEConnectionState
	connectionState          atomic.Value // PeerConnectionState

	idpLoginURL *string

	isClosed                                *atomicBool
	isGracefullyClosingOrClosed             bool
	isCloseDone                             chan struct{}
	isGracefulCloseDone                     chan struct{}
	isNegotiationNeeded                     *atomicBool
	updateNegotiationNeededFlagOnEmptyChain *atomicBool

	lastOffer  string
	lastAnswer string

	// a value containing the last known greater mid value
	// we internally generate mids as numbers. Needed since JSEP
	// requires that when reusing a media section a new unique mid
	// should be defined (see JSEP 3.4.1).
	greaterMid int

	rtpTransceivers        []*RTPTransceiver
	nonMediaBandwidthProbe atomic.Value // RTPReceiver

	onSignalingStateChangeHandler     func(SignalingState)
	onICEConnectionStateChangeHandler atomic.Value // func(ICEConnectionState)
	onConnectionStateChangeHandler    atomic.Value // func(PeerConnectionState)
	onTrackHandler                    func(*TrackRemote, *RTPReceiver)
	onDataChannelHandler              func(*DataChannel)
	onNegotiationNeededHandler        atomic.Value // func()

	iceGatherer   *ICEGatherer
	iceTransport  *ICETransport
	dtlsTransport *DTLSTransport
	sctpTransport *SCTPTransport

	// A reference to the associated API state used by this connection
	api *API
	log logging.LeveledLogger

	interceptorRTCPWriter interceptor.RTCPWriter
}

// NewPeerConnection creates a PeerConnection with the default codecs and interceptors.
//
// If you wish to customize the set of available codecs and/or the set of active interceptors,
// create an API with a custom MediaEngine and/or interceptor.Registry,
// then call [(*API).NewPeerConnection] instead of this function.
func ( Configuration) (*PeerConnection, error) {
	 := NewAPI()

	return .NewPeerConnection()
}

// NewPeerConnection creates a new PeerConnection with the provided configuration against the received API object.
// This method will attach a default set of codecs and interceptors to
// the resulting PeerConnection.  If this behavior is not desired,
// set the set of codecs and interceptors explicitly by using
// [WithMediaEngine] and [WithInterceptorRegistry] when calling [NewAPI].
func ( *API) ( Configuration) (*PeerConnection, error) {
	// https://w3c.github.io/webrtc-pc/#constructor (Step #2)
	// Some variables defined explicitly despite their implicit zero values to
	// allow better readability to understand what is happening.

	 := &PeerConnection{
		statsID: fmt.Sprintf("PeerConnection-%d", time.Now().UnixNano()),
		configuration: Configuration{
			ICEServers:           []ICEServer{},
			ICETransportPolicy:   ICETransportPolicyAll,
			BundlePolicy:         BundlePolicyBalanced,
			RTCPMuxPolicy:        RTCPMuxPolicyRequire,
			Certificates:         []Certificate{},
			ICECandidatePoolSize: 0,
		},
		isClosed:                                &atomicBool{},
		isCloseDone:                             make(chan struct{}),
		isGracefulCloseDone:                     make(chan struct{}),
		isNegotiationNeeded:                     &atomicBool{},
		updateNegotiationNeededFlagOnEmptyChain: &atomicBool{},
		lastOffer:                               "",
		lastAnswer:                              "",
		greaterMid:                              -1,
		signalingState:                          SignalingStateStable,

		api: ,
		log: .settingEngine.LoggerFactory.NewLogger("pc"),
	}
	.ops = newOperations(.updateNegotiationNeededFlagOnEmptyChain, .onNegotiationNeeded)

	.iceConnectionState.Store(ICEConnectionStateNew)
	.connectionState.Store(PeerConnectionStateNew)

	,  := .interceptorRegistry.Build("")
	if  != nil {
		return nil, 
	}

	.api = &API{
		settingEngine: .settingEngine,
		interceptor:   ,
	}

	if .settingEngine.disableMediaEngineCopy {
		.api.mediaEngine = .mediaEngine
	} else {
		.api.mediaEngine = .mediaEngine.copy()
		.api.mediaEngine.setMultiCodecNegotiation(!.settingEngine.disableMediaEngineMultipleCodecs)
	}

	if  = .initConfiguration();  != nil {
		return nil, 
	}

	.iceGatherer,  = .createICEGatherer()
	if  != nil {
		return nil, 
	}

	// Create the ice transport
	 := .createICETransport()
	.iceTransport = 

	// Create the DTLS transport
	,  := .api.NewDTLSTransport(.iceTransport, .configuration.Certificates)
	if  != nil {
		return nil, 
	}
	.dtlsTransport = 

	// Create the SCTP transport
	.sctpTransport = .api.NewSCTPTransport(.dtlsTransport)

	// Wire up the on datachannel handler
	.sctpTransport.OnDataChannel(func( *DataChannel) {
		.mu.RLock()
		 := .onDataChannelHandler
		.mu.RUnlock()
		if  != nil {
			()
		}
	})

	.interceptorRTCPWriter = .api.interceptor.BindRTCPWriter(interceptor.RTCPWriterFunc(.writeRTCP))

	return , nil
}

// initConfiguration defines validation of the specified Configuration and
// its assignment to the internal configuration variable. This function differs
// from its SetConfiguration counterpart because most of the checks do not
// include verification statements related to the existing state. Thus the
// function describes only minor verification of some the struct variables.
func ( *PeerConnection) ( Configuration) error { //nolint:cyclop
	if .PeerIdentity != "" {
		.configuration.PeerIdentity = .PeerIdentity
	}

	// https://www.w3.org/TR/webrtc/#constructor (step #3)
	if len(.Certificates) > 0 {
		 := time.Now()
		for ,  := range .Certificates {
			if !.Expires().IsZero() && .After(.Expires()) {
				return &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}
			}
			.configuration.Certificates = append(.configuration.Certificates, )
		}
	} else {
		,  := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
		if  != nil {
			return &rtcerr.UnknownError{Err: }
		}
		,  := GenerateCertificate()
		if  != nil {
			return 
		}
		.configuration.Certificates = []Certificate{*}
	}

	if .BundlePolicy != BundlePolicyUnknown {
		.configuration.BundlePolicy = .BundlePolicy
	}

	if .RTCPMuxPolicy != RTCPMuxPolicyUnknown {
		.configuration.RTCPMuxPolicy = .RTCPMuxPolicy
	}

	if .ICECandidatePoolSize != 0 {
		.configuration.ICECandidatePoolSize = .ICECandidatePoolSize
	}

	.configuration.ICETransportPolicy = .ICETransportPolicy
	.configuration.SDPSemantics = .SDPSemantics

	 := .getICEServers()
	if len() > 0 {
		for ,  := range  {
			if  := .validate();  != nil {
				return 
			}
		}
		.configuration.ICEServers = 
	}

	return nil
}

// OnSignalingStateChange sets an event handler which is invoked when the
// peer connection's signaling state changes.
func ( *PeerConnection) ( func(SignalingState)) {
	.mu.Lock()
	defer .mu.Unlock()
	.onSignalingStateChangeHandler = 
}

func ( *PeerConnection) ( SignalingState) {
	.mu.RLock()
	 := .onSignalingStateChangeHandler
	.mu.RUnlock()

	.log.Infof("signaling state changed to %s", )
	if  != nil {
		go ()
	}
}

// OnDataChannel sets an event handler which is invoked when a data
// channel message arrives from a remote peer.
func ( *PeerConnection) ( func(*DataChannel)) {
	.mu.Lock()
	defer .mu.Unlock()
	.onDataChannelHandler = 
}

// OnNegotiationNeeded sets an event handler which is invoked when
// a change has occurred which requires session negotiation.
func ( *PeerConnection) ( func()) {
	.onNegotiationNeededHandler.Store()
}

// onNegotiationNeeded enqueues negotiationNeededOp if necessary
// caller of this method should hold `pc.mu` lock
// https://www.w3.org/TR/webrtc/#dfn-update-the-negotiation-needed-flag
func ( *PeerConnection) () {
	// 4.7.3.1 If the length of connection.[[Operations]] is not 0, then set
	// connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and abort these steps.
	if !.ops.IsEmpty() {
		.updateNegotiationNeededFlagOnEmptyChain.set(true)

		return
	}
	.ops.Enqueue(.negotiationNeededOp)
}

// https://www.w3.org/TR/webrtc/#dfn-update-the-negotiation-needed-flag
func ( *PeerConnection) () {
	// 4.7.3.2.1 If connection.[[IsClosed]] is true, abort these steps.
	if .isClosed.get() {
		return
	}

	// 4.7.3.2.2 If the length of connection.[[Operations]] is not 0,
	// then set connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to
	// true, and abort these steps.
	if !.ops.IsEmpty() {
		.updateNegotiationNeededFlagOnEmptyChain.set(true)

		return
	}

	// 4.7.3.2.3 If connection's signaling state is not "stable", abort these steps.
	if .SignalingState() != SignalingStateStable {
		return
	}

	// 4.7.3.2.4 If the result of checking if negotiation is needed is false,
	// clear the negotiation-needed flag by setting connection.[[NegotiationNeeded]]
	// to false, and abort these steps.
	if !.checkNegotiationNeeded() {
		.isNegotiationNeeded.set(false)

		return
	}

	// 4.7.3.2.5 If connection.[[NegotiationNeeded]] is already true, abort these steps.
	if .isNegotiationNeeded.get() {
		return
	}

	// 4.7.3.2.6 Set connection.[[NegotiationNeeded]] to true.
	.isNegotiationNeeded.set(true)

	// 4.7.3.2.7 Fire an event named negotiationneeded at connection.
	if ,  := .onNegotiationNeededHandler.Load().(func());  &&  != nil {
		()
	}
}

func ( *PeerConnection) () bool { //nolint:gocognit,cyclop
	// To check if negotiation is needed for connection, perform the following checks:
	// Skip 1, 2 steps
	// Step 3
	.mu.Lock()
	defer .mu.Unlock()

	 := .currentLocalDescription
	 := .currentRemoteDescription

	if  == nil {
		return true
	}

	.sctpTransport.lock.Lock()
	 := len(.sctpTransport.dataChannels)
	.sctpTransport.lock.Unlock()

	if  != 0 && haveDataChannel() == nil {
		return true
	}

	for ,  := range .rtpTransceivers {
		// https://www.w3.org/TR/webrtc/#dfn-update-the-negotiation-needed-flag
		// Step 5.1
		// if t.stopping && !t.stopped {
		// 	return true
		// }
		 := getByMid(.Mid(), )

		// Step 5.2
		if  == nil {
			return true
		}

		// Step 5.3.1
		if .Direction() == RTPTransceiverDirectionSendrecv ||
			.Direction() == RTPTransceiverDirectionSendonly {
			,  := .Attribute(sdp.AttrKeyMsid)
			 := .Sender()
			if  == nil {
				return true
			}
			 := .Track()
			if  == nil {
				// Situation when sender's track is nil could happen when
				// a) replaceTrack(nil) is called
				// b) removeTrack() is called, changing the transceiver's direction to inactive
				// As t.Direction() in this branch is either sendrecv or sendonly, we believe (a) option is the case
				// As calling replaceTrack does not require renegotiation, we skip check for this transceiver
				continue
			}
			if ! ||  != .StreamID()+" "+.ID() {
				return true
			}
		}
		switch .Type {
		case SDPTypeOffer:
			// Step 5.3.2
			 := getByMid(.Mid(), )
			if  == nil {
				return true
			}

			if getPeerDirection() != .Direction() && getPeerDirection() != .Direction().Revers() {
				return true
			}
		case SDPTypeAnswer:
			// Step 5.3.3
			if ,  := .Attribute(.Direction().String()); ! {
				return true
			}
		default:
		}

		// Step 5.4
		// if t.stopped && t.Mid() != "" {
		// 	if getByMid(t.Mid(), localDesc) != nil || getByMid(t.Mid(), remoteDesc) != nil {
		// 		return true
		// 	}
		// }
	}
	// Step 6
	return false
}

// OnICECandidate sets an event handler which is invoked when a new ICE
// candidate is found.
// ICE candidate gathering only begins when SetLocalDescription or
// SetRemoteDescription is called.
// Take note that the handler will be called with a nil pointer when
// gathering is finished.
func ( *PeerConnection) ( func(*ICECandidate)) {
	.iceGatherer.OnLocalCandidate()
}

// OnICEGatheringStateChange sets an event handler which is invoked when the
// ICE candidate gathering state has changed.
func ( *PeerConnection) ( func(ICEGatheringState)) {
	.iceGatherer.OnStateChange(
		func( ICEGathererState) {
			switch  {
			case ICEGathererStateGathering:
				(ICEGatheringStateGathering)
			case ICEGathererStateComplete:
				(ICEGatheringStateComplete)
			default:
				// Other states ignored
			}
		})
}

// OnTrack sets an event handler which is called when remote track
// arrives from a remote peer.
func ( *PeerConnection) ( func(*TrackRemote, *RTPReceiver)) {
	.mu.Lock()
	defer .mu.Unlock()
	.onTrackHandler = 
}

func ( *PeerConnection) ( *TrackRemote,  *RTPReceiver) {
	.mu.RLock()
	 := .onTrackHandler
	.mu.RUnlock()

	.log.Debugf("got new track: %+v", )
	if  != nil {
		if  != nil {
			go (, )
		} else {
			.log.Warnf("OnTrack unset, unable to handle incoming media streams")
		}
	}
}

// OnICEConnectionStateChange sets an event handler which is called
// when an ICE connection state is changed.
func ( *PeerConnection) ( func(ICEConnectionState)) {
	.onICEConnectionStateChangeHandler.Store()
}

func ( *PeerConnection) ( ICEConnectionState) {
	.iceConnectionState.Store()
	.log.Infof("ICE connection state changed: %s", )
	if ,  := .onICEConnectionStateChangeHandler.Load().(func(ICEConnectionState));  &&  != nil {
		()
	}
}

// OnConnectionStateChange sets an event handler which is called
// when the PeerConnectionState has changed.
func ( *PeerConnection) ( func(PeerConnectionState)) {
	.onConnectionStateChangeHandler.Store()
}

func ( *PeerConnection) ( PeerConnectionState) {
	.connectionState.Store()
	.log.Infof("peer connection state changed: %s", )
	if ,  := .onConnectionStateChangeHandler.Load().(func(PeerConnectionState));  &&  != nil {
		go ()
	}
}

// SetConfiguration updates the configuration of this PeerConnection object.
func ( *PeerConnection) ( Configuration) error { //nolint:gocognit,cyclop
	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2)
	if .isClosed.get() {
		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #3)
	if .PeerIdentity != "" {
		if .PeerIdentity != .configuration.PeerIdentity {
			return &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity}
		}
		.configuration.PeerIdentity = .PeerIdentity
	}

	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #4)
	if len(.Certificates) > 0 {
		if len(.Certificates) != len(.configuration.Certificates) {
			return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}
		}

		for ,  := range .Certificates {
			if !.configuration.Certificates[].Equals() {
				return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}
			}
		}
		.configuration.Certificates = .Certificates
	}

	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #5)
	if .BundlePolicy != BundlePolicyUnknown {
		if .BundlePolicy != .configuration.BundlePolicy {
			return &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy}
		}
		.configuration.BundlePolicy = .BundlePolicy
	}

	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #6)
	if .RTCPMuxPolicy != RTCPMuxPolicyUnknown {
		if .RTCPMuxPolicy != .configuration.RTCPMuxPolicy {
			return &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy}
		}
		.configuration.RTCPMuxPolicy = .RTCPMuxPolicy
	}

	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #7)
	if .ICECandidatePoolSize != 0 {
		if .configuration.ICECandidatePoolSize != .ICECandidatePoolSize &&
			.LocalDescription() != nil {
			return &rtcerr.InvalidModificationError{Err: ErrModifyingICECandidatePoolSize}
		}
		.configuration.ICECandidatePoolSize = .ICECandidatePoolSize
	}

	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #8)
	.configuration.ICETransportPolicy = .ICETransportPolicy

	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11)
	if len(.ICEServers) > 0 {
		// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3)
		for ,  := range .ICEServers {
			if  := .validate();  != nil {
				return 
			}
		}
		.configuration.ICEServers = .ICEServers
	}

	return nil
}

// GetConfiguration returns a Configuration object representing the current
// configuration of this PeerConnection object. The returned object is a
// copy and direct mutation on it will not take affect until SetConfiguration
// has been called with Configuration passed as its only argument.
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration
func ( *PeerConnection) () Configuration {
	return .configuration
}

func ( *PeerConnection) () string {
	.mu.RLock()
	defer .mu.RUnlock()

	return .statsID
}

// hasLocalDescriptionChanged returns whether local media (rtpTransceivers) has changed
// caller of this method should hold `pc.mu` lock.
func ( *PeerConnection) ( *SessionDescription) bool {
	for ,  := range .rtpTransceivers {
		 := getByMid(.Mid(), )
		if  == nil {
			return true
		}

		if getPeerDirection() != .Direction() {
			return true
		}
	}

	return false
}

// CreateOffer starts the PeerConnection and generates the localDescription
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer
//
//nolint:gocognit,cyclop
func ( *PeerConnection) ( *OfferOptions) (SessionDescription, error) {
	 := .idpLoginURL != nil
	switch {
	case :
		return SessionDescription{}, errIdentityProviderNotImplemented
	case .isClosed.get():
		return SessionDescription{}, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	if  != nil && .ICERestart {
		if  := .iceTransport.restart();  != nil {
			return SessionDescription{}, 
		}
	}

	var (
		 *sdp.SessionDescription
		 SessionDescription
		   error
	)

	// This may be necessary to recompute if, for example, createOffer was called when only an
	// audio RTCRtpTransceiver was added to connection, but while performing the in-parallel
	// steps to create an offer, a video RTCRtpTransceiver was added, requiring additional
	// inspection of video system resources.
	 := 0
	.mu.Lock()
	defer .mu.Unlock()
	for {
		// We cache current transceivers to ensure they aren't
		// mutated during offer generation. We later check if they have
		// been mutated and recompute the offer if necessary.
		 := .rtpTransceivers

		// in-parallel steps to create an offer
		// https://w3c.github.io/webrtc-pc/#dfn-in-parallel-steps-to-create-an-offer
		 := .configuration.SDPSemantics == SDPSemanticsPlanB
		if .currentRemoteDescription != nil &&  {
			 = descriptionPossiblyPlanB(.currentRemoteDescription)
		}

		// include unmatched local transceivers
		if ! { //nolint:nestif
			// update the greater mid if the remote description provides a greater one
			if .currentRemoteDescription != nil {
				var  int
				for ,  := range .currentRemoteDescription.parsed.MediaDescriptions {
					 := getMidValue()
					if  == "" {
						continue
					}
					,  = strconv.Atoi()
					if  != nil {
						continue
					}
					if  > .greaterMid {
						.greaterMid = 
					}
				}
			}
			for ,  := range  {
				if  := .Mid();  != "" {
					,  := strconv.Atoi()
					if  == nil {
						if  > .greaterMid {
							.greaterMid = 
						}
					}

					continue
				}
				.greaterMid++
				 = .SetMid(strconv.Itoa(.greaterMid))
				if  != nil {
					return SessionDescription{}, 
				}
			}
		}

		if .currentRemoteDescription == nil {
			,  = .generateUnmatchedSDP(, )
		} else {
			,  = .generateMatchedSDP(
				,
				,
				true, /*includeUnmatched */
				connectionRoleFromDtlsRole(defaultDtlsRoleOffer),
			)
		}

		if  != nil {
			return SessionDescription{}, 
		}

		updateSDPOrigin(&.sdpOrigin, )
		,  := .Marshal()
		if  != nil {
			return SessionDescription{}, 
		}

		 = SessionDescription{
			Type:   SDPTypeOffer,
			SDP:    string(),
			parsed: ,
		}

		// Verify local media hasn't changed during offer
		// generation. Recompute if necessary
		if  || !.hasLocalDescriptionChanged(&) {
			break
		}
		++
		if  >= 128 {
			return SessionDescription{}, errExcessiveRetries
		}
	}

	.lastOffer = .SDP

	return , nil
}

func ( *PeerConnection) () (*ICEGatherer, error) {
	,  := .api.NewICEGatherer(ICEGatherOptions{
		ICEServers:      .configuration.getICEServers(),
		ICEGatherPolicy: .configuration.ICETransportPolicy,
	})
	if  != nil {
		return nil, 
	}

	return , nil
}

// Update the PeerConnectionState given the state of relevant transports
// https://www.w3.org/TR/webrtc/#rtcpeerconnectionstate-enum
//
//nolint:cyclop
func ( *PeerConnection) (
	 ICEConnectionState,
	 DTLSTransportState,
) {
	 := PeerConnectionStateNew
	switch {
	// The RTCPeerConnection object's [[IsClosed]] slot is true.
	case .isClosed.get():
		 = PeerConnectionStateClosed

	// Any of the RTCIceTransports or RTCDtlsTransports are in a "failed" state.
	case  == ICEConnectionStateFailed ||  == DTLSTransportStateFailed:
		 = PeerConnectionStateFailed

	// Any of the RTCIceTransports or RTCDtlsTransports are in the "disconnected"
	// state and none of them are in the "failed" or "connecting" or "checking" state.  */
	case  == ICEConnectionStateDisconnected:
		 = PeerConnectionStateDisconnected

	// None of the previous states apply and all RTCIceTransports are in the "new" or "closed" state,
	// and all RTCDtlsTransports are in the "new" or "closed" state, or there are no transports.
	case ( == ICEConnectionStateNew ||  == ICEConnectionStateClosed) &&
		( == DTLSTransportStateNew ||  == DTLSTransportStateClosed):
		 = PeerConnectionStateNew

	// None of the previous states apply and any RTCIceTransport is in the "new" or "checking" state or
	// any RTCDtlsTransport is in the "new" or "connecting" state.
	case ( == ICEConnectionStateNew ||  == ICEConnectionStateChecking) ||
		( == DTLSTransportStateNew ||  == DTLSTransportStateConnecting):
		 = PeerConnectionStateConnecting

	// All RTCIceTransports and RTCDtlsTransports are in the "connected", "completed" or "closed"
	// state and all RTCDtlsTransports are in the "connected" or "closed" state.
	case ( == ICEConnectionStateConnected ||
		 == ICEConnectionStateCompleted ||  == ICEConnectionStateClosed) &&
		( == DTLSTransportStateConnected ||  == DTLSTransportStateClosed):
		 = PeerConnectionStateConnected
	}

	if .connectionState.Load() ==  {
		return
	}

	.onConnectionStateChange()
}

func ( *PeerConnection) () *ICETransport {
	 := .api.NewICETransport(.iceGatherer)
	.internalOnConnectionStateChangeHandler.Store(func( ICETransportState) {
		var  ICEConnectionState
		switch  {
		case ICETransportStateNew:
			 = ICEConnectionStateNew
		case ICETransportStateChecking:
			 = ICEConnectionStateChecking
		case ICETransportStateConnected:
			 = ICEConnectionStateConnected
		case ICETransportStateCompleted:
			 = ICEConnectionStateCompleted
		case ICETransportStateFailed:
			 = ICEConnectionStateFailed
		case ICETransportStateDisconnected:
			 = ICEConnectionStateDisconnected
		case ICETransportStateClosed:
			 = ICEConnectionStateClosed
		default:
			.log.Warnf("OnConnectionStateChange: unhandled ICE state: %s", )

			return
		}
		.onICEConnectionStateChange()
		.updateConnectionState(, .dtlsTransport.State())
	})

	return 
}

// CreateAnswer starts the PeerConnection and generates the localDescription.
//
//nolint:cyclop
func ( *PeerConnection) (*AnswerOptions) (SessionDescription, error) {
	 := .idpLoginURL != nil
	 := .RemoteDescription()
	switch {
	case  == nil:
		return SessionDescription{}, &rtcerr.InvalidStateError{Err: ErrNoRemoteDescription}
	case :
		return SessionDescription{}, errIdentityProviderNotImplemented
	case .isClosed.get():
		return SessionDescription{}, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	case .signalingState.Get() != SignalingStateHaveRemoteOffer &&
		.signalingState.Get() != SignalingStateHaveLocalPranswer:
		return SessionDescription{}, &rtcerr.InvalidStateError{Err: ErrIncorrectSignalingState}
	}

	 := connectionRoleFromDtlsRole(.api.settingEngine.answeringDTLSRole)
	if  == sdp.ConnectionRole(0) {
		 = connectionRoleFromDtlsRole(defaultDtlsRoleAnswer)

		// If one of the agents is lite and the other one is not, the lite agent must be the controlled agent.
		// If both or neither agents are lite the offering agent is controlling.
		// RFC 8445 S6.1.1
		if isIceLiteSet(.parsed) && !.api.settingEngine.candidates.ICELite {
			 = connectionRoleFromDtlsRole(DTLSRoleServer)
		}
	}
	.mu.Lock()
	defer .mu.Unlock()

	,  := .generateMatchedSDP(.rtpTransceivers, , false /*includeUnmatched */, )
	if  != nil {
		return SessionDescription{}, 
	}

	updateSDPOrigin(&.sdpOrigin, )
	,  := .Marshal()
	if  != nil {
		return SessionDescription{}, 
	}

	 := SessionDescription{
		Type:   SDPTypeAnswer,
		SDP:    string(),
		parsed: ,
	}
	.lastAnswer = .SDP

	return , nil
}

// 4.4.1.6 Set the SessionDescription
//
//nolint:gocognit,cyclop
func ( *PeerConnection) ( *SessionDescription,  stateChangeOp) error {
	switch {
	case .isClosed.get():
		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	case NewSDPType(.Type.String()) == SDPTypeUnknown:
		return &rtcerr.TypeError{
			Err: fmt.Errorf("%w: '%d' is not a valid enum value of type SDPType", errPeerConnSDPTypeInvalidValue, .Type),
		}
	}

	,  := func() (SignalingState, error) {
		.mu.Lock()
		defer .mu.Unlock()

		 := .SignalingState()
		 := stateChangeOpSetLocal
		 := stateChangeOpSetRemote
		 := &rtcerr.InvalidModificationError{Err: errSDPDoesNotMatchOffer}
		 := &rtcerr.InvalidModificationError{Err: errSDPDoesNotMatchAnswer}

		var  SignalingState
		var  error
		switch  {
		case :
			switch .Type {
			// stable->SetLocal(offer)->have-local-offer
			case SDPTypeOffer:
				if .SDP != .lastOffer {
					return , 
				}
				,  = checkNextSignalingState(, SignalingStateHaveLocalOffer, , .Type)
				if  == nil {
					.pendingLocalDescription = 
				}
			// have-remote-offer->SetLocal(answer)->stable
			// have-local-pranswer->SetLocal(answer)->stable
			case SDPTypeAnswer:
				if .SDP != .lastAnswer {
					return , 
				}
				,  = checkNextSignalingState(, SignalingStateStable, , .Type)
				if  == nil {
					.currentLocalDescription = 
					.currentRemoteDescription = .pendingRemoteDescription
					.pendingRemoteDescription = nil
					.pendingLocalDescription = nil
				}
			case SDPTypeRollback:
				,  = checkNextSignalingState(, SignalingStateStable, , .Type)
				if  == nil {
					.pendingLocalDescription = nil
				}
			// have-remote-offer->SetLocal(pranswer)->have-local-pranswer
			case SDPTypePranswer:
				if .SDP != .lastAnswer {
					return , 
				}
				,  = checkNextSignalingState(, SignalingStateHaveLocalPranswer, , .Type)
				if  == nil {
					.pendingLocalDescription = 
				}
			default:
				return , &rtcerr.OperationError{Err: fmt.Errorf("%w: %s(%s)", errPeerConnStateChangeInvalid, , .Type)}
			}
		case :
			switch .Type {
			// stable->SetRemote(offer)->have-remote-offer
			case SDPTypeOffer:
				,  = checkNextSignalingState(, SignalingStateHaveRemoteOffer, , .Type)
				if  == nil {
					.pendingRemoteDescription = 
				}
			// have-local-offer->SetRemote(answer)->stable
			// have-remote-pranswer->SetRemote(answer)->stable
			case SDPTypeAnswer:
				,  = checkNextSignalingState(, SignalingStateStable, , .Type)
				if  == nil {
					.currentRemoteDescription = 
					.currentLocalDescription = .pendingLocalDescription
					.pendingRemoteDescription = nil
					.pendingLocalDescription = nil
				}
			case SDPTypeRollback:
				,  = checkNextSignalingState(, SignalingStateStable, , .Type)
				if  == nil {
					.pendingRemoteDescription = nil
				}
			// have-local-offer->SetRemote(pranswer)->have-remote-pranswer
			case SDPTypePranswer:
				,  = checkNextSignalingState(, SignalingStateHaveRemotePranswer, , .Type)
				if  == nil {
					.pendingRemoteDescription = 
				}
			default:
				return , &rtcerr.OperationError{Err: fmt.Errorf("%w: %s(%s)", errPeerConnStateChangeInvalid, , .Type)}
			}
		default:
			return , &rtcerr.OperationError{Err: fmt.Errorf("%w: %q", errPeerConnStateChangeUnhandled, )}
		}

		return , 
	}()

	if  == nil {
		.signalingState.Set()
		if .signalingState.Get() == SignalingStateStable {
			.isNegotiationNeeded.set(false)
			.mu.Lock()
			.onNegotiationNeeded()
			.mu.Unlock()
		}
		.onSignalingStateChange()
	}

	return 
}

// SetLocalDescription sets the SessionDescription of the local peer
//
//nolint:cyclop
func ( *PeerConnection) ( SessionDescription) error {
	if .isClosed.get() {
		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	 := .currentLocalDescription != nil

	// JSEP 5.4
	if .SDP == "" {
		switch .Type {
		case SDPTypeAnswer, SDPTypePranswer:
			.SDP = .lastAnswer
		case SDPTypeOffer:
			.SDP = .lastOffer
		default:
			return &rtcerr.InvalidModificationError{
				Err: fmt.Errorf("%w: %s", errPeerConnSDPTypeInvalidValueSetLocalDescription, .Type),
			}
		}
	}

	.parsed = &sdp.SessionDescription{}
	if  := .parsed.UnmarshalString(.SDP);  != nil {
		return 
	}
	if  := .setDescription(&, stateChangeOpSetLocal);  != nil {
		return 
	}

	 := append([]*RTPTransceiver{}, .GetTransceivers()...)

	 := .Type == SDPTypeAnswer
	 := .RemoteDescription()
	if  &&  != nil {
		_ = setRTPTransceiverCurrentDirection(&, , false)
		if  := .startRTPSenders();  != nil {
			return 
		}
		.configureRTPReceivers(, , )
		.ops.Enqueue(func() {
			.startRTP(, , )
		})
	}

	,  := selectCandidateMediaSection(.parsed)
	if  {
		.iceGatherer.setMediaStreamIdentification(.SDPMid, .SDPMLineIndex)
	}

	if .iceGatherer.State() == ICEGathererStateNew {
		return .iceGatherer.Gather()
	}

	return nil
}

// LocalDescription returns PendingLocalDescription if it is not null and
// otherwise it returns CurrentLocalDescription. This property is used to
// determine if SetLocalDescription has already been called.
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription
func ( *PeerConnection) () *SessionDescription {
	if  := .PendingLocalDescription();  != nil {
		return 
	}

	return .CurrentLocalDescription()
}

// SetRemoteDescription sets the SessionDescription of the remote peer
//
//nolint:gocognit,gocyclo,cyclop,maintidx
func ( *PeerConnection) ( SessionDescription) error {
	if .isClosed.get() {
		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	 := .currentRemoteDescription != nil

	if ,  := .Unmarshal();  != nil {
		return 
	}
	if  := .setDescription(&, stateChangeOpSetRemote);  != nil {
		return 
	}

	if  := .api.mediaEngine.updateFromRemoteDescription(*.parsed);  != nil {
		return 
	}

	// Disable RTX/FEC on RTPSenders if the remote didn't support it
	for ,  := range .GetSenders() {
		.configureRTXAndFEC()
	}

	var  *RTPTransceiver
	 := append([]*RTPTransceiver{}, .GetTransceivers()...)
	 := descriptionIsPlanB(.RemoteDescription(), .log)
	if .configuration.SDPSemantics != SDPSemanticsUnifiedPlan {
		 = descriptionPossiblyPlanB(.RemoteDescription())
	}

	 := .Type == SDPTypeAnswer

	if ! && ! { //nolint:nestif
		for ,  := range .RemoteDescription().parsed.MediaDescriptions {
			 := getMidValue()
			if  == "" {
				return errPeerConnRemoteDescriptionWithoutMidValue
			}

			if .MediaName.Media == mediaSectionApplication {
				continue
			}

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

			,  = findByMid(, )
			if  == nil {
				,  = satisfyTypeAndDirection(, , )
			} else if  == RTPTransceiverDirectionInactive {
				if  := .Stop();  != nil {
					return 
				}
			}

			switch {
			case  == nil:
				,  := .api.NewRTPReceiver(, .dtlsTransport)
				if  != nil {
					return 
				}

				 := RTPTransceiverDirectionRecvonly
				if  == RTPTransceiverDirectionRecvonly {
					 = RTPTransceiverDirectionSendonly
				} else if  == RTPTransceiverDirectionInactive {
					 = RTPTransceiverDirectionInactive
				}

				 = newRTPTransceiver(, nil, , , .api)
				.mu.Lock()
				.addRTPTransceiver()
				.mu.Unlock()

				// if transceiver is create by remote sdp, set prefer codec same as remote peer
				if ,  := codecsFromMediaDescription();  == nil {
					 := []RTPCodecParameters{}
					for ,  := range  {
						if ,  := codecParametersFuzzySearch(
							,
							.api.mediaEngine.getCodecsByKind(),
						);  == codecMatchExact {
							// if codec match exact, use payloadtype register to mediaengine
							.PayloadType = .PayloadType
							 = append(, )
						}
					}
					_ = .SetCodecPreferences()
				}

			case  == RTPTransceiverDirectionRecvonly:
				if .Direction() == RTPTransceiverDirectionSendrecv {
					.setDirection(RTPTransceiverDirectionSendonly)
				} else if .Direction() == RTPTransceiverDirectionRecvonly {
					.setDirection(RTPTransceiverDirectionInactive)
				}
			case  == RTPTransceiverDirectionSendrecv:
				if .Direction() == RTPTransceiverDirectionSendonly {
					.setDirection(RTPTransceiverDirectionSendrecv)
				} else if .Direction() == RTPTransceiverDirectionInactive {
					.setDirection(RTPTransceiverDirectionRecvonly)
				}
			case  == RTPTransceiverDirectionSendonly:
				if .Direction() == RTPTransceiverDirectionInactive {
					.setDirection(RTPTransceiverDirectionRecvonly)
				}
			}

			if .Mid() == "" {
				if  := .SetMid();  != nil {
					return 
				}
			}
		}
	}

	,  := extractICEDetails(.parsed, .log)
	if  != nil {
		return 
	}

	if  && .iceTransport.haveRemoteCredentialsChange(.Ufrag, .Password) {
		// An ICE Restart only happens implicitly for a SetRemoteDescription of type offer
		if ! {
			if  = .iceTransport.restart();  != nil {
				return 
			}
		}

		if  = .iceTransport.setRemoteCredentials(.Ufrag, .Password);  != nil {
			return 
		}
	}

	for  := range .Candidates {
		if  = .iceTransport.AddRemoteCandidate(&.Candidates[]);  != nil {
			return 
		}
	}

	 := append([]*RTPTransceiver{}, .GetTransceivers()...)

	if  {
		if  {
			_ = setRTPTransceiverCurrentDirection(&, , true)
			if  = .startRTPSenders();  != nil {
				return 
			}
			.configureRTPReceivers(true, &, )
			.ops.Enqueue(func() {
				.startRTP(true, &, )
			})
		}

		return nil
	}

	 := isIceLiteSet(.parsed)

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

	 := ICERoleControlled
	// If one of the agents is lite and the other one is not, the lite agent must be the controlled agent.
	// If both or neither agents are lite the offering agent is controlling.
	// RFC 8445 S6.1.1
	if ( &&  == .api.settingEngine.candidates.ICELite) ||
		( && !.api.settingEngine.candidates.ICELite) {
		 = ICERoleControlling
	}

	// Start the networking in a new routine since it will block until
	// the connection is actually established.
	if  {
		_ = setRTPTransceiverCurrentDirection(&, , true)
		if  := .startRTPSenders();  != nil {
			return 
		}

		.configureRTPReceivers(false, &, )
	}

	.ops.Enqueue(func() {
		.startTransports(
			,
			dtlsRoleFromRemoteSDP(.parsed),
			.Ufrag,
			.Password,
			,
			,
		)
		if  {
			.startRTP(false, &, )
		}
	})

	return nil
}

func ( *PeerConnection) ( trackDetails,  *RTPReceiver) {
	.configureReceive(trackDetailsToRTPReceiveParameters(&))

	// set track id and label early so they can be set as new track information
	// is received from the SDP.
	for  := range .tracks {
		.tracks[].track.mu.Lock()
		.tracks[].track.id = .id
		.tracks[].track.streamID = .streamID
		.tracks[].track.mu.Unlock()
	}
}

func ( *PeerConnection) ( trackDetails,  *RTPReceiver) {
	if  := .startReceive(trackDetailsToRTPReceiveParameters(&));  != nil {
		.log.Warnf("RTPReceiver Receive failed %s", )

		return
	}

	for ,  := range .Tracks() {
		if .SSRC() == 0 || .RID() != "" {
			return
		}

		if .api.settingEngine.fireOnTrackBeforeFirstRTP {
			.onTrack(, )

			return
		}
		go func( *TrackRemote) {
			 := make([]byte, .api.settingEngine.getReceiveMTU())
			, ,  := .peek()
			if  != nil {
				.log.Warnf("Could not determine PayloadType for SSRC %d (%s)", .SSRC(), )

				return
			}

			if  = .checkAndUpdateTrack([:]);  != nil {
				.log.Warnf("Failed to set codec settings for track SSRC %d (%s)", .SSRC(), )

				return
			}

			.onTrack(, )
		}()
	}
}

//nolint:cyclop
func setRTPTransceiverCurrentDirection(
	 *SessionDescription,
	 []*RTPTransceiver,
	 bool,
) error {
	 = append([]*RTPTransceiver{}, ...)
	for ,  := range .parsed.MediaDescriptions {
		 := getMidValue()
		if  == "" {
			return errPeerConnRemoteDescriptionWithoutMidValue
		}

		if .MediaName.Media == mediaSectionApplication {
			continue
		}

		var  *RTPTransceiver
		,  = findByMid(, )

		if  == nil {
			return fmt.Errorf("%w: %q", errPeerConnTranscieverMidNil, )
		}

		 := getPeerDirection()
		if  == RTPTransceiverDirectionUnknown {
			continue
		}

		// reverse direction if it was a remote answer
		if  {
			switch  {
			case RTPTransceiverDirectionSendonly:
				 = RTPTransceiverDirectionRecvonly
			case RTPTransceiverDirectionRecvonly:
				 = RTPTransceiverDirectionSendonly
			default:
			}
		}

		// If a transceiver is created by applying a remote description that has recvonly transceiver,
		// it will have no sender. In this case, the transceiver's current direction is set to inactive so
		// that the transceiver can be reused by next AddTrack.
		if ! &&  == RTPTransceiverDirectionSendonly && .Sender() == nil {
			 = RTPTransceiverDirectionInactive
		}

		.setCurrentDirection()
	}

	return nil
}

func runIfNewReceiver(
	 trackDetails,
	 []*RTPTransceiver,
	 func( trackDetails,  *RTPReceiver),
) bool {
	for ,  := range  {
		if .Mid() != .mid {
			continue
		}

		 := .Receiver()
		if (.kind != .Kind()) ||
			(.Direction() != RTPTransceiverDirectionRecvonly && .Direction() != RTPTransceiverDirectionSendrecv) ||
			 == nil ||
			(.haveReceived()) {
			continue
		}

		(, )

		return true
	}

	return false
}

// configureRTPReceivers opens knows inbound SRTP streams from the RemoteDescription.
//
//nolint:gocognit,cyclop
func ( *PeerConnection) (
	 bool,
	 *SessionDescription,
	 []*RTPTransceiver,
) {
	 := trackDetailsFromSDP(.log, .parsed)

	if  { //nolint:nestif
		for ,  := range  {
			 := .Receiver()
			if  == nil {
				continue
			}

			 := .Receiver().Tracks()
			if len() == 0 {
				continue
			}

			 := .Mid()
			 := false
			for ,  := range  {
				func( *TrackRemote) {
					.mu.Lock()
					defer .mu.Unlock()

					if .rid != "" {
						if  := trackDetailsForRID(, , .rid);  != nil {
							.id = .id
							.streamID = .streamID

							return
						}
					} else if .ssrc != 0 {
						if  := trackDetailsForSSRC(, .ssrc);  != nil {
							.id = .id
							.streamID = .streamID

							return
						}
					}

					 = true
				}()
			}

			if ! {
				continue
			}

			if  := .Stop();  != nil {
				.log.Warnf("Failed to stop RtpReceiver: %s", )

				continue
			}

			,  := .api.NewRTPReceiver(.kind, .dtlsTransport)
			if  != nil {
				.log.Warnf("Failed to create new RtpReceiver: %s", )

				continue
			}
			.setReceiver()
		}
	}

	 := append([]*RTPTransceiver{}, ...)

	// Ensure we haven't already started a transceiver for this ssrc
	 := append([]trackDetails{}, ...)
	for ,  := range  {
		// If we already have a TrackRemote for a given SSRC don't handle it again
		for ,  := range  {
			if  := .Receiver();  != nil {
				for ,  := range .Tracks() {
					for ,  := range .ssrcs {
						if  == .SSRC() {
							 = filterTrackWithSSRC(, .SSRC())
						}
					}
				}
			}
		}
	}

	for ,  := range  {
		_ = runIfNewReceiver(, , .configureReceiver)
	}
}

// startRTPReceivers opens knows inbound SRTP streams from the RemoteDescription.
func ( *PeerConnection) ( *SessionDescription,  []*RTPTransceiver) {
	 := trackDetailsFromSDP(.log, .parsed)
	if len() == 0 {
		return
	}

	 := append([]*RTPTransceiver{}, ...)

	 := [:0]
	for ,  := range  {
		 := runIfNewReceiver(, , .startReceiver)
		if ! {
			 = append(, )
		}
	}

	 := false
	switch .configuration.SDPSemantics {
	case SDPSemanticsPlanB:
		 = true
	case SDPSemanticsUnifiedPlanWithFallback:
		 = descriptionPossiblyPlanB(.RemoteDescription())
	default:
		// none
	}

	if  {
		for ,  := range  {
			,  := .AddTransceiverFromKind(.kind, RTPTransceiverInit{
				Direction: RTPTransceiverDirectionSendrecv,
			})
			if  != nil {
				.log.Warnf("Could not add transceiver for remote SSRC %d: %s", .ssrcs[0], )

				continue
			}
			.configureReceiver(, .Receiver())
			.startReceiver(, .Receiver())
		}
	}
}

// startRTPSenders starts all outbound RTP streams.
func ( *PeerConnection) ( []*RTPTransceiver) error {
	for ,  := range  {
		if  := .Sender();  != nil && .isNegotiated() && !.hasSent() {
			 := .Send(.GetParameters())
			if  != nil {
				return 
			}
		}
	}

	return nil
}

// Start SCTP subsystem.
func ( *PeerConnection) ( uint32) {
	// Start sctp
	if  := .sctpTransport.Start(SCTPCapabilities{
		MaxMessageSize: ,
	});  != nil {
		.log.Warnf("Failed to start SCTP: %s", )
		if  = .sctpTransport.Stop();  != nil {
			.log.Warnf("Failed to stop SCTPTransport: %s", )
		}

		return
	}
}

func ( *PeerConnection) (
	 SSRC,
	 *sdp.MediaDescription,
) ( bool,  error) {
	 := ""
	 := ""
	 := false
	 := false

	for ,  := range .Attributes {
		switch .Key {
		case sdp.AttrKeyMsid:
			if  := strings.Split(.Value, " "); len() == 2 {
				 = [0]
				 = [1]
			}
		case sdp.AttrKeySSRC:
			 = true
		case sdpAttributeRid:
			 = true
		}
	}

	if  {
		return false, nil
	} else if  {
		return false, errMediaSectionHasExplictSSRCAttribute
	}

	 := trackDetails{
		ssrcs:    []SSRC{},
		kind:     RTPCodecTypeVideo,
		streamID: ,
		id:       ,
	}
	if .MediaName.Media == RTPCodecTypeAudio.String() {
		.kind = RTPCodecTypeAudio
	}

	,  := .AddTransceiverFromKind(.kind, RTPTransceiverInit{
		Direction: RTPTransceiverDirectionSendrecv,
	})
	if  != nil {
		// nolint
		return false, fmt.Errorf("%w: %d: %s", errPeerConnRemoteSSRCAddTransceiver, , )
	}

	.configureReceiver(, .Receiver())
	.startReceiver(, .Receiver())

	return true, nil
}

// For legacy clients that didn't support urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
// or urn:ietf:params:rtp-hdrext:sdes:mid extension, and didn't declare a=ssrc lines.
// Assumes that the payload type is unique across the media section.
func ( *PeerConnection) (
	 PayloadType,
	 *SessionDescription,
) ( *sdp.MediaDescription,  bool) {
	for  := range .parsed.MediaDescriptions {
		 := .parsed.MediaDescriptions[]
		 := .MediaName.Media
		if !strings.EqualFold(, "video") && !strings.EqualFold(, "audio") {
			continue
		}

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

			// Return the first media section that has the payload type.
			// Assuming that the payload type is unique across the media section.
			if PayloadType() ==  {
				return .parsed.MediaDescriptions[], true
			}
		}
	}

	return nil, false
}

// Chrome sends probing traffic on SSRC 0. This reads the packets to ensure that we properly
// generate TWCC reports for it. Since this isn't actually media we don't pass this to the user.
func ( *PeerConnection) () {
	,  := .api.NewRTPReceiver(RTPCodecTypeVideo, .dtlsTransport)
	if  != nil {
		.log.Errorf("handleNonMediaBandwidthProbe failed to create RTPReceiver: %v", )

		return
	}

	if  = .Receive(RTPReceiveParameters{
		Encodings: []RTPDecodingParameters{{RTPCodingParameters: RTPCodingParameters{}}},
	});  != nil {
		.log.Errorf("handleNonMediaBandwidthProbe failed to start RTPReceiver: %v", )

		return
	}

	.nonMediaBandwidthProbe.Store()
	 := make([]byte, .api.settingEngine.getReceiveMTU())
	for {
		if _, _,  = .readRTP(, .Track());  != nil {
			.log.Tracef("handleNonMediaBandwidthProbe read exiting: %v", )

			return
		}
	}
}

func ( *PeerConnection) ( io.Reader,  SSRC) error { //nolint:gocyclo,gocognit,cyclop
	 := .RemoteDescription()
	if  == nil {
		return errPeerConnRemoteDescriptionNil
	}

	// If a SSRC already exists in the RemoteDescription don't perform heuristics upon it
	for ,  := range trackDetailsFromSDP(.log, .parsed) {
		if .rtxSsrc != nil &&  == *.rtxSsrc {
			return nil
		}
		if .fecSsrc != nil &&  == *.fecSsrc {
			return nil
		}
		for ,  := range .ssrcs {
			if  ==  {
				return nil
			}
		}
	}

	// if the SSRC is not declared in the SDP and there is only one media section,
	// we attempt to resolve it using this single section
	// This applies even if the client supports RTP extensions:
	// (urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id and urn:ietf:params:rtp-hdrext:sdes:mid)
	// and even if the RTP stream contains an incorrect MID or RID.
	// while this can be incorrect, this is done to maintain compatibility with older behavior.
	if len(.parsed.MediaDescriptions) == 1 {
		 := .parsed.MediaDescriptions[0]
		if ,  := .handleUndeclaredSSRC(, );  ||  != nil {
			return 
		}
	}

	// We read the RTP packet to determine the payload type
	 := make([]byte, .api.settingEngine.getReceiveMTU())

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

	if  < 4 {
		return errRTPTooShort
	}

	 := PayloadType([1] & 0x7f)
	,  := .api.mediaEngine.getRTPParametersByPayloadType()
	if  != nil {
		return 
	}

	, ,  := .api.mediaEngine.getHeaderExtensionID(
		RTPHeaderExtensionCapability{sdp.SDESMidURI},
	)
	if ! && ! {
		// try to find media section by payload type as a last resort for legacy clients.
		,  := .findMediaSectionByPayloadType(, )
		if  {
			if ,  = .handleUndeclaredSSRC(, );  ||  != nil {
				return 
			}
		}

		return errPeerConnSimulcastMidRTPExtensionRequired
	}

	, ,  := .api.mediaEngine.getHeaderExtensionID(
		RTPHeaderExtensionCapability{sdp.SDESRTPStreamIDURI},
	)
	if ! && ! {
		return errPeerConnSimulcastStreamIDRTPExtensionRequired
	}

	, ,  := .api.mediaEngine.getHeaderExtensionID(
		RTPHeaderExtensionCapability{sdp.SDESRepairRTPStreamIDURI},
	)

	 := createStreamInfo(
		"",
		,
		0, 0,
		.Codecs[0].PayloadType,
		0, 0,
		.Codecs[0].RTPCodecCapability,
		.HeaderExtensions,
	)
	, , , ,  := .dtlsTransport.streamsForSSRC(, *)
	if  != nil {
		return 
	}

	var , ,  string
	var  bool
	for  := 0;  <= simulcastProbeCount; ++ {
		if  == "" || ( == "" &&  == "") {
			// skip padding only packets for probing
			if  {
				--
			}

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

			if _, ,  = handleUnknownRTPPacket(
				[:], uint8(), //nolint:gosec // G115
				uint8(),       //nolint:gosec // G115
				uint8(), //nolint:gosec // G115
				&,
				&,
				&,
			);  != nil {
				return 
			}

			continue
		}

		for ,  := range .GetTransceivers() {
			 := .Receiver()
			if .Mid() !=  ||  == nil {
				continue
			}

			if  != "" {
				.mu.Lock()
				defer .mu.Unlock()

				return .receiveForRtx(SSRC(0), , , , , , )
			}

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

			return nil
		}
	}

	.api.interceptor.UnbindRemoteStream()

	return errPeerConnSimulcastIncomingSSRCFailed
}

// undeclaredMediaProcessor handles RTP/RTCP packets that don't match any a:ssrc lines.
func ( *PeerConnection) () {
	go .undeclaredRTPMediaProcessor()
	go .undeclaredRTCPMediaProcessor()
}

func ( *PeerConnection) () { //nolint:cyclop
	var  uint64
	for {
		,  := .dtlsTransport.getSRTPSession()
		if  != nil {
			.log.Warnf("undeclaredMediaProcessor failed to open SrtpSession: %v", )

			return
		}

		,  := .dtlsTransport.getSRTCPSession()
		if  != nil {
			.log.Warnf("undeclaredMediaProcessor failed to open SrtcpSession: %v", )

			return
		}

		, ,  := .AcceptStream()
		if  != nil {
			.log.Warnf("Failed to accept RTP %v", )

			return
		}

		// open accompanying srtcp stream
		,  := .OpenReadStream()
		if  != nil {
			.log.Warnf("Failed to open RTCP stream for %d: %v", , )

			return
		}

		if .isClosed.get() {
			if  = .Close();  != nil {
				.log.Warnf("Failed to close RTP stream %v", )
			}
			if  = .Close();  != nil {
				.log.Warnf("Failed to close RTCP stream %v", )
			}

			continue
		}

		.dtlsTransport.storeSimulcastStream(, )

		if  == 0 {
			go .handleNonMediaBandwidthProbe()

			continue
		}

		if atomic.AddUint64(&, 1) >= simulcastMaxProbeRoutines {
			atomic.AddUint64(&, ^uint64(0))
			.log.Warn(ErrSimulcastProbeOverflow.Error())

			continue
		}

		go func( io.Reader,  SSRC) {
			if  := .handleIncomingSSRC(, );  != nil {
				.log.Errorf(incomingUnhandledRTPSsrc, , )
			}
			atomic.AddUint64(&, ^uint64(0))
		}(, SSRC())
	}
}

func ( *PeerConnection) () {
	var  []*srtp.ReadStreamSRTCP
	defer func() {
		for ,  := range  {
			_ = .Close()
		}
	}()
	for {
		,  := .dtlsTransport.getSRTCPSession()
		if  != nil {
			.log.Warnf("undeclaredMediaProcessor failed to open SrtcpSession: %v", )

			return
		}

		, ,  := .AcceptStream()
		if  != nil {
			.log.Warnf("Failed to accept RTCP %v", )

			return
		}
		.log.Warnf("Incoming unhandled RTCP ssrc(%d), OnTrack will not be fired", )
		 = append(, )
	}
}

// RemoteDescription returns pendingRemoteDescription if it is not null and
// otherwise it returns currentRemoteDescription. This property is used to
// determine if setRemoteDescription has already been called.
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-remotedescription
func ( *PeerConnection) () *SessionDescription {
	.mu.RLock()
	defer .mu.RUnlock()

	if .pendingRemoteDescription != nil {
		return .pendingRemoteDescription
	}

	return .currentRemoteDescription
}

// AddICECandidate accepts an ICE candidate string and adds it
// to the existing set of candidates.
func ( *PeerConnection) ( ICECandidateInit) error {
	 := .RemoteDescription()
	if  == nil {
		return &rtcerr.InvalidStateError{Err: ErrNoRemoteDescription}
	}

	 := strings.TrimPrefix(.Candidate, "candidate:")

	if  == "" {
		return .iceTransport.AddRemoteCandidate(nil)
	}

	,  := ice.UnmarshalCandidate()
	if  != nil {
		if errors.Is(, ice.ErrUnknownCandidateTyp) || errors.Is(, ice.ErrDetermineNetworkType) {
			.log.Warnf("Discarding remote candidate: %s", )

			return nil
		}

		return 
	}

	// Reject candidates from old generations.
	// If candidate.usernameFragment is not null,
	// and is not equal to any username fragment present in the corresponding media
	//  description of an applied remote description,
	// return a promise rejected with a newly created OperationError.
	// https://w3c.github.io/webrtc-pc/#dom-peerconnection-addicecandidate
	if ,  := .GetExtension("ufrag");  {
		if !.descriptionContainsUfrag(.parsed, .Value) {
			.log.Errorf("dropping candidate with ufrag %s because it doesn't match the current ufrags", .Value)

			return nil
		}
	}

	,  := newICECandidateFromICE(, "", 0)
	if  != nil {
		return 
	}

	return .iceTransport.AddRemoteCandidate(&)
}

// Return true if the sdp contains a specific ufrag.
func ( *PeerConnection) ( *sdp.SessionDescription,  string) bool {
	,  := .Attribute("ice-ufrag")
	if  &&  ==  {
		return true
	}

	for ,  := range .MediaDescriptions {
		,  := .Attribute("ice-ufrag")
		if  &&  ==  {
			return true
		}
	}

	return false
}

// ICEConnectionState returns the ICE connection state of the
// PeerConnection instance.
func ( *PeerConnection) () ICEConnectionState {
	if ,  := .iceConnectionState.Load().(ICEConnectionState);  {
		return 
	}

	return ICEConnectionState(0)
}

// GetSenders returns the RTPSender that are currently attached to this PeerConnection.
func ( *PeerConnection) () ( []*RTPSender) {
	.mu.Lock()
	defer .mu.Unlock()

	for ,  := range .rtpTransceivers {
		if  := .Sender();  != nil {
			 = append(, )
		}
	}

	return 
}

// GetReceivers returns the RTPReceivers that are currently attached to this PeerConnection.
func ( *PeerConnection) () ( []*RTPReceiver) {
	.mu.Lock()
	defer .mu.Unlock()

	for ,  := range .rtpTransceivers {
		if  := .Receiver();  != nil {
			 = append(, )
		}
	}

	return
}

// GetTransceivers returns the RtpTransceiver that are currently attached to this PeerConnection.
func ( *PeerConnection) () []*RTPTransceiver {
	.mu.Lock()
	defer .mu.Unlock()

	return .rtpTransceivers
}

// AddTrack adds a Track to the PeerConnection.
//
//nolint:cyclop
func ( *PeerConnection) ( TrackLocal) (*RTPSender, error) {
	if .isClosed.get() {
		return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	.mu.Lock()
	defer .mu.Unlock()
	for ,  := range .rtpTransceivers {
		 := .getCurrentDirection()
		// According to https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-addtrack, if the
		// transceiver can be reused only if it's currentDirection never be sendrecv or sendonly.
		// But that will cause sdp inflate. So we only check currentDirection's current value,
		// that's worked for all browsers.
		if .kind == .Kind() && .Sender() == nil &&
			!( == RTPTransceiverDirectionSendrecv ||  == RTPTransceiverDirectionSendonly) {
			,  := .api.NewRTPSender(, .dtlsTransport)
			if  == nil {
				 = .SetSender(, )
				if  != nil {
					_ = .Stop()
					.setSender(nil)
				}
			}
			if  != nil {
				return nil, 
			}
			.onNegotiationNeeded()

			return , nil
		}
	}

	,  := .newTransceiverFromTrack(RTPTransceiverDirectionSendrecv, )
	if  != nil {
		return nil, 
	}
	.addRTPTransceiver()

	return .Sender(), nil
}

// RemoveTrack removes a Track from the PeerConnection.
func ( *PeerConnection) ( *RTPSender) ( error) {
	if .isClosed.get() {
		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	var  *RTPTransceiver
	.mu.Lock()
	defer .mu.Unlock()
	for ,  := range .rtpTransceivers {
		if .Sender() ==  {
			 = 

			break
		}
	}
	if  == nil {
		return &rtcerr.InvalidAccessError{Err: ErrSenderNotCreatedByConnection}
	} else if  = .Stop();  == nil {
		 = .setSendingTrack(nil)
		if  == nil {
			.onNegotiationNeeded()
		}
	}

	return
}

//nolint:cyclop
func ( *PeerConnection) (
	 RTPTransceiverDirection,
	 TrackLocal,
	 ...RTPTransceiverInit,
) ( *RTPTransceiver,  error) {
	var (
		 *RTPReceiver
		   *RTPSender
	)
	switch  {
	case RTPTransceiverDirectionSendrecv:
		,  = .api.NewRTPReceiver(.Kind(), .dtlsTransport)
		if  != nil {
			return , 
		}
		,  = .api.NewRTPSender(, .dtlsTransport)
	case RTPTransceiverDirectionSendonly:
		,  = .api.NewRTPSender(, .dtlsTransport)
	default:
		 = errPeerConnAddTransceiverFromTrackSupport
	}
	if  != nil {
		return , 
	}

	// Allow RTPTransceiverInit to override SSRC
	if  != nil && len(.trackEncodings) == 1 &&
		len() == 1 && len([0].SendEncodings) == 1 && [0].SendEncodings[0].SSRC != 0 {
		.trackEncodings[0].ssrc = [0].SendEncodings[0].SSRC
	}

	return newRTPTransceiver(, , , .Kind(), .api), nil
}

// AddTransceiverFromKind Create a new RtpTransceiver and adds it to the set of transceivers.
//
//nolint:cyclop
func ( *PeerConnection) (
	 RTPCodecType,
	 ...RTPTransceiverInit,
) ( *RTPTransceiver,  error) {
	if .isClosed.get() {
		return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	 := RTPTransceiverDirectionSendrecv
	if len() > 1 {
		return nil, errPeerConnAddTransceiverFromKindOnlyAcceptsOne
	} else if len() == 1 {
		 = [0].Direction
	}
	switch  {
	case RTPTransceiverDirectionSendonly, RTPTransceiverDirectionSendrecv:
		 := .api.mediaEngine.getCodecsByKind()
		if len() == 0 {
			return nil, ErrNoCodecsAvailable
		}
		,  := NewTrackLocalStaticSample([0].RTPCodecCapability, util.MathRandAlpha(16), util.MathRandAlpha(16))
		if  != nil {
			return nil, 
		}
		,  = .newTransceiverFromTrack(, , ...)
		if  != nil {
			return nil, 
		}
	case RTPTransceiverDirectionRecvonly:
		,  := .api.NewRTPReceiver(, .dtlsTransport)
		if  != nil {
			return nil, 
		}
		 = newRTPTransceiver(, nil, RTPTransceiverDirectionRecvonly, , .api)
	default:
		return nil, errPeerConnAddTransceiverFromKindSupport
	}
	.mu.Lock()
	.addRTPTransceiver()
	.mu.Unlock()

	return , nil
}

// AddTransceiverFromTrack Create a new RtpTransceiver(SendRecv or SendOnly) and add it to the set of transceivers.
func ( *PeerConnection) (
	 TrackLocal,
	 ...RTPTransceiverInit,
) ( *RTPTransceiver,  error) {
	if .isClosed.get() {
		return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	 := RTPTransceiverDirectionSendrecv
	if len() > 1 {
		return nil, errPeerConnAddTransceiverFromTrackOnlyAcceptsOne
	} else if len() == 1 {
		 = [0].Direction
	}

	,  = .newTransceiverFromTrack(, , ...)
	if  == nil {
		.mu.Lock()
		.addRTPTransceiver()
		.mu.Unlock()
	}

	return
}

// CreateDataChannel creates a new DataChannel object with the given label
// and optional DataChannelInit used to configure properties of the
// underlying channel such as data reliability.
//
//nolint:cyclop
func ( *PeerConnection) ( string,  *DataChannelInit) (*DataChannel, error) {
	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #2)
	if .isClosed.get() {
		return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
	}

	 := &DataChannelParameters{
		Label:   ,
		Ordered: true,
	}

	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #19)
	if  != nil {
		.ID = .ID
	}

	if  != nil { //nolint:nestif
		// Ordered indicates if data is allowed to be delivered out of order. The
		// default value of true, guarantees that data will be delivered in order.
		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #9)
		if .Ordered != nil {
			.Ordered = *.Ordered
		}

		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #7)
		if .MaxPacketLifeTime != nil {
			.MaxPacketLifeTime = .MaxPacketLifeTime
		}

		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #8)
		if .MaxRetransmits != nil {
			.MaxRetransmits = .MaxRetransmits
		}

		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #10)
		if .Protocol != nil {
			.Protocol = *.Protocol
		}

		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #11)
		if len(.Protocol) > 65535 {
			return nil, &rtcerr.TypeError{Err: ErrProtocolTooLarge}
		}

		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #12)
		if .Negotiated != nil {
			.Negotiated = *.Negotiated
		}
	}

	,  := .api.newDataChannel(, nil, .log)
	if  != nil {
		return nil, 
	}

	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #16)
	if .maxPacketLifeTime != nil && .maxRetransmits != nil {
		return nil, &rtcerr.TypeError{Err: ErrRetransmitsOrPacketLifeTime}
	}

	.sctpTransport.lock.Lock()
	.sctpTransport.dataChannels = append(.sctpTransport.dataChannels, )
	if .ID() != nil {
		.sctpTransport.dataChannelIDsUsed[*.ID()] = struct{}{}
	}
	.sctpTransport.dataChannelsRequested++
	.sctpTransport.lock.Unlock()

	// If SCTP already connected open all the channels
	if .sctpTransport.State() == SCTPTransportStateConnected {
		if  = .open(.sctpTransport);  != nil {
			return nil, 
		}
	}

	.mu.Lock()
	.onNegotiationNeeded()
	.mu.Unlock()

	return , nil
}

// SetIdentityProvider is used to configure an identity provider to generate identity assertions.
func ( *PeerConnection) (string) error {
	return errPeerConnSetIdentityProviderNotImplemented
}

// WriteRTCP sends a user provided RTCP packet to the connected peer. If no peer is connected the
// packet is discarded. It also runs any configured interceptors.
func ( *PeerConnection) ( []rtcp.Packet) error {
	,  := .interceptorRTCPWriter.Write(, make(interceptor.Attributes))

	return 
}

func ( *PeerConnection) ( []rtcp.Packet,  interceptor.Attributes) (int, error) {
	return .dtlsTransport.WriteRTCP()
}

// Close ends the PeerConnection.
func ( *PeerConnection) () error {
	return .close(false /* shouldGracefullyClose */)
}

// GracefulClose ends the PeerConnection. It also waits
// for any goroutines it started to complete. This is only safe to call outside of
// PeerConnection callbacks or if in a callback, in its own goroutine.
func ( *PeerConnection) () error {
	return .close(true /* shouldGracefullyClose */)
}

func ( *PeerConnection) ( bool) error { //nolint:cyclop
	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #1)
	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #2)

	.mu.Lock()
	// A lock in this critical section is needed because pc.isClosed and
	// pc.isGracefullyClosingOrClosed are related to each other in that we
	// want to make graceful and normal closure one time operations in order
	// to avoid any double closure errors from cropping up. However, there are
	// some overlapping close cases when both normal and graceful close are used
	// that should be idempotent, but be cautioned when writing new close behavior
	// to preserve this property.
	 := .isClosed.swap(true)
	 := .isGracefullyClosingOrClosed
	if  && ! {
		.isGracefullyClosingOrClosed = true
	}
	.mu.Unlock()

	if  {
		if ! {
			return nil
		}
		// Even if we're already closing, it may not be graceful:
		// If we are not the ones doing the closing, we just wait for the graceful close
		// to happen and then return.
		if  {
			<-.isGracefulCloseDone

			return nil
		}
		// Otherwise we need to go through the graceful closure flow once the
		// normal closure is done since there are extra steps to take with a
		// graceful close.
		<-.isCloseDone
	} else {
		defer close(.isCloseDone)
	}

	if  {
		defer close(.isGracefulCloseDone)
	}

	// Try closing everything and collect the errors
	// Shutdown strategy:
	// 1. All Conn close by closing their underlying Conn.
	// 2. A Mux stops this chain. It won't close the underlying
	//    Conn if one of the endpoints is closed down. To
	//    continue the chain the Mux has to be closed.
	 := make([]error, 4)

	 := func() []error {
		if ! {
			return nil
		}

		// these are all non-canon steps
		var  []error
		if .iceTransport != nil {
			 = append(, .iceTransport.GracefulStop())
		}

		.ops.GracefulClose()

		.sctpTransport.lock.Lock()
		for ,  := range .sctpTransport.dataChannels {
			 = append(, .GracefulClose())
		}
		.sctpTransport.lock.Unlock()

		return 
	}

	if  {
		return util.FlattenErrs(())
	}

	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #3)
	.signalingState.Set(SignalingStateClosed)

	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #4)
	.mu.Lock()
	for ,  := range .rtpTransceivers {
		 = append(, .Stop()) //nolint:makezero // todo fix
	}
	if ,  := .nonMediaBandwidthProbe.Load().(*RTPReceiver);  {
		 = append(, .Stop()) //nolint:makezero // todo fix
	}
	.mu.Unlock()

	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #5)
	.sctpTransport.lock.Lock()
	for ,  := range .sctpTransport.dataChannels {
		.setReadyState(DataChannelStateClosed)
	}
	.sctpTransport.lock.Unlock()

	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #6)
	if .sctpTransport != nil {
		 = append(, .sctpTransport.Stop()) //nolint:makezero // todo fix
	}

	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #7)
	 = append(, .dtlsTransport.Stop()) //nolint:makezero // todo fix

	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #8, #9, #10)
	if .iceTransport != nil && ! {
		// we will stop gracefully in doGracefulCloseOps
		 = append(, .iceTransport.Stop()) //nolint:makezero // todo fix
	}

	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #11)
	.updateConnectionState(.ICEConnectionState(), .dtlsTransport.State())

	 = append(, ()...) //nolint:makezero // todo fix

	// Interceptor closes at the end to prevent Bind from being called after interceptor is closed
	 = append(, .api.interceptor.Close()) //nolint:makezero // todo fix

	return util.FlattenErrs()
}

// addRTPTransceiver appends t into rtpTransceivers
// and fires onNegotiationNeeded;
// caller of this method should hold `pc.mu` lock.
func ( *PeerConnection) ( *RTPTransceiver) {
	.rtpTransceivers = append(.rtpTransceivers, )
	.onNegotiationNeeded()
}

// CurrentLocalDescription represents the local description that was
// successfully negotiated the last time the PeerConnection transitioned
// into the stable state plus any local candidates that have been generated
// by the ICEAgent since the offer or answer was created.
func ( *PeerConnection) () *SessionDescription {
	.mu.Lock()
	defer .mu.Unlock()

	 := .currentLocalDescription
	 := .iceGatherer
	 := .ICEGatheringState()

	return populateLocalCandidates(, , )
}

// PendingLocalDescription represents a local description that is in the
// process of being negotiated plus any local candidates that have been
// generated by the ICEAgent since the offer or answer was created. If the
// PeerConnection is in the stable state, the value is null.
func ( *PeerConnection) () *SessionDescription {
	.mu.Lock()
	defer .mu.Unlock()

	 := .pendingLocalDescription
	 := .iceGatherer
	 := .ICEGatheringState()

	return populateLocalCandidates(, , )
}

// CurrentRemoteDescription represents the last remote description that was
// successfully negotiated the last time the PeerConnection transitioned
// into the stable state plus any remote candidates that have been supplied
// via AddICECandidate() since the offer or answer was created.
func ( *PeerConnection) () *SessionDescription {
	.mu.RLock()
	defer .mu.RUnlock()

	return .currentRemoteDescription
}

// PendingRemoteDescription represents a remote description that is in the
// process of being negotiated, complete with any remote candidates that
// have been supplied via AddICECandidate() since the offer or answer was
// created. If the PeerConnection is in the stable state, the value is
// null.
func ( *PeerConnection) () *SessionDescription {
	.mu.RLock()
	defer .mu.RUnlock()

	return .pendingRemoteDescription
}

// SignalingState attribute returns the signaling state of the
// PeerConnection instance.
func ( *PeerConnection) () SignalingState {
	return .signalingState.Get()
}

// ICEGatheringState attribute returns the ICE gathering state of the
// PeerConnection instance.
func ( *PeerConnection) () ICEGatheringState {
	if .iceGatherer == nil {
		return ICEGatheringStateNew
	}

	switch .iceGatherer.State() {
	case ICEGathererStateNew:
		return ICEGatheringStateNew
	case ICEGathererStateGathering:
		return ICEGatheringStateGathering
	default:
		return ICEGatheringStateComplete
	}
}

// ConnectionState attribute returns the connection state of the
// PeerConnection instance.
func ( *PeerConnection) () PeerConnectionState {
	if ,  := .connectionState.Load().(PeerConnectionState);  {
		return 
	}

	return PeerConnectionState(0)
}

// GetStats return data providing statistics about the overall connection.
func ( *PeerConnection) () StatsReport {
	var (
		  uint32
		    uint32
		    uint32
		 uint32
	)
	 := newStatsReportCollector()
	.Collecting()

	.mu.Lock()
	if .iceGatherer != nil {
		.iceGatherer.collectStats()
	}
	if .iceTransport != nil {
		.iceTransport.collectStats()
	}

	.sctpTransport.lock.Lock()
	 := append([]*DataChannel{}, .sctpTransport.dataChannels...)
	 = .sctpTransport.dataChannelsAccepted
	 = .sctpTransport.dataChannelsOpened
	 = .sctpTransport.dataChannelsRequested
	.sctpTransport.lock.Unlock()

	for ,  := range  {
		 := .ReadyState()
		if  != DataChannelStateConnecting &&  != DataChannelStateOpen {
			++
		}

		.collectStats()
	}
	.sctpTransport.collectStats()

	 := PeerConnectionStats{
		Timestamp:             statsTimestampNow(),
		Type:                  StatsTypePeerConnection,
		ID:                    .statsID,
		DataChannelsAccepted:  ,
		DataChannelsClosed:    ,
		DataChannelsOpened:    ,
		DataChannelsRequested: ,
	}

	.Collect(.ID, )

	 := .configuration.Certificates
	for ,  := range  {
		if  := .collectStats();  != nil {
			continue
		}
	}
	.mu.Unlock()

	.api.mediaEngine.collectStats()

	return .Ready()
}

// Start all transports. PeerConnection now has enough state.
func ( *PeerConnection) (
	 ICERole,
	 DTLSRole,
	, , ,  string,
) {
	// Start the ice transport
	 := .iceTransport.Start(
		.iceGatherer,
		ICEParameters{
			UsernameFragment: ,
			Password:         ,
			ICELite:          false,
		},
		&,
	)
	if  != nil {
		.log.Warnf("Failed to start manager: %s", )

		return
	}

	.dtlsTransport.internalOnCloseHandler = func() {
		if .isClosed.get() || .api.settingEngine.disableCloseByDTLS {
			return
		}

		.log.Info("Closing PeerConnection from DTLS CloseNotify")
		go func() {
			if  := .Close();  != nil {
				.log.Warnf("Failed to close PeerConnection from DTLS CloseNotify: %s", )
			}
		}()
	}

	// Start the dtls transport
	 = .dtlsTransport.Start(DTLSParameters{
		Role:         ,
		Fingerprints: []DTLSFingerprint{{Algorithm: , Value: }},
	})
	.updateConnectionState(.ICEConnectionState(), .dtlsTransport.State())
	if  != nil {
		.log.Warnf("Failed to start manager: %s", )

		return
	}
}

// nolint: gocognit
func ( *PeerConnection) (
	 bool,
	 *SessionDescription,
	 []*RTPTransceiver,
) {
	if ! {
		.undeclaredMediaProcessor()
	}

	.startRTPReceivers(, )
	if  := haveDataChannel();  != nil {
		.startSCTP(getMaxMessageSize())
	}
}

// generateUnmatchedSDP generates an SDP that doesn't take remote state into account
// This is used for the initial call for CreateOffer.
//
//nolint:cyclop
func ( *PeerConnection) (
	 []*RTPTransceiver,
	 bool,
) (*sdp.SessionDescription, error) {
	,  := sdp.NewJSEPSessionDescription()
	if  != nil {
		return nil, 
	}
	.Attributes = append(.Attributes, sdp.Attribute{Key: sdp.AttrKeyMsidSemantic, Value: "WMS *"})

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

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

	 := .configuration.SDPSemantics == SDPSemanticsPlanB
	 := []mediaSection{}

	// Needed for pc.sctpTransport.dataChannelsRequested
	.sctpTransport.lock.Lock()
	defer .sctpTransport.lock.Unlock()

	if  { //nolint:nestif
		 := make([]*RTPTransceiver, 0)
		 := make([]*RTPTransceiver, 0)

		for ,  := range  {
			if .kind == RTPCodecTypeVideo {
				 = append(, )
			} else if .kind == RTPCodecTypeAudio {
				 = append(, )
			}
			if  := .Sender();  != nil {
				.setNegotiated()
			}
		}

		if len() > 0 {
			 = append(, mediaSection{id: "video", transceivers: })
		}
		if len() > 0 {
			 = append(, mediaSection{id: "audio", transceivers: })
		}

		if .sctpTransport.dataChannelsRequested != 0 {
			 = append(, mediaSection{id: "data", data: true})
		}
	} else {
		for ,  := range  {
			if  := .Sender();  != nil {
				.setNegotiated()
			}
			 = append(, mediaSection{id: .Mid(), transceivers: []*RTPTransceiver{}})
		}

		if .sctpTransport.dataChannelsRequested != 0 {
			 = append(, mediaSection{id: strconv.Itoa(len()), data: true})
		}
	}

	,  := .configuration.Certificates[0].GetFingerprints()
	if  != nil {
		return nil, 
	}

	return populateSDP(
		,
		,
		,
		.api.settingEngine.sdpMediaLevelFingerprints,
		.api.settingEngine.candidates.ICELite,
		true,
		.api.mediaEngine,
		connectionRoleFromDtlsRole(defaultDtlsRoleOffer),
		,
		,
		,
		.ICEGatheringState(),
		nil,
		.api.settingEngine.getSCTPMaxMessageSize(),
	)
}

// generateMatchedSDP generates a SDP and takes the remote state into account
// this is used everytime we have a RemoteDescription
//
//nolint:gocognit,gocyclo,cyclop
func ( *PeerConnection) (
	 []*RTPTransceiver,
	,  bool,
	 sdp.ConnectionRole,
) (*sdp.SessionDescription, error) {
	,  := sdp.NewJSEPSessionDescription()
	if  != nil {
		return nil, 
	}
	.Attributes = append(.Attributes, sdp.Attribute{Key: sdp.AttrKeyMsidSemantic, Value: "WMS *"})

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

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

	var  *RTPTransceiver
	 := .currentRemoteDescription
	if .pendingRemoteDescription != nil {
		 = .pendingRemoteDescription
	}
	 := isExtMapAllowMixedSet(.parsed)
	 := append([]*RTPTransceiver{}, ...)

	 := descriptionIsPlanB(, .log)
	if .configuration.SDPSemantics != SDPSemanticsUnifiedPlan {
		 = descriptionPossiblyPlanB()
	}

	 := []mediaSection{}
	 := false
	for ,  := range .parsed.MediaDescriptions {
		 := getMidValue()
		if  == "" {
			return nil, errPeerConnRemoteDescriptionWithoutMidValue
		}

		if .MediaName.Media == mediaSectionApplication {
			 = append(, mediaSection{id: , data: true})
			 = true

			continue
		}

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

		 := .configuration.SDPSemantics

		switch {
		case  == SDPSemanticsPlanB ||  == SDPSemanticsUnifiedPlanWithFallback && :
			if ! {
				return nil, &rtcerr.TypeError{
					Err: fmt.Errorf("%w: Expected PlanB, but RemoteDescription is UnifiedPlan", ErrIncorrectSDPSemantics),
				}
			}
			// If we're responding to a plan-b offer, then we should try to fill up this
			// media entry with all matching local transceivers
			 := []*RTPTransceiver{}
			for {
				// keep going until we can't get any more
				,  = satisfyTypeAndDirection(, , )
				if  == nil {
					if len() == 0 {
						 = &RTPTransceiver{kind: , api: .api, codecs: .api.mediaEngine.getCodecsByKind()}
						.setDirection(RTPTransceiverDirectionInactive)
						 = append(, )
					}

					break
				}
				if  := .Sender();  != nil {
					.setNegotiated()
				}
				 = append(, )
			}
			 = append(, mediaSection{id: , transceivers: })
		case  == SDPSemanticsUnifiedPlan ||  == SDPSemanticsUnifiedPlanWithFallback:
			if  {
				return nil, &rtcerr.TypeError{
					Err: fmt.Errorf(
						"%w: Expected UnifiedPlan, but RemoteDescription is PlanB",
						ErrIncorrectSDPSemantics,
					),
				}
			}
			,  = findByMid(, )
			if  == nil {
				return nil, fmt.Errorf("%w: %q", errPeerConnTranscieverMidNil, )
			}
			if  := .Sender();  != nil {
				.setNegotiated()
			}
			 := []*RTPTransceiver{}

			,  := rtpExtensionsFromMediaDescription()
			 = append(
				,
				mediaSection{id: , transceivers: , matchExtensions: , rids: getRids()},
			)
		}
	}

	.sctpTransport.lock.Lock()
	defer .sctpTransport.lock.Unlock()

	var  *string
	// If we are offering also include unmatched local transceivers
	if  { //nolint:nestif
		if ! {
			for ,  := range  {
				if  := .Sender();  != nil {
					.setNegotiated()
				}
				 = append(, mediaSection{id: .Mid(), transceivers: []*RTPTransceiver{}})
			}
		}

		if .sctpTransport.dataChannelsRequested != 0 && ! {
			if  {
				 = append(, mediaSection{id: "data", data: true})
			} else {
				 = append(, mediaSection{id: strconv.Itoa(len()), data: true})
			}
		}
	} else if  != nil {
		,  := .parsed.Attribute(sdp.AttrKeyGroup)
		 = strings.TrimLeft(, "BUNDLE")
		 = &
	}

	if .configuration.SDPSemantics == SDPSemanticsUnifiedPlanWithFallback &&  {
		.log.Info("Plan-B Offer detected; responding with Plan-B Answer")
	}

	,  := .configuration.Certificates[0].GetFingerprints()
	if  != nil {
		return nil, 
	}

	return populateSDP(
		,
		,
		,
		.api.settingEngine.sdpMediaLevelFingerprints,
		.api.settingEngine.candidates.ICELite,
		,
		.api.mediaEngine,
		,
		,
		,
		,
		.ICEGatheringState(),
		,
		.api.settingEngine.getSCTPMaxMessageSize(),
	)
}

func ( *PeerConnection) ( func()) {
	.iceGatherer.onGatheringCompleteHandler.Store()
}

// SCTP returns the SCTPTransport for this PeerConnection
//
// The SCTP transport over which SCTP data is sent and received. If SCTP has not been negotiated, the value is nil.
// https://www.w3.org/TR/webrtc/#attributes-15
func ( *PeerConnection) () *SCTPTransport {
	return .sctpTransport
}