// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package webrtc import ( "fmt" "github.com/pion/ice/v2" ) // ICECandidate represents a ice candidate type ICECandidate struct { statsID string Foundation string `json:"foundation"` Priority uint32 `json:"priority"` Address string `json:"address"` Protocol ICEProtocol `json:"protocol"` Port uint16 `json:"port"` Typ ICECandidateType `json:"type"` Component uint16 `json:"component"` RelatedAddress string `json:"relatedAddress"` RelatedPort uint16 `json:"relatedPort"` TCPType string `json:"tcpType"` } // Conversion for package ice func newICECandidatesFromICE(iceCandidates []ice.Candidate) ([]ICECandidate, error) { candidates := []ICECandidate{} for _, i := range iceCandidates { c, err := newICECandidateFromICE(i) if err != nil { return nil, err } candidates = append(candidates, c) } return candidates, nil } func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) { typ, err := convertTypeFromICE(i.Type()) if err != nil { return ICECandidate{}, err } protocol, err := NewICEProtocol(i.NetworkType().NetworkShort()) if err != nil { return ICECandidate{}, err } c := ICECandidate{ statsID: i.ID(), Foundation: i.Foundation(), Priority: i.Priority(), Address: i.Address(), Protocol: protocol, Port: uint16(i.Port()), Component: i.Component(), Typ: typ, TCPType: i.TCPType().String(), } if i.RelatedAddress() != nil { c.RelatedAddress = i.RelatedAddress().Address c.RelatedPort = uint16(i.RelatedAddress().Port) } return c, nil } func (c ICECandidate) toICE() (ice.Candidate, error) { candidateID := c.statsID switch c.Typ { case ICECandidateTypeHost: config := ice.CandidateHostConfig{ CandidateID: candidateID, Network: c.Protocol.String(), Address: c.Address, Port: int(c.Port), Component: c.Component, TCPType: ice.NewTCPType(c.TCPType), Foundation: c.Foundation, Priority: c.Priority, } return ice.NewCandidateHost(&config) case ICECandidateTypeSrflx: config := ice.CandidateServerReflexiveConfig{ CandidateID: candidateID, Network: c.Protocol.String(), Address: c.Address, Port: int(c.Port), Component: c.Component, Foundation: c.Foundation, Priority: c.Priority, RelAddr: c.RelatedAddress, RelPort: int(c.RelatedPort), } return ice.NewCandidateServerReflexive(&config) case ICECandidateTypePrflx: config := ice.CandidatePeerReflexiveConfig{ CandidateID: candidateID, Network: c.Protocol.String(), Address: c.Address, Port: int(c.Port), Component: c.Component, Foundation: c.Foundation, Priority: c.Priority, RelAddr: c.RelatedAddress, RelPort: int(c.RelatedPort), } return ice.NewCandidatePeerReflexive(&config) case ICECandidateTypeRelay: config := ice.CandidateRelayConfig{ CandidateID: candidateID, Network: c.Protocol.String(), Address: c.Address, Port: int(c.Port), Component: c.Component, Foundation: c.Foundation, Priority: c.Priority, RelAddr: c.RelatedAddress, RelPort: int(c.RelatedPort), } return ice.NewCandidateRelay(&config) default: return nil, fmt.Errorf("%w: %s", errICECandidateTypeUnknown, c.Typ) } } func convertTypeFromICE(t ice.CandidateType) (ICECandidateType, error) { switch t { case ice.CandidateTypeHost: return ICECandidateTypeHost, nil case ice.CandidateTypeServerReflexive: return ICECandidateTypeSrflx, nil case ice.CandidateTypePeerReflexive: return ICECandidateTypePrflx, nil case ice.CandidateTypeRelay: return ICECandidateTypeRelay, nil default: return ICECandidateType(t), fmt.Errorf("%w: %s", errICECandidateTypeUnknown, t) } } func (c ICECandidate) String() string { ic, err := c.toICE() if err != nil { return fmt.Sprintf("%#v failed to convert to ICE: %s", c, err) } return ic.String() } // ToJSON returns an ICECandidateInit // as indicated by the spec https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson func (c ICECandidate) ToJSON() ICECandidateInit { zeroVal := uint16(0) emptyStr := "" candidateStr := "" candidate, err := c.toICE() if err == nil { candidateStr = candidate.Marshal() } return ICECandidateInit{ Candidate: fmt.Sprintf("candidate:%s", candidateStr), SDPMid: &emptyStr, SDPMLineIndex: &zeroVal, } }