diff --git a/broker/broker.go b/broker/broker.go index 17c677e..29b85d0 100644 --- a/broker/broker.go +++ b/broker/broker.go @@ -28,9 +28,10 @@ import ( ) const ( - ClientTimeout = 10 - ProxyTimeout = 10 - readLimit = 100000 //Maximum number of bytes to be read from an HTTP request + LatestProxyVersion = "1.0" + ClientTimeout = 10 + ProxyTimeout = 10 + readLimit = 100000 //Maximum number of bytes to be read from an HTTP request ) type BrokerContext struct { @@ -170,12 +171,14 @@ func proxyPolls(ctx *BrokerContext, w http.ResponseWriter, r *http.Request) { return } - sid, proxyType, err := messages.DecodePollRequest(body) + sid, proxyType, proxyVersion, err := messages.DecodePollRequest(body) if err != nil { w.WriteHeader(http.StatusBadRequest) return } + update := (proxyVersion != LatestProxyVersion) + // Log geoip stats remoteIP, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { @@ -194,7 +197,7 @@ func proxyPolls(ctx *BrokerContext, w http.ResponseWriter, r *http.Request) { ctx.metrics.proxyIdleCount++ ctx.metrics.lock.Unlock() - b, err = messages.EncodePollResponse("", false) + b, err = messages.EncodePollResponse("", false, update) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -203,7 +206,7 @@ func proxyPolls(ctx *BrokerContext, w http.ResponseWriter, r *http.Request) { w.Write(b) return } - b, err = messages.EncodePollResponse(string(offer), true) + b, err = messages.EncodePollResponse(string(offer), true, update) if err != nil { w.WriteHeader(http.StatusInternalServerError) return diff --git a/broker/snowflake-broker_test.go b/broker/snowflake-broker_test.go index 18b83dd..d6f0c8e 100644 --- a/broker/snowflake-broker_test.go +++ b/broker/snowflake-broker_test.go @@ -128,7 +128,7 @@ func TestBroker(t *testing.T) { p.offerChannel <- []byte("fake offer") <-done So(w.Code, ShouldEqual, http.StatusOK) - So(w.Body.String(), ShouldEqual, `{"Status":"client match","Offer":"fake offer"}`) + So(w.Body.String(), ShouldEqual, `{"Status":"client match","Update":true,"Offer":"fake offer"}`) }) Convey("return empty 200 OK when no client offer is available.", func() { @@ -141,7 +141,7 @@ func TestBroker(t *testing.T) { // nil means timeout p.offerChannel <- nil <-done - So(w.Body.String(), ShouldEqual, `{"Status":"no match","Offer":""}`) + So(w.Body.String(), ShouldEqual, `{"Status":"no match","Update":true,"Offer":""}`) So(w.Code, ShouldEqual, http.StatusOK) }) }) @@ -279,7 +279,7 @@ func TestBroker(t *testing.T) { <-polled So(wP.Code, ShouldEqual, http.StatusOK) - So(wP.Body.String(), ShouldResemble, `{"Status":"client match","Offer":"fake offer"}`) + So(wP.Body.String(), ShouldResemble, `{"Status":"client match","Update":true,"Offer":"fake offer"}`) So(ctx.idToSnowflake["ymbcCMto7KHNGYlp"], ShouldNotBeNil) // Follow up with the answer request afterwards wA := httptest.NewRecorder() diff --git a/common/messages/proxy.go b/common/messages/proxy.go index 89dd43c..a26cd5e 100644 --- a/common/messages/proxy.go +++ b/common/messages/proxy.go @@ -9,14 +9,15 @@ import ( "strings" ) -const version = "1.1" +const version = "1.2" -/* Version 1.1 specification: +/* Version 1.2 specification: == ProxyPollRequest == { Sid: [generated session id of proxy], - Version: 1.1, + Version: 1.2, + ProxyVersion: Major.Minor, Type: ["badge"|"webext"|"standalone"] } @@ -29,13 +30,15 @@ HTTP 200 OK type: offer, sdp: [WebRTC SDP] } + Update: [true|false] } 2) If a client is not matched: HTTP 200 OK { - Status: "no match" + Status: "no match", + Update: [true|false] } 3) If the request is malformed: @@ -44,7 +47,7 @@ HTTP 400 BadRequest == ProxyAnswerRequest == { Sid: [generated session id of proxy], - Version: 1.1, + Version: 1.2, Answer: { type: answer, @@ -73,82 +76,87 @@ HTTP 400 BadRequest */ type ProxyPollRequest struct { - Sid string - Version string - Type string + Sid string + Version string + ProxyVersion string + Type string } -func EncodePollRequest(sid string, proxyType string) ([]byte, error) { +func EncodePollRequest(sid string, proxyType string, proxyVersion string) ([]byte, error) { return json.Marshal(ProxyPollRequest{ - Sid: sid, - Version: version, - Type: proxyType, + Sid: sid, + Version: version, + ProxyVersion: proxyVersion, + Type: proxyType, }) } // Decodes a poll message from a snowflake proxy and returns the // sid and proxy type of the proxy on success and an error if it failed -func DecodePollRequest(data []byte) (string, string, error) { +func DecodePollRequest(data []byte) (string, string, string, error) { var message ProxyPollRequest err := json.Unmarshal(data, &message) if err != nil { - return "", "", err + return "", "", "", err } majorVersion := strings.Split(message.Version, ".")[0] if majorVersion != "1" { - return "", "", fmt.Errorf("using unknown version") + return "", "", "", fmt.Errorf("using unknown version") } // Version 1.x requires an Sid if message.Sid == "" { - return "", "", fmt.Errorf("no supplied session id") + return "", "", "", fmt.Errorf("no supplied session id") } - return message.Sid, message.Type, nil + return message.Sid, message.Type, message.ProxyVersion, nil } type ProxyPollResponse struct { Status string + Update bool Offer string } -func EncodePollResponse(offer string, success bool) ([]byte, error) { +func EncodePollResponse(offer string, success bool, update bool) ([]byte, error) { if success { return json.Marshal(ProxyPollResponse{ Status: "client match", Offer: offer, + Update: update, }) } return json.Marshal(ProxyPollResponse{ Status: "no match", + Update: update, }) } // Decodes a poll response from the broker and returns an offer // If there is a client match, the returned offer string will be non-empty -func DecodePollResponse(data []byte) (string, error) { +func DecodePollResponse(data []byte) (string, bool, error) { var message ProxyPollResponse err := json.Unmarshal(data, &message) if err != nil { - return "", err + return "", false, err } if message.Status == "" { - return "", fmt.Errorf("received invalid data") + return "", false, fmt.Errorf("received invalid data") } if message.Status == "client match" { if message.Offer == "" { - return "", fmt.Errorf("no supplied offer") + return "", false, fmt.Errorf("no supplied offer") } } else { message.Offer = "" } - return message.Offer, nil + return message.Offer, message.Update, nil } type ProxyAnswerRequest struct { @@ -159,7 +167,7 @@ type ProxyAnswerRequest struct { func EncodeAnswerRequest(answer string, sid string) ([]byte, error) { return json.Marshal(ProxyAnswerRequest{ - Version: "1.1", + Version: "1.2", Sid: sid, Answer: answer, }) diff --git a/common/messages/proxy_test.go b/common/messages/proxy_test.go index 1570d4f..a7761dc 100644 --- a/common/messages/proxy_test.go +++ b/common/messages/proxy_test.go @@ -13,6 +13,7 @@ func TestDecodeProxyPollRequest(t *testing.T) { for _, test := range []struct { sid string proxyType string + version string data string err error }{ @@ -20,6 +21,7 @@ func TestDecodeProxyPollRequest(t *testing.T) { //Version 1.0 proxy message "ymbcCMto7KHNGYlp", "", + "", `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0"}`, nil, }, @@ -27,44 +29,59 @@ func TestDecodeProxyPollRequest(t *testing.T) { //Version 1.1 proxy message "ymbcCMto7KHNGYlp", "standalone", + "", `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.1","Type":"standalone"}`, nil, }, + { + //Version 1.2 proxy message + "ymbcCMto7KHNGYlp", + "standalone", + "1.0", + `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.1","Type":"standalone", "ProxyVersion":"1.0"}`, + nil, + }, { //Version 0.X proxy message: "", "", + "", "ymbcCMto7KHNGYlp", &json.SyntaxError{}, }, { + "", "", "", `{"Sid":"ymbcCMto7KHNGYlp"}`, fmt.Errorf(""), }, { + "", "", "", "{}", fmt.Errorf(""), }, { + "", "", "", `{"Version":"1.0"}`, fmt.Errorf(""), }, { + "", "", "", `{"Version":"2.0"}`, fmt.Errorf(""), }, } { - sid, proxyType, err := DecodePollRequest([]byte(test.data)) + sid, proxyType, version, err := DecodePollRequest([]byte(test.data)) So(sid, ShouldResemble, test.sid) So(proxyType, ShouldResemble, test.proxyType) + So(version, ShouldResemble, test.version) So(err, ShouldHaveSameTypeAs, test.err) } @@ -73,11 +90,12 @@ func TestDecodeProxyPollRequest(t *testing.T) { func TestEncodeProxyPollRequests(t *testing.T) { Convey("Context", t, func() { - b, err := EncodePollRequest("ymbcCMto7KHNGYlp", "standalone") + b, err := EncodePollRequest("ymbcCMto7KHNGYlp", "standalone", "1.0") So(err, ShouldEqual, nil) - sid, proxyType, err := DecodePollRequest(b) + sid, proxyType, version, err := DecodePollRequest(b) So(sid, ShouldEqual, "ymbcCMto7KHNGYlp") So(proxyType, ShouldEqual, "standalone") + So(version, ShouldEqual, "1.0") So(err, ShouldEqual, nil) }) } @@ -85,34 +103,52 @@ func TestEncodeProxyPollRequests(t *testing.T) { func TestDecodeProxyPollResponse(t *testing.T) { Convey("Context", t, func() { for _, test := range []struct { - offer string - data string - err error + offer string + update bool + data string + err error }{ { "fake offer", - `{"Status":"client match","Offer":"fake offer"}`, + false, + `{"Status":"client match","Offer":"fake offer","Update":false}`, nil, }, { "", + false, + `{"Status":"no match","Update":false}`, + nil, + }, + { + "", + true, + `{"Status":"no match","Update":true}`, + nil, + }, + { + "", + false, `{"Status":"no match"}`, nil, }, { "", - `{"Status":"client match"}`, + false, + `{"Status":"client match","Update":false}`, fmt.Errorf("no supplied offer"), }, { "", - `{"Test":"test"}`, + false, + `{"Test":"test","Update":false}`, fmt.Errorf(""), }, } { - offer, err := DecodePollResponse([]byte(test.data)) - So(offer, ShouldResemble, test.offer) + offer, update, err := DecodePollResponse([]byte(test.data)) So(err, ShouldHaveSameTypeAs, test.err) + So(offer, ShouldResemble, test.offer) + So(update, ShouldResemble, test.update) } }) @@ -120,16 +156,18 @@ func TestDecodeProxyPollResponse(t *testing.T) { func TestEncodeProxyPollResponse(t *testing.T) { Convey("Context", t, func() { - b, err := EncodePollResponse("fake offer", true) + b, err := EncodePollResponse("fake offer", true, false) So(err, ShouldEqual, nil) - offer, err := DecodePollResponse(b) + offer, update, err := DecodePollResponse(b) So(offer, ShouldEqual, "fake offer") + So(update, ShouldEqual, false) So(err, ShouldEqual, nil) - b, err = EncodePollResponse("", false) + b, err = EncodePollResponse("", false, true) So(err, ShouldEqual, nil) - offer, err = DecodePollResponse(b) + offer, update, err = DecodePollResponse(b) So(offer, ShouldEqual, "") + So(update, ShouldEqual, true) So(err, ShouldEqual, nil) }) } diff --git a/proxy-go/proxy-go_test.go b/proxy-go/proxy-go_test.go index 2429d1e..e0e32f9 100644 --- a/proxy-go/proxy-go_test.go +++ b/proxy-go/proxy-go_test.go @@ -247,7 +247,7 @@ func TestBrokerInteractions(t *testing.T) { Convey("polls broker correctly", func() { var err error - b, err := messages.EncodePollResponse(sampleOffer, true) + b, err := messages.EncodePollResponse(sampleOffer, true, true) So(err, ShouldEqual, nil) broker.transport = &MockTransport{ http.StatusOK, diff --git a/proxy-go/snowflake.go b/proxy-go/snowflake.go index 0b91059..607eec2 100644 --- a/proxy-go/snowflake.go +++ b/proxy-go/snowflake.go @@ -41,6 +41,7 @@ var broker *Broker var relayURL string const ( + version = "1.0" sessionIDLength = 16 ) @@ -173,7 +174,7 @@ func (b *Broker) pollOffer(sid string) *webrtc.SessionDescription { timeOfNextPoll = now } - body, err := messages.EncodePollRequest(sid, "standalone") + body, err := messages.EncodePollRequest(sid, "standalone", version) if err != nil { log.Printf("Error encoding poll message: %s", err.Error()) return nil @@ -192,7 +193,11 @@ func (b *Broker) pollOffer(sid string) *webrtc.SessionDescription { log.Printf("error reading broker response: %s", err) } else { - offer, err := messages.DecodePollResponse(body) + offer, update, err := messages.DecodePollResponse(body) + if update { + log.Printf(`There is a new version of the Go standalone snowflake + proxy available. Please update your install.`) + } if err != nil { log.Printf("error reading broker response: %s", err.Error()) log.Printf("body: %s", body)