diff --git a/.gitignore b/.gitignore
index 7d08791..adc6a75 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
entropy
+vendor
exec-entropy
**.sw*
**/**.sw*
*.sw*
**/*.sw*
+testdata
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..93fdaa2
--- /dev/null
+++ b/config.go
@@ -0,0 +1,17 @@
+package main
+
+import "local/args"
+
+func config() *args.ArgSet {
+ as := args.NewArgSet()
+
+ as.Append(args.INT, "p", "port to listen on", "58080")
+ as.Append(args.STRING, "d", "root dir to serve static", ".")
+ as.Append(args.STRING, "crt", "path to crt", "./cert.crt")
+ as.Append(args.STRING, "key", "path to key", "./cert.key")
+
+ if err := as.Parse(); err != nil {
+ panic(err)
+ }
+ return as
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..b52577d
--- /dev/null
+++ b/main.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "crypto/tls"
+ "fmt"
+ "log"
+ "net/http"
+)
+
+func main() {
+ as := config()
+ httpsServer := &http.Server{
+ Addr: fmt.Sprintf(":%d", as.GetInt("p")),
+ Handler: New(),
+ TLSConfig: &tls.Config{
+ MinVersion: tls.VersionTLS12,
+ CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
+ PreferServerCipherSuites: true,
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ },
+ },
+ TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
+ }
+ c := as.GetString("crt")
+ k := as.GetString("key")
+ log.Printf("listening on %q", httpsServer.Addr)
+ if err := httpsServer.ListenAndServeTLS(c, k); err != nil {
+ panic(err)
+ }
+}
diff --git a/public/index.html b/public/index.html
new file mode 100755
index 0000000..0f1299f
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/webrtc.js b/public/webrtc.js
new file mode 100755
index 0000000..97c860d
--- /dev/null
+++ b/public/webrtc.js
@@ -0,0 +1,103 @@
+var localVideo;
+var localStream;
+var remoteVideo;
+var peerConnection;
+var uuid;
+var serverConnection;
+
+var peerConnectionConfig = {
+ 'iceServers': [
+ {'urls': 'stun:stun.stunprotocol.org:3478'},
+ {'urls': 'stun:stun.l.google.com:19302'},
+ ]
+};
+
+function pageReady() {
+ uuid = createUUID();
+
+ localVideo = document.getElementById('localVideo');
+ remoteVideo = document.getElementById('remoteVideo');
+
+ serverConnection = new WebSocket('wss://' + window.location.hostname + '/abc');
+ serverConnection.onmessage = gotMessageFromServer;
+
+ var constraints = {
+ video: true,
+ audio: true,
+ };
+
+ if(navigator.mediaDevices.getUserMedia) {
+ navigator.mediaDevices.getUserMedia(constraints).then(getUserMediaSuccess).catch(errorHandler);
+ } else {
+ alert('Your browser does not support getUserMedia API');
+ }
+}
+
+function getUserMediaSuccess(stream) {
+ localStream = stream;
+ localVideo.srcObject = stream;
+}
+
+function start(isCaller) {
+ peerConnection = new RTCPeerConnection(peerConnectionConfig);
+ peerConnection.onicecandidate = gotIceCandidate;
+ peerConnection.ontrack = gotRemoteStream;
+ peerConnection.addStream(localStream);
+
+ if(isCaller) {
+ peerConnection.createOffer().then(createdDescription).catch(errorHandler);
+ }
+}
+
+function gotMessageFromServer(message) {
+ if(!peerConnection) start(false);
+
+ var signal = JSON.parse(message.data);
+
+ // Ignore messages from ourself
+ if(signal.uuid == uuid) return;
+
+ if(signal.sdp) {
+ peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
+ // Only create answers in response to offers
+ if(signal.sdp.type == 'offer') {
+ peerConnection.createAnswer().then(createdDescription).catch(errorHandler);
+ }
+ }).catch(errorHandler);
+ } else if(signal.ice) {
+ peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler);
+ }
+}
+
+function gotIceCandidate(event) {
+ if(event.candidate != null) {
+ serverConnection.send(JSON.stringify({'ice': event.candidate, 'uuid': uuid}));
+ }
+}
+
+function createdDescription(description) {
+ console.log('got description');
+
+ peerConnection.setLocalDescription(description).then(function() {
+ serverConnection.send(JSON.stringify({'sdp': peerConnection.localDescription, 'uuid': uuid}));
+ }).catch(errorHandler);
+}
+
+function gotRemoteStream(event) {
+ console.log('got remote stream');
+ remoteVideo.srcObject = event.streams[0];
+}
+
+function errorHandler(error) {
+ console.log(error);
+}
+
+// Taken from http://stackoverflow.com/a/105074/515584
+// Strictly speaking, it's not a real UUID, but it gets the job done here
+function createUUID() {
+ function s4() {
+ return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
+ }
+
+ return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
+}
diff --git a/server.go b/server.go
new file mode 100644
index 0000000..86eab94
--- /dev/null
+++ b/server.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "log"
+ "net/http"
+ "os"
+ "path"
+)
+
+type Server struct {
+ fs http.Handler
+ ws *WS
+}
+
+func New() *Server {
+ fs := http.FileServer(http.Dir(config().GetString("d")))
+ return &Server{
+ fs: fs,
+ ws: NewWS(),
+ }
+}
+
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ log.Println("serving", r.URL)
+ if _, err := os.Stat(path.Join(config().GetString("d"), r.URL.Path[1:])); os.IsNotExist(err) {
+ s.ws.ServeHTTP(w, r)
+ } else {
+ log.Printf("Serving static %q from %q", r.URL.Path, config().GetString("d"))
+ s.fs.ServeHTTP(w, r)
+ }
+}
diff --git a/ws.go b/ws.go
new file mode 100644
index 0000000..8d01927
--- /dev/null
+++ b/ws.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "io"
+ "log"
+ "net/http"
+
+ "github.com/gorilla/websocket"
+)
+
+type WS struct {
+ upgrader websocket.Upgrader
+}
+
+func NewWS() *WS {
+ return &WS{
+ upgrader: websocket.Upgrader{
+ ReadBufferSize: 10240,
+ WriteBufferSize: 10240,
+ CheckOrigin: func(_ *http.Request) bool { return true },
+ },
+ }
+}
+
+func (ws *WS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if err := ws.serveHTTP(w, r); err != nil {
+ log.Println(r.URL.Path, err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+
+func (ws *WS) serveHTTP(w http.ResponseWriter, r *http.Request) error {
+ conn, err := ws.upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ return err
+ }
+ for {
+ mt, r, err := conn.NextReader()
+ if err != nil {
+ return err
+ }
+ w, err := conn.NextWriter(mt)
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(w, r); err != nil { // todo impl broadcast to channel;; sync map to all channels, goes to a forking reader-writer pipe, all listeners to broadcast read from pipe
+ return err
+ }
+ if err := w.Close(); err != nil {
+ return err
+ }
+ }
+}