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  }