// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package webrtc import ( "fmt" "sync/atomic" "github.com/pion/webrtc/v3/pkg/rtcerr" ) type stateChangeOp int const ( stateChangeOpSetLocal stateChangeOp = iota + 1 stateChangeOpSetRemote ) func (op stateChangeOp) String() string { switch op { case stateChangeOpSetLocal: return "SetLocal" case stateChangeOpSetRemote: return "SetRemote" default: return "Unknown State Change Operation" } } // SignalingState indicates the signaling state of the offer/answer process. type SignalingState int32 const ( // SignalingStateStable indicates there is no offer/answer exchange in // progress. This is also the initial state, in which case the local and // remote descriptions are nil. SignalingStateStable SignalingState = iota + 1 // SignalingStateHaveLocalOffer indicates that a local description, of // type "offer", has been successfully applied. SignalingStateHaveLocalOffer // SignalingStateHaveRemoteOffer indicates that a remote description, of // type "offer", has been successfully applied. SignalingStateHaveRemoteOffer // SignalingStateHaveLocalPranswer indicates that a remote description // of type "offer" has been successfully applied and a local description // of type "pranswer" has been successfully applied. SignalingStateHaveLocalPranswer // SignalingStateHaveRemotePranswer indicates that a local description // of type "offer" has been successfully applied and a remote description // of type "pranswer" has been successfully applied. SignalingStateHaveRemotePranswer // SignalingStateClosed indicates The PeerConnection has been closed. SignalingStateClosed ) // This is done this way because of a linter. const ( signalingStateStableStr = "stable" signalingStateHaveLocalOfferStr = "have-local-offer" signalingStateHaveRemoteOfferStr = "have-remote-offer" signalingStateHaveLocalPranswerStr = "have-local-pranswer" signalingStateHaveRemotePranswerStr = "have-remote-pranswer" signalingStateClosedStr = "closed" ) func newSignalingState(raw string) SignalingState { switch raw { case signalingStateStableStr: return SignalingStateStable case signalingStateHaveLocalOfferStr: return SignalingStateHaveLocalOffer case signalingStateHaveRemoteOfferStr: return SignalingStateHaveRemoteOffer case signalingStateHaveLocalPranswerStr: return SignalingStateHaveLocalPranswer case signalingStateHaveRemotePranswerStr: return SignalingStateHaveRemotePranswer case signalingStateClosedStr: return SignalingStateClosed default: return SignalingState(Unknown) } } func (t SignalingState) String() string { switch t { case SignalingStateStable: return signalingStateStableStr case SignalingStateHaveLocalOffer: return signalingStateHaveLocalOfferStr case SignalingStateHaveRemoteOffer: return signalingStateHaveRemoteOfferStr case SignalingStateHaveLocalPranswer: return signalingStateHaveLocalPranswerStr case SignalingStateHaveRemotePranswer: return signalingStateHaveRemotePranswerStr case SignalingStateClosed: return signalingStateClosedStr default: return ErrUnknownType.Error() } } // Get thread safe read value func (t *SignalingState) Get() SignalingState { return SignalingState(atomic.LoadInt32((*int32)(t))) } // Set thread safe write value func (t *SignalingState) Set(state SignalingState) { atomic.StoreInt32((*int32)(t), int32(state)) } func checkNextSignalingState(cur, next SignalingState, op stateChangeOp, sdpType SDPType) (SignalingState, error) { // nolint:gocognit // Special case for rollbacks if sdpType == SDPTypeRollback && cur == SignalingStateStable { return cur, &rtcerr.InvalidModificationError{ Err: errSignalingStateCannotRollback, } } // 4.3.1 valid state transitions switch cur { // nolint:exhaustive case SignalingStateStable: switch op { case stateChangeOpSetLocal: // stable->SetLocal(offer)->have-local-offer if sdpType == SDPTypeOffer && next == SignalingStateHaveLocalOffer { return next, nil } case stateChangeOpSetRemote: // stable->SetRemote(offer)->have-remote-offer if sdpType == SDPTypeOffer && next == SignalingStateHaveRemoteOffer { return next, nil } } case SignalingStateHaveLocalOffer: if op == stateChangeOpSetRemote { switch sdpType { // nolint:exhaustive // have-local-offer->SetRemote(answer)->stable case SDPTypeAnswer: if next == SignalingStateStable { return next, nil } // have-local-offer->SetRemote(pranswer)->have-remote-pranswer case SDPTypePranswer: if next == SignalingStateHaveRemotePranswer { return next, nil } } } case SignalingStateHaveRemotePranswer: if op == stateChangeOpSetRemote && sdpType == SDPTypeAnswer { // have-remote-pranswer->SetRemote(answer)->stable if next == SignalingStateStable { return next, nil } } case SignalingStateHaveRemoteOffer: if op == stateChangeOpSetLocal { switch sdpType { // nolint:exhaustive // have-remote-offer->SetLocal(answer)->stable case SDPTypeAnswer: if next == SignalingStateStable { return next, nil } // have-remote-offer->SetLocal(pranswer)->have-local-pranswer case SDPTypePranswer: if next == SignalingStateHaveLocalPranswer { return next, nil } } } case SignalingStateHaveLocalPranswer: if op == stateChangeOpSetLocal && sdpType == SDPTypeAnswer { // have-local-pranswer->SetLocal(answer)->stable if next == SignalingStateStable { return next, nil } } } return cur, &rtcerr.InvalidModificationError{ Err: fmt.Errorf("%w: %s->%s(%s)->%s", errSignalingStateProposedTransitionInvalid, cur, op, sdpType, next), } }