github.com/pion/webrtc/v4@v4.0.1/iceserver.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 package webrtc 8 9 import ( 10 "encoding/json" 11 12 "github.com/pion/stun/v3" 13 "github.com/pion/webrtc/v4/pkg/rtcerr" 14 ) 15 16 // ICEServer describes a single STUN and TURN server that can be used by 17 // the ICEAgent to establish a connection with a peer. 18 type ICEServer struct { 19 URLs []string `json:"urls"` 20 Username string `json:"username,omitempty"` 21 Credential interface{} `json:"credential,omitempty"` 22 CredentialType ICECredentialType `json:"credentialType,omitempty"` 23 } 24 25 func (s ICEServer) parseURL(i int) (*stun.URI, error) { 26 return stun.ParseURI(s.URLs[i]) 27 } 28 29 func (s ICEServer) validate() error { 30 _, err := s.urls() 31 return err 32 } 33 34 func (s ICEServer) urls() ([]*stun.URI, error) { 35 urls := []*stun.URI{} 36 37 for i := range s.URLs { 38 url, err := s.parseURL(i) 39 if err != nil { 40 return nil, &rtcerr.InvalidAccessError{Err: err} 41 } 42 43 if url.Scheme == stun.SchemeTypeTURN || url.Scheme == stun.SchemeTypeTURNS { 44 // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.2) 45 if s.Username == "" || s.Credential == nil { 46 return nil, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredentials} 47 } 48 url.Username = s.Username 49 50 switch s.CredentialType { 51 case ICECredentialTypePassword: 52 // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.3) 53 password, ok := s.Credential.(string) 54 if !ok { 55 return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials} 56 } 57 url.Password = password 58 59 case ICECredentialTypeOauth: 60 // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.4) 61 if _, ok := s.Credential.(OAuthCredential); !ok { 62 return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials} 63 } 64 65 default: 66 return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials} 67 } 68 } 69 70 urls = append(urls, url) 71 } 72 73 return urls, nil 74 } 75 76 func iceserverUnmarshalUrls(val interface{}) (*[]string, error) { 77 s, ok := val.([]interface{}) 78 if !ok { 79 return nil, errInvalidICEServer 80 } 81 out := make([]string, len(s)) 82 for idx, url := range s { 83 out[idx], ok = url.(string) 84 if !ok { 85 return nil, errInvalidICEServer 86 } 87 } 88 return &out, nil 89 } 90 91 func iceserverUnmarshalOauth(val interface{}) (*OAuthCredential, error) { 92 c, ok := val.(map[string]interface{}) 93 if !ok { 94 return nil, errInvalidICEServer 95 } 96 MACKey, ok := c["MACKey"].(string) 97 if !ok { 98 return nil, errInvalidICEServer 99 } 100 AccessToken, ok := c["AccessToken"].(string) 101 if !ok { 102 return nil, errInvalidICEServer 103 } 104 return &OAuthCredential{ 105 MACKey: MACKey, 106 AccessToken: AccessToken, 107 }, nil 108 } 109 110 func (s *ICEServer) iceserverUnmarshalFields(m map[string]interface{}) error { 111 if val, ok := m["urls"]; ok { 112 u, err := iceserverUnmarshalUrls(val) 113 if err != nil { 114 return err 115 } 116 s.URLs = *u 117 } else { 118 s.URLs = []string{} 119 } 120 121 if val, ok := m["username"]; ok { 122 s.Username, ok = val.(string) 123 if !ok { 124 return errInvalidICEServer 125 } 126 } 127 if val, ok := m["credentialType"]; ok { 128 ct, ok := val.(string) 129 if !ok { 130 return errInvalidICEServer 131 } 132 tpe, err := newICECredentialType(ct) 133 if err != nil { 134 return err 135 } 136 s.CredentialType = tpe 137 } else { 138 s.CredentialType = ICECredentialTypePassword 139 } 140 if val, ok := m["credential"]; ok { 141 switch s.CredentialType { 142 case ICECredentialTypePassword: 143 s.Credential = val 144 case ICECredentialTypeOauth: 145 c, err := iceserverUnmarshalOauth(val) 146 if err != nil { 147 return err 148 } 149 s.Credential = *c 150 default: 151 return errInvalidICECredentialTypeString 152 } 153 } 154 return nil 155 } 156 157 // UnmarshalJSON parses the JSON-encoded data and stores the result 158 func (s *ICEServer) UnmarshalJSON(b []byte) error { 159 var tmp interface{} 160 err := json.Unmarshal(b, &tmp) 161 if err != nil { 162 return err 163 } 164 if m, ok := tmp.(map[string]interface{}); ok { 165 return s.iceserverUnmarshalFields(m) 166 } 167 return errInvalidICEServer 168 } 169 170 // MarshalJSON returns the JSON encoding 171 func (s ICEServer) MarshalJSON() ([]byte, error) { 172 m := make(map[string]interface{}) 173 m["urls"] = s.URLs 174 if s.Username != "" { 175 m["username"] = s.Username 176 } 177 if s.Credential != nil { 178 m["credential"] = s.Credential 179 } 180 m["credentialType"] = s.CredentialType 181 return json.Marshal(m) 182 }