226 lines
5.0 KiB
Go
226 lines
5.0 KiB
Go
package ice
|
|
|
|
import (
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
// SchemeType indicates the type of server used in the ice.URL structure.
|
|
type SchemeType int
|
|
|
|
// Unknown defines default public constant to use for "enum" like struct
|
|
// comparisons when no value was defined.
|
|
const Unknown = iota
|
|
|
|
const (
|
|
// SchemeTypeSTUN indicates the URL represents a STUN server.
|
|
SchemeTypeSTUN SchemeType = iota + 1
|
|
|
|
// SchemeTypeSTUNS indicates the URL represents a STUNS (secure) server.
|
|
SchemeTypeSTUNS
|
|
|
|
// SchemeTypeTURN indicates the URL represents a TURN server.
|
|
SchemeTypeTURN
|
|
|
|
// SchemeTypeTURNS indicates the URL represents a TURNS (secure) server.
|
|
SchemeTypeTURNS
|
|
)
|
|
|
|
// NewSchemeType defines a procedure for creating a new SchemeType from a raw
|
|
// string naming the scheme type.
|
|
func NewSchemeType(raw string) SchemeType {
|
|
switch raw {
|
|
case "stun":
|
|
return SchemeTypeSTUN
|
|
case "stuns":
|
|
return SchemeTypeSTUNS
|
|
case "turn":
|
|
return SchemeTypeTURN
|
|
case "turns":
|
|
return SchemeTypeTURNS
|
|
default:
|
|
return SchemeType(Unknown)
|
|
}
|
|
}
|
|
|
|
func (t SchemeType) String() string {
|
|
switch t {
|
|
case SchemeTypeSTUN:
|
|
return "stun"
|
|
case SchemeTypeSTUNS:
|
|
return "stuns"
|
|
case SchemeTypeTURN:
|
|
return "turn"
|
|
case SchemeTypeTURNS:
|
|
return "turns"
|
|
default:
|
|
return ErrUnknownType.Error()
|
|
}
|
|
}
|
|
|
|
// ProtoType indicates the transport protocol type that is used in the ice.URL
|
|
// structure.
|
|
type ProtoType int
|
|
|
|
const (
|
|
// ProtoTypeUDP indicates the URL uses a UDP transport.
|
|
ProtoTypeUDP ProtoType = iota + 1
|
|
|
|
// ProtoTypeTCP indicates the URL uses a TCP transport.
|
|
ProtoTypeTCP
|
|
)
|
|
|
|
// NewProtoType defines a procedure for creating a new ProtoType from a raw
|
|
// string naming the transport protocol type.
|
|
func NewProtoType(raw string) ProtoType {
|
|
switch raw {
|
|
case "udp":
|
|
return ProtoTypeUDP
|
|
case "tcp":
|
|
return ProtoTypeTCP
|
|
default:
|
|
return ProtoType(Unknown)
|
|
}
|
|
}
|
|
|
|
func (t ProtoType) String() string {
|
|
switch t {
|
|
case ProtoTypeUDP:
|
|
return "udp"
|
|
case ProtoTypeTCP:
|
|
return "tcp"
|
|
default:
|
|
return ErrUnknownType.Error()
|
|
}
|
|
}
|
|
|
|
// URL represents a STUN (rfc7064) or TURN (rfc7065) URL
|
|
type URL struct {
|
|
Scheme SchemeType
|
|
Host string
|
|
Port int
|
|
Username string
|
|
Password string
|
|
Proto ProtoType
|
|
}
|
|
|
|
// ParseURL parses a STUN or TURN urls following the ABNF syntax described in
|
|
// https://tools.ietf.org/html/rfc7064 and https://tools.ietf.org/html/rfc7065
|
|
// respectively.
|
|
func ParseURL(raw string) (*URL, error) { //nolint:gocognit
|
|
rawParts, err := url.Parse(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var u URL
|
|
u.Scheme = NewSchemeType(rawParts.Scheme)
|
|
if u.Scheme == SchemeType(Unknown) {
|
|
return nil, ErrSchemeType
|
|
}
|
|
|
|
var rawPort string
|
|
if u.Host, rawPort, err = net.SplitHostPort(rawParts.Opaque); err != nil {
|
|
if e, ok := err.(*net.AddrError); ok {
|
|
if e.Err == "missing port in address" {
|
|
nextRawURL := u.Scheme.String() + ":" + rawParts.Opaque
|
|
switch {
|
|
case u.Scheme == SchemeTypeSTUN || u.Scheme == SchemeTypeTURN:
|
|
nextRawURL += ":3478"
|
|
if rawParts.RawQuery != "" {
|
|
nextRawURL += "?" + rawParts.RawQuery
|
|
}
|
|
return ParseURL(nextRawURL)
|
|
case u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS:
|
|
nextRawURL += ":5349"
|
|
if rawParts.RawQuery != "" {
|
|
nextRawURL += "?" + rawParts.RawQuery
|
|
}
|
|
return ParseURL(nextRawURL)
|
|
}
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
if u.Host == "" {
|
|
return nil, ErrHost
|
|
}
|
|
|
|
if u.Port, err = strconv.Atoi(rawPort); err != nil {
|
|
return nil, ErrPort
|
|
}
|
|
|
|
switch u.Scheme {
|
|
case SchemeTypeSTUN:
|
|
qArgs, err := url.ParseQuery(rawParts.RawQuery)
|
|
if err != nil || len(qArgs) > 0 {
|
|
return nil, ErrSTUNQuery
|
|
}
|
|
u.Proto = ProtoTypeUDP
|
|
case SchemeTypeSTUNS:
|
|
qArgs, err := url.ParseQuery(rawParts.RawQuery)
|
|
if err != nil || len(qArgs) > 0 {
|
|
return nil, ErrSTUNQuery
|
|
}
|
|
u.Proto = ProtoTypeTCP
|
|
case SchemeTypeTURN:
|
|
proto, err := parseProto(rawParts.RawQuery)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
u.Proto = proto
|
|
if u.Proto == ProtoType(Unknown) {
|
|
u.Proto = ProtoTypeUDP
|
|
}
|
|
case SchemeTypeTURNS:
|
|
proto, err := parseProto(rawParts.RawQuery)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
u.Proto = proto
|
|
if u.Proto == ProtoType(Unknown) {
|
|
u.Proto = ProtoTypeTCP
|
|
}
|
|
}
|
|
|
|
return &u, nil
|
|
}
|
|
|
|
func parseProto(raw string) (ProtoType, error) {
|
|
qArgs, err := url.ParseQuery(raw)
|
|
if err != nil || len(qArgs) > 1 {
|
|
return ProtoType(Unknown), ErrInvalidQuery
|
|
}
|
|
|
|
var proto ProtoType
|
|
if rawProto := qArgs.Get("transport"); rawProto != "" {
|
|
if proto = NewProtoType(rawProto); proto == ProtoType(0) {
|
|
return ProtoType(Unknown), ErrProtoType
|
|
}
|
|
return proto, nil
|
|
}
|
|
|
|
if len(qArgs) > 0 {
|
|
return ProtoType(Unknown), ErrInvalidQuery
|
|
}
|
|
|
|
return proto, nil
|
|
}
|
|
|
|
func (u URL) String() string {
|
|
rawURL := u.Scheme.String() + ":" + net.JoinHostPort(u.Host, strconv.Itoa(u.Port))
|
|
if u.Scheme == SchemeTypeTURN || u.Scheme == SchemeTypeTURNS {
|
|
rawURL += "?transport=" + u.Proto.String()
|
|
}
|
|
return rawURL
|
|
}
|
|
|
|
// IsSecure returns whether the this URL's scheme describes secure scheme or not.
|
|
func (u URL) IsSecure() bool {
|
|
return u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS
|
|
}
|