From 450601f0e8d9e3778fa95f849f1652f0cd5fff23 Mon Sep 17 00:00:00 2001
From: Juliusz Chroboczek <jch@irif.fr>
Date: Sat, 2 Jan 2021 00:20:47 +0100
Subject: [PATCH] Move ICE code into own module, add tests.

---
 galene.go             |  5 ++--
 {group => ice}/ice.go | 30 ++++++++++----------
 ice/ice_test.go       | 65 +++++++++++++++++++++++++++++++++++++++++++
 rtpconn/rtpconn.go    |  5 ++--
 rtpconn/webclient.go  |  5 ++--
 5 files changed, 89 insertions(+), 21 deletions(-)
 rename {group => ice}/ice.go (81%)
 create mode 100644 ice/ice_test.go

diff --git a/galene.go b/galene.go
index b50f2e8..5e18cd5 100644
--- a/galene.go
+++ b/galene.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/jech/galene/diskwriter"
 	"github.com/jech/galene/group"
+	"github.com/jech/galene/ice"
 	"github.com/jech/galene/webserver"
 )
 
@@ -39,7 +40,7 @@ func main() {
 	flag.StringVar(&mutexprofile, "mutexprofile", "",
 		"store mutex profile in `file`")
 	flag.BoolVar(&group.UseMDNS, "mdns", false, "gather mDNS addresses")
-	flag.BoolVar(&group.ICERelayOnly, "relay-only", false,
+	flag.BoolVar(&ice.ICERelayOnly, "relay-only", false,
 		"require use of TURN relays for all media traffic")
 	flag.Parse()
 
@@ -81,7 +82,7 @@ func main() {
 		}()
 	}
 
-	group.ICEFilename = filepath.Join(dataDir, "ice-servers.json")
+	ice.ICEFilename = filepath.Join(dataDir, "ice-servers.json")
 
 	go group.ReadPublicGroups()
 
diff --git a/group/ice.go b/ice/ice.go
similarity index 81%
rename from group/ice.go
rename to ice/ice.go
index e7f3fec..a73a700 100644
--- a/group/ice.go
+++ b/ice/ice.go
@@ -1,4 +1,4 @@
-package group
+package ice
 
 import (
 	"bytes"
@@ -16,14 +16,14 @@ import (
 	"github.com/pion/webrtc/v3"
 )
 
-type ICEServer struct {
+type Server struct {
 	URLs           []string    `json:"urls"`
 	Username       string      `json:"username,omitempty"`
 	Credential     interface{} `json:"credential,omitempty"`
 	CredentialType string      `json:"credentialType,omitempty"`
 }
 
-func getICEServer(server ICEServer) (webrtc.ICEServer, error) {
+func getServer(server Server) (webrtc.ICEServer, error) {
 	s := webrtc.ICEServer{
 		URLs:       server.URLs,
 		Username:   server.Username,
@@ -65,16 +65,16 @@ func getICEServer(server ICEServer) (webrtc.ICEServer, error) {
 var ICEFilename string
 var ICERelayOnly bool
 
-type iceConf struct {
+type configuration struct {
 	conf      webrtc.Configuration
 	timestamp time.Time
 }
 
-var iceConfiguration atomic.Value
+var conf atomic.Value
 
-func updateICEConfiguration() *iceConf {
+func updateICEConfiguration() *configuration {
 	now := time.Now()
-	var conf webrtc.Configuration
+	var cf webrtc.Configuration
 
 	if ICEFilename != "" {
 		file, err := os.Open(ICEFilename)
@@ -83,36 +83,36 @@ func updateICEConfiguration() *iceConf {
 		} else {
 			defer file.Close()
 			d := json.NewDecoder(file)
-			var servers []ICEServer
+			var servers []Server
 			err = d.Decode(&servers)
 			if err != nil {
 				log.Printf("Get ICE configuration: %v", err)
 			}
 			for _, s := range servers {
-				ss, err := getICEServer(s)
+				ss, err := getServer(s)
 				if err != nil {
 					log.Printf("parse ICE server: %v", err)
 					continue
 				}
-				conf.ICEServers = append(conf.ICEServers, ss)
+				cf.ICEServers = append(cf.ICEServers, ss)
 			}
 		}
 	}
 
 	if ICERelayOnly {
-		conf.ICETransportPolicy = webrtc.ICETransportPolicyRelay
+		cf.ICETransportPolicy = webrtc.ICETransportPolicyRelay
 	}
 
-	iceConf := iceConf{
-		conf:      conf,
+	iceConf := configuration{
+		conf:      cf,
 		timestamp: now,
 	}
-	iceConfiguration.Store(&iceConf)
+	conf.Store(&iceConf)
 	return &iceConf
 }
 
 func ICEConfiguration() *webrtc.Configuration {
-	conf, ok := iceConfiguration.Load().(*iceConf)
+	conf, ok := conf.Load().(*configuration)
 	if !ok || time.Since(conf.timestamp) > 5*time.Minute {
 		conf = updateICEConfiguration()
 	} else if time.Since(conf.timestamp) > 2*time.Minute {
diff --git a/ice/ice_test.go b/ice/ice_test.go
new file mode 100644
index 0000000..71197af
--- /dev/null
+++ b/ice/ice_test.go
@@ -0,0 +1,65 @@
+package ice
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha1"
+	"encoding/base64"
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/pion/webrtc/v3"
+)
+
+func TestPassword(t *testing.T) {
+	s := Server{
+		URLs:       []string{"turn:turn.example.org"},
+		Username:   "jch",
+		Credential: "secret",
+	}
+
+	ss := webrtc.ICEServer{
+		URLs:       []string{"turn:turn.example.org"},
+		Username:   "jch",
+		Credential: "secret",
+	}
+
+	sss, err := getServer(s)
+
+	if err != nil || !reflect.DeepEqual(sss, ss) {
+		t.Errorf("Got %v, expected %v", sss, ss)
+	}
+}
+
+func TestHMAC(t *testing.T) {
+	s := Server{
+		URLs:           []string{"turn:turn.example.org"},
+		Username:       "jch",
+		Credential:     "secret",
+		CredentialType: "hmac-sha1",
+	}
+
+	ss := webrtc.ICEServer{
+		URLs: []string{"turn:turn.example.org"},
+	}
+
+	sss, err := getServer(s)
+
+	if !strings.HasSuffix(sss.Username, ":"+s.Username) {
+		t.Errorf("username is %v", ss.Username)
+	}
+	ss.Username = sss.Username
+
+	mac := hmac.New(sha1.New, []byte(s.Credential.(string)))
+	mac.Write([]byte(sss.Username))
+	buf := bytes.Buffer{}
+	e := base64.NewEncoder(base64.StdEncoding, &buf)
+	e.Write(mac.Sum(nil))
+	e.Close()
+	ss.Credential = string(buf.Bytes())
+
+	if err != nil || !reflect.DeepEqual(sss, ss) {
+		t.Errorf("Got %v, expected %v", sss, ss)
+	}
+}
diff --git a/rtpconn/rtpconn.go b/rtpconn/rtpconn.go
index 9628754..589ab08 100644
--- a/rtpconn/rtpconn.go
+++ b/rtpconn/rtpconn.go
@@ -16,6 +16,7 @@ import (
 	"github.com/jech/galene/conn"
 	"github.com/jech/galene/estimator"
 	"github.com/jech/galene/group"
+	"github.com/jech/galene/ice"
 	"github.com/jech/galene/jitter"
 	"github.com/jech/galene/packetcache"
 	"github.com/jech/galene/rtptime"
@@ -138,7 +139,7 @@ type rtpDownConnection struct {
 
 func newDownConn(c group.Client, id string, remote conn.Up) (*rtpDownConnection, error) {
 	api := group.APIFromCodecs(remote.Codecs())
-	pc, err := api.NewPeerConnection(*group.ICEConfiguration())
+	pc, err := api.NewPeerConnection(*ice.ICEConfiguration())
 	if err != nil {
 		return nil, err
 	}
@@ -457,7 +458,7 @@ func pushConn(up *rtpUpConnection, g *group.Group, cs []group.Client) {
 }
 
 func newUpConn(c group.Client, id string, labels map[string]string) (*rtpUpConnection, error) {
-	pc, err := c.Group().API().NewPeerConnection(*group.ICEConfiguration())
+	pc, err := c.Group().API().NewPeerConnection(*ice.ICEConfiguration())
 	if err != nil {
 		return nil, err
 	}
diff --git a/rtpconn/webclient.go b/rtpconn/webclient.go
index d789bcf..ba88491 100644
--- a/rtpconn/webclient.go
+++ b/rtpconn/webclient.go
@@ -16,6 +16,7 @@ import (
 	"github.com/jech/galene/diskwriter"
 	"github.com/jech/galene/estimator"
 	"github.com/jech/galene/group"
+	"github.com/jech/galene/ice"
 )
 
 func errorToWSCloseMessage(id string, err error) (*clientMessage, []byte) {
@@ -877,7 +878,7 @@ func clientLoop(c *webClient, ws *websocket.Conn) error {
 					Kind:             "change",
 					Group:            g.Name(),
 					Permissions:      &perms,
-					RTCConfiguration: group.ICEConfiguration(),
+					RTCConfiguration: ice.ICEConfiguration(),
 				})
 				if !c.permissions.Present {
 					up := getUpConns(c)
@@ -1084,7 +1085,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
 			Kind:             "join",
 			Group:            m.Group,
 			Permissions:      &perms,
-			RTCConfiguration: group.ICEConfiguration(),
+			RTCConfiguration: ice.ICEConfiguration(),
 		})
 		if err != nil {
 			return err
