1package webrtc
2
3import (
4	"fmt"
5
6	"github.com/pion/ice"
7	"github.com/pion/sdp/v2"
8)
9
10// ICECandidate represents a ice candidate
11type ICECandidate struct {
12	statsID        string
13	Foundation     string           `json:"foundation"`
14	Priority       uint32           `json:"priority"`
15	Address        string           `json:"address"`
16	Protocol       ICEProtocol      `json:"protocol"`
17	Port           uint16           `json:"port"`
18	Typ            ICECandidateType `json:"type"`
19	Component      uint16           `json:"component"`
20	RelatedAddress string           `json:"relatedAddress"`
21	RelatedPort    uint16           `json:"relatedPort"`
22}
23
24// Conversion for package ice
25
26func newICECandidatesFromICE(iceCandidates []ice.Candidate) ([]ICECandidate, error) {
27	candidates := []ICECandidate{}
28
29	for _, i := range iceCandidates {
30		c, err := newICECandidateFromICE(i)
31		if err != nil {
32			return nil, err
33		}
34		candidates = append(candidates, c)
35	}
36
37	return candidates, nil
38}
39
40func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) {
41	typ, err := convertTypeFromICE(i.Type())
42	if err != nil {
43		return ICECandidate{}, err
44	}
45	protocol, err := NewICEProtocol(i.NetworkType().NetworkShort())
46	if err != nil {
47		return ICECandidate{}, err
48	}
49
50	c := ICECandidate{
51		statsID:    i.ID(),
52		Foundation: "foundation",
53		Priority:   i.Priority(),
54		Address:    i.Address(),
55		Protocol:   protocol,
56		Port:       uint16(i.Port()),
57		Component:  i.Component(),
58		Typ:        typ,
59	}
60
61	if i.RelatedAddress() != nil {
62		c.RelatedAddress = i.RelatedAddress().Address
63		c.RelatedPort = uint16(i.RelatedAddress().Port)
64	}
65
66	return c, nil
67}
68
69func (c ICECandidate) toICE() (ice.Candidate, error) {
70	candidateID := c.statsID
71	switch c.Typ {
72	case ICECandidateTypeHost:
73		config := ice.CandidateHostConfig{
74			CandidateID: candidateID,
75			Network:     c.Protocol.String(),
76			Address:     c.Address,
77			Port:        int(c.Port),
78			Component:   c.Component,
79		}
80		return ice.NewCandidateHost(&config)
81	case ICECandidateTypeSrflx:
82		config := ice.CandidateServerReflexiveConfig{
83			CandidateID: candidateID,
84			Network:     c.Protocol.String(),
85			Address:     c.Address,
86			Port:        int(c.Port),
87			Component:   c.Component,
88			RelAddr:     c.RelatedAddress,
89			RelPort:     int(c.RelatedPort),
90		}
91		return ice.NewCandidateServerReflexive(&config)
92	case ICECandidateTypePrflx:
93		config := ice.CandidatePeerReflexiveConfig{
94			CandidateID: candidateID,
95			Network:     c.Protocol.String(),
96			Address:     c.Address,
97			Port:        int(c.Port),
98			Component:   c.Component,
99			RelAddr:     c.RelatedAddress,
100			RelPort:     int(c.RelatedPort),
101		}
102		return ice.NewCandidatePeerReflexive(&config)
103	case ICECandidateTypeRelay:
104		config := ice.CandidateRelayConfig{
105			CandidateID: candidateID,
106			Network:     c.Protocol.String(),
107			Address:     c.Address,
108			Port:        int(c.Port),
109			Component:   c.Component,
110			RelAddr:     c.RelatedAddress,
111			RelPort:     int(c.RelatedPort),
112		}
113		return ice.NewCandidateRelay(&config)
114	default:
115		return nil, fmt.Errorf("unknown candidate type: %s", c.Typ)
116	}
117}
118
119func convertTypeFromICE(t ice.CandidateType) (ICECandidateType, error) {
120	switch t {
121	case ice.CandidateTypeHost:
122		return ICECandidateTypeHost, nil
123	case ice.CandidateTypeServerReflexive:
124		return ICECandidateTypeSrflx, nil
125	case ice.CandidateTypePeerReflexive:
126		return ICECandidateTypePrflx, nil
127	case ice.CandidateTypeRelay:
128		return ICECandidateTypeRelay, nil
129	default:
130		return ICECandidateType(t), fmt.Errorf("unknown ICE candidate type: %s", t)
131	}
132}
133
134func (c ICECandidate) String() string {
135	ic, err := c.toICE()
136	if err != nil {
137		return fmt.Sprintf("%#v failed to convert to ICE: %s", c, err)
138	}
139	return ic.String()
140}
141
142func iceCandidateToSDP(c ICECandidate) sdp.ICECandidate {
143	return sdp.ICECandidate{
144		Foundation:     c.Foundation,
145		Priority:       c.Priority,
146		Address:        c.Address,
147		Protocol:       c.Protocol.String(),
148		Port:           c.Port,
149		Component:      c.Component,
150		Typ:            c.Typ.String(),
151		RelatedAddress: c.RelatedAddress,
152		RelatedPort:    c.RelatedPort,
153	}
154}
155
156// ToJSON returns an ICECandidateInit
157// as indicated by the spec https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson
158func (c ICECandidate) ToJSON() ICECandidateInit {
159	var sdpmLineIndex uint16
160	return ICECandidateInit{
161		Candidate:     fmt.Sprintf("candidate:%s", iceCandidateToSDP(c).Marshal()),
162		SDPMLineIndex: &sdpmLineIndex,
163	}
164}
165