commit c9013b2f80aeda0aa6e86d984f1d7e9b89aabd46
Author: Serene Han <[email protected]>
Date:   Thu Jan 21 13:02:46 2016 -0800

    answer successfully roundtripped back from snowflake proxy through broker 
to client (#1)
---
 broker/config.go           |   16 ------
 broker/snowflake-broker.go |  123 +++++++++++++++++++++++++++++++-------------
 proxy/broker.coffee        |   42 ++++++++++++---
 proxy/proxypair.coffee     |    5 +-
 proxy/snowflake.coffee     |   11 +---
 5 files changed, 127 insertions(+), 70 deletions(-)

diff --git a/broker/config.go b/broker/config.go
deleted file mode 100644
index 7e250aa..0000000
--- a/broker/config.go
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
-This is the server-side code that runs on Google App Engine for the
-"appspot" registration method.
-
-See doc/appspot-howto.txt for more details about setting up an
-application, and advice on running one.
-
-To upload a new version:
-$ torify ~/go_appengine/appcfg.py --no_cookies -A $YOUR_APP_ID update .
-*/
-package snowflake_broker
-
-// host:port/basepath of the broker you want to register with
-// for example, fp-broker.org or example.com:12345/broker
-// https:// and /reg/ will be prepended and appended respectively.
-const SNOWFLAKE_BROKER = ""
diff --git a/broker/snowflake-broker.go b/broker/snowflake-broker.go
index 1d26eb1..c2bde7b 100644
--- a/broker/snowflake-broker.go
+++ b/broker/snowflake-broker.go
@@ -1,3 +1,12 @@
+/*
+Broker acts as the HTTP signaling channel.
+It matches clients and snowflake proxies by passing corresponding
+SessionDescriptions in order to negotiate a WebRTC connection.
+
+TODO(serene): This code is currently the absolute minimum required to
+cause a successful negotiation.
+It's otherwise very unsafe and problematic, and needs quite some work...
+*/
 package snowflake_broker
 
 import (
@@ -8,8 +17,6 @@ import (
        "net"
        "net/http"
        "time"
-       // "appengine"
-       // "appengine/urlfetch"
 )
 
 // This is an intermediate step - a basic hardcoded appengine rendezvous
@@ -23,10 +30,11 @@ import (
 // var snowflakes []chan []byte
 
 type Snowflake struct {
-       id         string
-       sigChannel chan []byte
-       clients    int
-       index      int
+       id            string
+       offerChannel  chan []byte
+       answerChannel chan []byte
+       clients       int
+       index         int
 }
 
 // Implements heap.Interface, and holds Snowflakes.
@@ -63,14 +71,17 @@ func (sh *SnowflakeHeap) Pop() interface{} {
 }
 
 var snowflakes *SnowflakeHeap
+var snowflakeMap map[string]*Snowflake
 
 // Create and add a Snowflake to the heap.
 func AddSnowflake(id string) *Snowflake {
        snowflake := new(Snowflake)
        snowflake.id = id
        snowflake.clients = 0
-       snowflake.sigChannel = make(chan []byte)
+       snowflake.offerChannel = make(chan []byte)
+       snowflake.answerChannel = make(chan []byte)
        heap.Push(snowflakes, snowflake)
+       snowflakeMap[id] = snowflake
        return snowflake
 }
 
@@ -97,65 +108,108 @@ func clientHandler(w http.ResponseWriter, r 
*http.Request) {
        offer, err := ioutil.ReadAll(r.Body)
        if nil != err {
                log.Println("Invalid data.")
+               w.WriteHeader(http.StatusBadRequest)
                return
        }
        w.Header().Set("Access-Control-Allow-Origin", "*")
-       // Pop the most available snowflake proxy, and pass the offer to it.
-       // TODO: Make this much better.
+       w.Header().Set("Access-Control-Allow-Headers", "X-Session-ID")
+
+       // Find the most available snowflake proxy, and pass the offer to it.
+       // TODO: Needs improvement.
        snowflake := heap.Pop(snowflakes).(*Snowflake)
        if nil == snowflake {
-               // w.Header().Set("Status", http.StatusServiceUnavailable)
-               w.Write([]byte("no snowflake proxies available"))
+               w.Header().Set("Status", http.StatusServiceUnavailable)
+               // w.Write([]byte("no snowflake proxies available"))
                return
        }
-       // snowflakes = snowflakes[1:]
-       snowflake.sigChannel <- offer
-       w.Write([]byte("sent offer to proxy!"))
-       // TODO: Get browser snowflake to talkto this appengine instance
-       // so it can reply with an answer, and not just the offer again :)
-       // TODO: Real broker which matches clients and snowflake proxies.
-       w.Write(offer)
+       snowflake.offerChannel <- offer
+
+       // Wait for the answer to be returned on the channel.
+       select {
+       case answer := <-snowflake.answerChannel:
+               log.Println("Retrieving answer")
+               w.Write(answer)
+               // Only remove from the snowflake map once the answer is set.
+               delete(snowflakeMap, snowflake.id)
+
+       case <-time.After(time.Second * 10):
+               w.WriteHeader(http.StatusGatewayTimeout)
+               w.Write([]byte("timed out waiting for answer!"))
+       }
 }
 
 /*
-A snowflake browser proxy requests a client from the Broker.
+For snowflake proxies to request a client from the Broker.
 */
 func proxyHandler(w http.ResponseWriter, r *http.Request) {
+       w.Header().Set("Access-Control-Allow-Origin", "*")
+       w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Session-ID")
+       // For CORS preflight.
+       if "OPTIONS" == r.Method {
+               return
+       }
+
+       id := r.Header.Get("X-Session-ID")
        body, err := ioutil.ReadAll(r.Body)
        if nil != err {
                log.Println("Invalid data.")
+               w.WriteHeader(http.StatusBadRequest)
                return
        }
-       w.Header().Set("Access-Control-Allow-Origin", "*")
-       snowflakeSession := body
-       log.Println("Received snowflake: ", snowflakeSession)
-       snowflake := AddSnowflake(string(snowflakeSession))
+       if string(body) != id { // Mismatched IDs!
+               w.WriteHeader(http.StatusBadRequest)
+       }
+       // Maybe confirm that X-Session-ID is the same.
+       log.Println("Received snowflake: ", id)
+       snowflake := AddSnowflake(id)
+
+       // Wait for a client to avail an offer to the snowflake, or timeout
+       // and ask the snowflake to poll later.
        select {
-       case offer := <-snowflake.sigChannel:
+       case offer := <-snowflake.offerChannel:
                log.Println("Passing client offer to snowflake.")
                w.Write(offer)
+
        case <-time.After(time.Second * 10):
-               // s := fmt.Sprintf("%d snowflakes left.", snowflakes.Len())
-               // w.Write([]byte("timed out. " + s))
-               // w.Header().Set("Status", http.StatusRequestTimeout)
-               w.WriteHeader(http.StatusGatewayTimeout)
                heap.Remove(snowflakes, snowflake.index)
+               w.WriteHeader(http.StatusGatewayTimeout)
        }
 }
 
-func reflectHandler(w http.ResponseWriter, r *http.Request) {
+/*
+Expects snowflake proxes which have previously successfully received
+an offer from proxyHandler to respond with an answer in an HTTP POST,
+which the broker will pass back to the original client.
+*/
+func answerHandler(w http.ResponseWriter, r *http.Request) {
+       w.Header().Set("Access-Control-Allow-Origin", "*")
+       w.Header().Set("Access-Control-Allow-Headers", "X-Session-ID")
+       // For CORS preflight.
+       if "OPTIONS" == r.Method {
+               return
+       }
+
+       id := r.Header.Get("X-Session-ID")
+       snowflake, ok := snowflakeMap[id]
+       if !ok || nil == snowflake {
+               // The snowflake took too long to respond with an answer,
+               // and the designated client is no longer around / recognized 
by the Broker.
+               w.WriteHeader(http.StatusGone)
+               return
+       }
        body, err := ioutil.ReadAll(r.Body)
        if nil != err {
                log.Println("Invalid data.")
+               w.WriteHeader(http.StatusBadRequest)
                return
        }
-       w.Header().Set("Access-Control-Allow-Origin", "*")
-       w.Write(body)
+       log.Println("Received answer: ", body)
+       snowflake.answerChannel <- body
 }
 
 func init() {
-       // snowflakes = make([]chan []byte, 0)
        snowflakes = new(SnowflakeHeap)
+       snowflakeMap = make(map[string]*Snowflake)
        heap.Init(snowflakes)
 
        http.HandleFunc("/robots.txt", robotsTxtHandler)
@@ -163,8 +217,5 @@ func init() {
 
        http.HandleFunc("/client", clientHandler)
        http.HandleFunc("/proxy", proxyHandler)
-       http.HandleFunc("/reflect", reflectHandler)
-       // if SNOWFLAKE_BROKER == "" {
-       // panic("SNOWFLAKE_BROKER empty; did you forget to edit config.go?")
-       // }
+       http.HandleFunc("/answer", answerHandler)
 }
diff --git a/proxy/broker.coffee b/proxy/broker.coffee
index 688d45d..91a81d2 100644
--- a/proxy/broker.coffee
+++ b/proxy/broker.coffee
@@ -6,19 +6,25 @@ to get assigned to clients.
 ###
 
 STATUS_OK = 200
+STATUS_GONE = 410
 STATUS_GATEWAY_TIMEOUT = 504
 
+genSnowflakeID = ->
+  Math.random().toString(36).substring(2)
+
 # Represents a broker running remotely.
 class Broker
 
   clients: 0
+  id: null
 
   # When interacting with the Broker, snowflake must generate a unique session
   # ID so the Broker can keep track of which signalling channel it's speaking
   # to.
   constructor: (@url) ->
-    log 'Using Broker at ' + @url
-    clients = 0
+    @clients = 0
+    @id = genSnowflakeID()
+    log 'Contacting Broker at ' + @url + '\nSnowflake ID: ' + @id
 
   # Snowflake registers with the broker using an HTTP POST request, and expects
   # a response from the broker containing some client offer.
@@ -27,7 +33,8 @@ class Broker
     new Promise (fulfill, reject) =>
       xhr = new XMLHttpRequest()
       try
-        xhr.open 'POST', @url
+        xhr.open 'POST', @url + 'proxy'
+        xhr.setRequestHeader('X-Session-ID', @id)
       catch err
         ###
         An exception happens here when, for example, NoScript allows the domain
@@ -47,10 +54,29 @@ class Broker
           else
             log 'Broker ERROR: Unexpected ' + xhr.status +
                 ' - ' + xhr.statusText
-
-      xhr.send 'snowflake-testing'
-      log "Broker: polling for client offer..."
+      xhr.send @id
+      log @id + " - polling for client offer..."
 
   sendAnswer: (answer) ->
-    log 'Sending answer to broker.'
-    log answer
+    log @id + ' - Sending answer back to broker...\n'
+    log answer.sdp
+    xhr = new XMLHttpRequest()
+    try
+      xhr.open 'POST', @url + 'answer'
+      xhr.setRequestHeader('X-Session-ID', @id)
+    catch err
+      log 'Broker: exception while connecting: ' + err.message
+      return
+    xhr.onreadystatechange = ->
+      return if xhr.DONE != xhr.readyState
+      log xhr
+      switch xhr.status
+        when STATUS_OK
+          log 'Broker: Successfully replied with answer.'
+          log xhr.responseText
+        when STATUS_GONE
+          log 'Broker: No longer valid to reply with answer.'
+        else
+          log 'Broker ERROR: Unexpected ' + xhr.status +
+              ' - ' + xhr.statusText
+    xhr.send JSON.stringify(answer)
diff --git a/proxy/proxypair.coffee b/proxy/proxypair.coffee
index a27ac92..f78e9f6 100644
--- a/proxy/proxypair.coffee
+++ b/proxy/proxypair.coffee
@@ -32,7 +32,10 @@ class ProxyPair
         # TODO: Use a promise.all to tell Snowflake about all offers at once,
         # once multiple proxypairs are supported.
         log 'Finished gathering ICE candidates.'
-        Signalling.send @pc.localDescription
+        if COPY_PASTE_ENABLED
+          Signalling.send @pc.localDescription
+        else
+          snowflake.broker.sendAnswer @pc.localDescription
     # OnDataChannel triggered remotely from the client when connection 
succeeds.
     @pc.ondatachannel = (dc) =>
       console.log dc
diff --git a/proxy/snowflake.coffee b/proxy/snowflake.coffee
index 646b9b4..cc34f81 100644
--- a/proxy/snowflake.coffee
+++ b/proxy/snowflake.coffee
@@ -8,7 +8,7 @@ Assume that the webrtc client plugin is always the offerer, in 
which case
 this must always act as the answerer.
 ###
 DEFAULT_WEBSOCKET = '192.81.135.242:9901'
-DEFAULT_BROKER = 'https://snowflake-reg.appspot.com/proxy'
+DEFAULT_BROKER = 'https://snowflake-reg.appspot.com/'
 COPY_PASTE_ENABLED = false
 DEFAULT_PORTS =
   http:  80
@@ -104,8 +104,8 @@ class Snowflake
     poll = =>
       recv = broker.getClientOffer()
       recv.then((desc) =>
-        log 'Received:\n\n' + desc + '\n'
         offer = JSON.parse desc
+        log 'Received:\n\n' + offer.sdp + '\n'
         @receiveOffer offer
       , (err) ->
         log err
@@ -113,13 +113,6 @@ class Snowflake
       )
     poll()
 
-    # if @proxyPairs.length >= MAX_NUM_CLIENTS * CONNECTIONS_PER_CLIENT
-      # setTimeout(@proxyMain, @broker_poll_interval * 1000)
-      # return
-    # params = [['r', '1']]
-    # params.push ['transport', 'websocket']
-    # params.push ['transport', 'webrtc']
-
   # Receive an SDP offer from client plugin.
   receiveOffer: (desc) =>
     sdp = new RTCSessionDescription desc



_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to