1package ice 2 3import ( 4 "net" 5 "net/url" 6 "strconv" 7) 8 9// SchemeType indicates the type of server used in the ice.URL structure. 10type SchemeType int 11 12// Unknown defines default public constant to use for "enum" like struct 13// comparisons when no value was defined. 14const Unknown = iota 15 16const ( 17 // SchemeTypeSTUN indicates the URL represents a STUN server. 18 SchemeTypeSTUN SchemeType = iota + 1 19 20 // SchemeTypeSTUNS indicates the URL represents a STUNS (secure) server. 21 SchemeTypeSTUNS 22 23 // SchemeTypeTURN indicates the URL represents a TURN server. 24 SchemeTypeTURN 25 26 // SchemeTypeTURNS indicates the URL represents a TURNS (secure) server. 27 SchemeTypeTURNS 28) 29 30// NewSchemeType defines a procedure for creating a new SchemeType from a raw 31// string naming the scheme type. 32func NewSchemeType(raw string) SchemeType { 33 switch raw { 34 case "stun": 35 return SchemeTypeSTUN 36 case "stuns": 37 return SchemeTypeSTUNS 38 case "turn": 39 return SchemeTypeTURN 40 case "turns": 41 return SchemeTypeTURNS 42 default: 43 return SchemeType(Unknown) 44 } 45} 46 47func (t SchemeType) String() string { 48 switch t { 49 case SchemeTypeSTUN: 50 return "stun" 51 case SchemeTypeSTUNS: 52 return "stuns" 53 case SchemeTypeTURN: 54 return "turn" 55 case SchemeTypeTURNS: 56 return "turns" 57 default: 58 return ErrUnknownType.Error() 59 } 60} 61 62// ProtoType indicates the transport protocol type that is used in the ice.URL 63// structure. 64type ProtoType int 65 66const ( 67 // ProtoTypeUDP indicates the URL uses a UDP transport. 68 ProtoTypeUDP ProtoType = iota + 1 69 70 // ProtoTypeTCP indicates the URL uses a TCP transport. 71 ProtoTypeTCP 72) 73 74// NewProtoType defines a procedure for creating a new ProtoType from a raw 75// string naming the transport protocol type. 76func NewProtoType(raw string) ProtoType { 77 switch raw { 78 case "udp": 79 return ProtoTypeUDP 80 case "tcp": 81 return ProtoTypeTCP 82 default: 83 return ProtoType(Unknown) 84 } 85} 86 87func (t ProtoType) String() string { 88 switch t { 89 case ProtoTypeUDP: 90 return "udp" 91 case ProtoTypeTCP: 92 return "tcp" 93 default: 94 return ErrUnknownType.Error() 95 } 96} 97 98// URL represents a STUN (rfc7064) or TURN (rfc7065) URL 99type URL struct { 100 Scheme SchemeType 101 Host string 102 Port int 103 Username string 104 Password string 105 Proto ProtoType 106} 107 108// ParseURL parses a STUN or TURN urls following the ABNF syntax described in 109// https://tools.ietf.org/html/rfc7064 and https://tools.ietf.org/html/rfc7065 110// respectively. 111func ParseURL(raw string) (*URL, error) { //nolint:gocognit 112 rawParts, err := url.Parse(raw) 113 if err != nil { 114 return nil, err 115 } 116 117 var u URL 118 u.Scheme = NewSchemeType(rawParts.Scheme) 119 if u.Scheme == SchemeType(Unknown) { 120 return nil, ErrSchemeType 121 } 122 123 var rawPort string 124 if u.Host, rawPort, err = net.SplitHostPort(rawParts.Opaque); err != nil { 125 if e, ok := err.(*net.AddrError); ok { 126 if e.Err == "missing port in address" { 127 nextRawURL := u.Scheme.String() + ":" + rawParts.Opaque 128 switch { 129 case u.Scheme == SchemeTypeSTUN || u.Scheme == SchemeTypeTURN: 130 nextRawURL += ":3478" 131 if rawParts.RawQuery != "" { 132 nextRawURL += "?" + rawParts.RawQuery 133 } 134 return ParseURL(nextRawURL) 135 case u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS: 136 nextRawURL += ":5349" 137 if rawParts.RawQuery != "" { 138 nextRawURL += "?" + rawParts.RawQuery 139 } 140 return ParseURL(nextRawURL) 141 } 142 } 143 } 144 return nil, err 145 } 146 147 if u.Host == "" { 148 return nil, ErrHost 149 } 150 151 if u.Port, err = strconv.Atoi(rawPort); err != nil { 152 return nil, ErrPort 153 } 154 155 switch u.Scheme { 156 case SchemeTypeSTUN: 157 qArgs, err := url.ParseQuery(rawParts.RawQuery) 158 if err != nil || len(qArgs) > 0 { 159 return nil, ErrSTUNQuery 160 } 161 u.Proto = ProtoTypeUDP 162 case SchemeTypeSTUNS: 163 qArgs, err := url.ParseQuery(rawParts.RawQuery) 164 if err != nil || len(qArgs) > 0 { 165 return nil, ErrSTUNQuery 166 } 167 u.Proto = ProtoTypeTCP 168 case SchemeTypeTURN: 169 proto, err := parseProto(rawParts.RawQuery) 170 if err != nil { 171 return nil, err 172 } 173 174 u.Proto = proto 175 if u.Proto == ProtoType(Unknown) { 176 u.Proto = ProtoTypeUDP 177 } 178 case SchemeTypeTURNS: 179 proto, err := parseProto(rawParts.RawQuery) 180 if err != nil { 181 return nil, err 182 } 183 184 u.Proto = proto 185 if u.Proto == ProtoType(Unknown) { 186 u.Proto = ProtoTypeTCP 187 } 188 } 189 190 return &u, nil 191} 192 193func parseProto(raw string) (ProtoType, error) { 194 qArgs, err := url.ParseQuery(raw) 195 if err != nil || len(qArgs) > 1 { 196 return ProtoType(Unknown), ErrInvalidQuery 197 } 198 199 var proto ProtoType 200 if rawProto := qArgs.Get("transport"); rawProto != "" { 201 if proto = NewProtoType(rawProto); proto == ProtoType(0) { 202 return ProtoType(Unknown), ErrProtoType 203 } 204 return proto, nil 205 } 206 207 if len(qArgs) > 0 { 208 return ProtoType(Unknown), ErrInvalidQuery 209 } 210 211 return proto, nil 212} 213 214func (u URL) String() string { 215 rawURL := u.Scheme.String() + ":" + net.JoinHostPort(u.Host, strconv.Itoa(u.Port)) 216 if u.Scheme == SchemeTypeTURN || u.Scheme == SchemeTypeTURNS { 217 rawURL += "?transport=" + u.Proto.String() 218 } 219 return rawURL 220} 221 222// IsSecure returns whether the this URL's scheme describes secure scheme or not. 223func (u URL) IsSecure() bool { 224 return u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS 225} 226