From d6b88543e91a15948f57784572c24e627b947b6c Mon Sep 17 00:00:00 2001 From: Cecylia Bocovich Date: Wed, 11 Mar 2020 14:50:38 -0400 Subject: [PATCH] Have broker inform proxies of updates This is targeted specifically at the standalone go proxies. Proxies report their version to the broker and the broker will include an additional field in their response to indicate whether the proxies should update. If there is a newer version available, a log message will br printed after every poll. --- broker/broker.go | 15 +++++--- broker/snowflake-broker_test.go | 6 +-- common/messages/proxy.go | 56 +++++++++++++++------------ common/messages/proxy_test.go | 68 +++++++++++++++++++++++++-------- proxy-go/proxy-go_test.go | 2 +- proxy-go/snowflake.go | 9 ++++- 6 files changed, 105 insertions(+), 51 deletions(-) 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)