Skip to content

Commit

Permalink
Add json support to ICECredentialType
Browse files Browse the repository at this point in the history
This allows the interface to be slightly closer to
upstream interfaces.
  • Loading branch information
wdouglass authored and Sean-Der committed May 6, 2022
1 parent cecdbc9 commit c9aaf81
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 12 deletions.
2 changes: 1 addition & 1 deletion configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestConfiguration_getICEServers(t *testing.T) {

func TestConfigurationJSON(t *testing.T) {
j := `{
"iceServers": [{"URLs": ["turn:turn.example.org"],
"iceServers": [{"urls": ["turn:turn.example.org"],
"username": "jch",
"credential": "topsecret"
}],
Expand Down
3 changes: 3 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ var (

errStatsICECandidateStateInvalid = errors.New("cannot convert to StatsICECandidatePairStateSucceeded invalid ice candidate state")

errInvalidICECredentialTypeString = errors.New("invalid ICECredentialType")
errInvalidICEServer = errors.New("invalid ICEServer")

errICETransportNotInNew = errors.New("ICETransport can only be called in ICETransportStateNew")

errCertificatePEMFormatError = errors.New("bad Certificate PEM format")
Expand Down
32 changes: 31 additions & 1 deletion icecredentialtype.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package webrtc

import (
"encoding/json"
"fmt"
)

// ICECredentialType indicates the type of credentials used to connect to
// an ICE server.
type ICECredentialType int

const (
// ICECredentialTypePassword describes username and password based
// credentials as described in https://tools.ietf.org/html/rfc5389.
ICECredentialTypePassword ICECredentialType = iota
ICECredentialTypePassword ICECredentialType = iota + 1

// ICECredentialTypeOauth describes token based credential as described
// in https://tools.ietf.org/html/rfc7635.
Expand All @@ -33,6 +38,8 @@ func newICECredentialType(raw string) ICECredentialType {

func (t ICECredentialType) String() string {
switch t {
case Unknown:
return ""
case ICECredentialTypePassword:
return iceCredentialTypePasswordStr
case ICECredentialTypeOauth:
Expand All @@ -41,3 +48,26 @@ func (t ICECredentialType) String() string {
return ErrUnknownType.Error()
}
}

// UnmarshalJSON parses the JSON-encoded data and stores the result
func (t *ICECredentialType) UnmarshalJSON(b []byte) error {
var val string
var tmp ICECredentialType
if err := json.Unmarshal(b, &val); err != nil {
return err
}

tmp = newICECredentialType(val)

if (tmp == ICECredentialType(Unknown)) && (val != "") {
return fmt.Errorf("%w: (%s)", errInvalidICECredentialTypeString, val)
}

*t = tmp
return nil
}

// MarshalJSON returns the JSON encoding
func (t ICECredentialType) MarshalJSON() ([]byte, error) {
return json.Marshal(t.String())
}
57 changes: 57 additions & 0 deletions icecredentialtype_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package webrtc

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -41,3 +42,59 @@ func TestICECredentialType_String(t *testing.T) {
)
}
}

func TestICECredentialType_new(t *testing.T) {
testCases := []struct {
credentialType ICECredentialType
expectedString string
}{
{ICECredentialTypePassword, "password"},
{ICECredentialTypeOauth, "oauth"},
}

for i, testCase := range testCases {
assert.Equal(t,
newICECredentialType(testCase.expectedString),
testCase.credentialType,
"testCase: %d %v", i, testCase,
)
}
}

func TestICECredentialType_Json(t *testing.T) {
testCases := []struct {
credentialType ICECredentialType
jsonRepresentation []byte
}{
{ICECredentialTypePassword, []byte("\"password\"")},
{ICECredentialTypeOauth, []byte("\"oauth\"")},
}

for i, testCase := range testCases {
m, err := json.Marshal(testCase.credentialType)
assert.NoError(t, err)
assert.Equal(t,
testCase.jsonRepresentation,
m,
"Marshal testCase: %d %v", i, testCase,
)
var ct ICECredentialType
err = json.Unmarshal(testCase.jsonRepresentation, &ct)
assert.NoError(t, err)
assert.Equal(t,
testCase.credentialType,
ct,
"Unmarshal testCase: %d %v", i, testCase,
)
}

{
ct := ICECredentialType(1000)
err := json.Unmarshal([]byte("\"invalid\""), &ct)
assert.Error(t, err)
assert.Equal(t, ct, ICECredentialType(1000))
err = json.Unmarshal([]byte("\"invalid"), &ct)
assert.Error(t, err)
assert.Equal(t, ct, ICECredentialType(1000))
}
}
110 changes: 110 additions & 0 deletions iceserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package webrtc

import (
"encoding/json"

"github.com/pion/ice/v2"
"github.com/pion/webrtc/v3/pkg/rtcerr"
)
Expand Down Expand Up @@ -67,3 +69,111 @@ func (s ICEServer) urls() ([]*ice.URL, error) {

return urls, nil
}

func iceserverUnmarshalUrls(val interface{}) (*[]string, error) {
s, ok := val.([]interface{})
if !ok {
return nil, errInvalidICEServer
}
out := make([]string, len(s))
for idx, url := range s {
out[idx], ok = url.(string)
if !ok {
return nil, errInvalidICEServer
}
}
return &out, nil
}

func iceserverUnmarshalOauth(val interface{}) (*OAuthCredential, error) {
c, ok := val.(map[string]interface{})
if !ok {
return nil, errInvalidICEServer
}
MACKey, ok := c["MACKey"].(string)
if !ok {
return nil, errInvalidICEServer
}
AccessToken, ok := c["AccessToken"].(string)
if !ok {
return nil, errInvalidICEServer
}
return &OAuthCredential{
MACKey: MACKey,
AccessToken: AccessToken,
}, nil
}

func (s *ICEServer) iceserverUnmarshalFields(m map[string]interface{}) error {
if val, ok := m["urls"]; ok {
u, err := iceserverUnmarshalUrls(val)
if err != nil {
return err
}
s.URLs = *u
} else {
s.URLs = []string{}
}

if val, ok := m["username"]; ok {
s.Username, ok = val.(string)
if !ok {
return errInvalidICEServer
}
}
if val, ok := m["credentialType"]; ok {
ct, ok := val.(string)
if !ok || (newICECredentialType(ct) == ICECredentialType(Unknown)) {
return errInvalidICEServer
}
s.CredentialType = newICECredentialType(ct)
} else {
s.CredentialType = ICECredentialType(Unknown)
}
if val, ok := m["credential"]; ok {
switch s.CredentialType {
case ICECredentialType(Unknown):
s.Credential = val
case ICECredentialTypePassword:
s.Credential = val
case ICECredentialTypeOauth:
c, err := iceserverUnmarshalOauth(val)
if err != nil {
return err
}
s.Credential = *c
default:
return errInvalidICECredentialTypeString
}
}
return nil
}

// UnmarshalJSON parses the JSON-encoded data and stores the result
func (s *ICEServer) UnmarshalJSON(b []byte) error {
var tmp interface{}
err := json.Unmarshal(b, &tmp)
if err != nil {
return err
}
if m, ok := tmp.(map[string]interface{}); ok {
return s.iceserverUnmarshalFields(m)
}
return errInvalidICEServer
}

// MarshalJSON returns the JSON encoding
func (s ICEServer) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{})
m["urls"] = s.URLs
if s.Username != "" {
m["username"] = s.Username
}
if s.Credential != nil {
m["credential"] = s.Credential
}
if s.CredentialType != ICECredentialType(Unknown) {
m["credentialType"] = s.CredentialType
}
return json.Marshal(m)
}
25 changes: 24 additions & 1 deletion iceserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package webrtc

import (
"encoding/json"
"testing"

"github.com/pion/ice/v2"
Expand Down Expand Up @@ -41,7 +42,13 @@ func TestICEServer_validate(t *testing.T) {
}

for i, testCase := range testCases {
_, err := testCase.iceServer.urls()
var iceServer ICEServer
jsonobj, err := json.Marshal(testCase.iceServer)
assert.NoError(t, err)
err = json.Unmarshal(jsonobj, &iceServer)
assert.NoError(t, err)
assert.Equal(t, iceServer, testCase.iceServer)
_, err = testCase.iceServer.urls()
assert.Nil(t, err, "testCase: %d %v", i, testCase)
}
})
Expand Down Expand Up @@ -88,4 +95,20 @@ func TestICEServer_validate(t *testing.T) {
)
}
})
t.Run("JsonFailure", func(t *testing.T) {
testCases := [][]byte{
[]byte(`{"urls":"NOTAURL","username":"unittest","credential":"placeholder","credentialType":"password"}`),
[]byte(`{"urls":["turn:[2001:db8:1234:5678::1]?transport=udp"],"username":"unittest","credential":"placeholder","credentialType":"invalid"}`),
[]byte(`{"urls":["turn:[2001:db8:1234:5678::1]?transport=udp"],"username":6,"credential":"placeholder","credentialType":"password"}`),
[]byte(`{"urls":["turn:192.158.29.39?transport=udp"],"username":"unittest","credential":{"Bad Object": true},"credentialType":"oauth"}`),
[]byte(`{"urls":["turn:192.158.29.39?transport=udp"],"username":"unittest","credential":{"MACKey":"WmtzanB3ZW9peFhtdm42NzUzNG0=","AccessToken":null,"credentialType":"oauth"}`),
[]byte(`{"urls":["turn:192.158.29.39?transport=udp"],"username":"unittest","credential":{"MACKey":"WmtzanB3ZW9peFhtdm42NzUzNG0=","AccessToken":null,"credentialType":"password"}`),
[]byte(`{"urls":["turn:192.158.29.39?transport=udp"],"username":"unittest","credential":{"MACKey":1337,"AccessToken":"AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ5VhNDgeMR3+ZlZ35byg972fW8QjpEl7bx91YLBPFsIhsxloWcXPhA=="},"credentialType":"oauth"}`),
}
for i, testCase := range testCases {
var tc ICEServer
err := json.Unmarshal(testCase, &tc)
assert.Error(t, err, "testCase: %d %v", i, string(testCase))
}
})
}
60 changes: 51 additions & 9 deletions peerconnection_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,13 +566,33 @@ func iceServersToValue(iceServers []ICEServer) js.Value {
return js.ValueOf(maps)
}

func oauthCredentialToValue(o OAuthCredential) js.Value {
out := map[string]interface{}{
"MACKey": o.MACKey,
"AccessToken": o.AccessToken,
}
return js.ValueOf(out)
}

func iceServerToValue(server ICEServer) js.Value {
return js.ValueOf(map[string]interface{}{
"urls": stringsToValue(server.URLs), // required
"username": stringToValueOrUndefined(server.Username),
"credential": interfaceToValueOrUndefined(server.Credential),
"credentialType": stringEnumToValueOrUndefined(server.CredentialType.String()),
})
out := map[string]interface{}{
"urls": stringsToValue(server.URLs), // required
}
if server.Username != "" {
out["username"] = stringToValueOrUndefined(server.Username)
}
if server.Credential != nil {
switch t := server.Credential.(type) {
case string:
out["credential"] = stringToValueOrUndefined(t)
case OAuthCredential:
out["credential"] = oauthCredentialToValue(t)
}
}
if server.CredentialType != ICECredentialType(Unknown) {
out["credentialType"] = stringEnumToValueOrUndefined(server.CredentialType.String())
}
return js.ValueOf(out)
}

func valueToConfiguration(configValue js.Value) Configuration {
Expand Down Expand Up @@ -603,14 +623,36 @@ func valueToICEServers(iceServersValue js.Value) []ICEServer {
return iceServers
}

func valueToICECredential(iceCredentialValue js.Value) interface{} {
if iceCredentialValue.IsNull() || iceCredentialValue.IsUndefined() {
return nil
}
if iceCredentialValue.Type() == js.TypeString {
return iceCredentialValue.String()
}
if iceCredentialValue.Type() == js.TypeObject {
return OAuthCredential{
MACKey: iceCredentialValue.Get("MACKey").String(),
AccessToken: iceCredentialValue.Get("AccessToken").String(),
}
}
return nil
}

func valueToICEServer(iceServerValue js.Value) ICEServer {
return ICEServer{
s := ICEServer{
URLs: valueToStrings(iceServerValue.Get("urls")), // required
Username: valueToStringOrZero(iceServerValue.Get("username")),
// Note: Credential and CredentialType are not currently supported.
// Credential: iceServerValue.Get("credential"),
// CredentialType: newICECredentialType(valueToStringOrZero(iceServerValue.Get("credentialType"))),
Credential: valueToICECredential(iceServerValue.Get("credential")),
CredentialType: newICECredentialType(valueToStringOrZero(iceServerValue.Get("credentialType"))),
}

// default to password
if s.CredentialType == ICECredentialType(Unknown) {
s.CredentialType = ICECredentialTypePassword
}
return s
}

func valueToICECandidate(val js.Value) *ICECandidate {
Expand Down
Loading

0 comments on commit c9aaf81

Please sign in to comment.