mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 20:11:19 -04:00
Encode client-broker messages as json in HTTP body
Send the client poll request and response in a json-encoded format in the HTTP request body rather than sending the data in HTTP headers. This will pave the way for using domain-fronting alternatives for the Snowflake rendezvous.
This commit is contained in:
parent
ae7cc478fd
commit
270eb21803
7 changed files with 472 additions and 63 deletions
107
common/messages/client.go
Normal file
107
common/messages/client.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
//Package for communication with the snowflake broker
|
||||
|
||||
//import "git.torproject.org/pluggable-transports/snowflake.git/common/messages"
|
||||
package messages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const ClientVersion = "1.0"
|
||||
|
||||
/* Client--Broker protocol v1.x specification:
|
||||
|
||||
All messages contain the version number
|
||||
followed by a new line and then the message body
|
||||
<message> := <version>\n<body>
|
||||
<version> := <digit>.<digit>
|
||||
<body> := <poll request>|<poll response>
|
||||
|
||||
There are two different types of body messages,
|
||||
each encoded in JSON format
|
||||
|
||||
== ClientPollRequest ==
|
||||
<poll request> :=
|
||||
{
|
||||
offer: <sdp offer>
|
||||
[nat: (unknown|restricted|unrestricted)]
|
||||
}
|
||||
|
||||
The NAT field is optional, and if it is missing a
|
||||
value of "unknown" will be assumed.
|
||||
|
||||
== ClientPollResponse ==
|
||||
<poll response> :=
|
||||
{
|
||||
[answer: <sdp answer>]
|
||||
[error: <error string>]
|
||||
}
|
||||
|
||||
If the broker succeeded in matching the client with a proxy,
|
||||
the answer field MUST contain a valid SDP answer, and the
|
||||
error field MUST be empty. If the answer field is empty, the
|
||||
error field MUST contain a string explaining with a reason
|
||||
for the error.
|
||||
|
||||
*/
|
||||
|
||||
type ClientPollRequest struct {
|
||||
Offer string `json:"offer"`
|
||||
NAT string `json:"nat"`
|
||||
}
|
||||
|
||||
// Encodes a poll message from a snowflake client
|
||||
func (req *ClientPollRequest) EncodePollRequest() ([]byte, error) {
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]byte(ClientVersion+"\n"), body...), nil
|
||||
}
|
||||
|
||||
// Decodes a poll message from a snowflake client
|
||||
func DecodeClientPollRequest(data []byte) (*ClientPollRequest, error) {
|
||||
var message ClientPollRequest
|
||||
|
||||
err := json.Unmarshal(data, &message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if message.Offer == "" {
|
||||
return nil, fmt.Errorf("no supplied offer")
|
||||
}
|
||||
|
||||
if message.NAT == "" {
|
||||
message.NAT = "unknown"
|
||||
}
|
||||
|
||||
return &message, nil
|
||||
}
|
||||
|
||||
type ClientPollResponse struct {
|
||||
Answer string `json:"answer,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Encodes a poll response for a snowflake client
|
||||
func (resp *ClientPollResponse) EncodePollResponse() ([]byte, error) {
|
||||
return json.Marshal(resp)
|
||||
}
|
||||
|
||||
// Decodes a poll response for a snowflake client
|
||||
// If the Error field is empty, the Answer should be non-empty
|
||||
func DecodeClientPollResponse(data []byte) (*ClientPollResponse, error) {
|
||||
var message ClientPollResponse
|
||||
|
||||
err := json.Unmarshal(data, &message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if message.Error == "" && message.Answer == "" {
|
||||
return nil, fmt.Errorf("received empty broker response")
|
||||
}
|
||||
|
||||
return &message, nil
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package messages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
@ -252,3 +253,118 @@ func TestEncodeProxyAnswerResponse(t *testing.T) {
|
|||
So(err, ShouldEqual, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecodeClientPollRequest(t *testing.T) {
|
||||
Convey("Context", t, func() {
|
||||
for _, test := range []struct {
|
||||
natType string
|
||||
offer string
|
||||
data string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
//version 1.0 client message
|
||||
"unknown",
|
||||
"fake",
|
||||
`{"nat":"unknown","offer":"fake"}`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
//version 1.0 client message
|
||||
"unknown",
|
||||
"fake",
|
||||
`{"offer":"fake"}`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
//unknown version
|
||||
"",
|
||||
"",
|
||||
`{"version":"2.0"}`,
|
||||
fmt.Errorf(""),
|
||||
},
|
||||
{
|
||||
//no offer
|
||||
"",
|
||||
"",
|
||||
`{"nat":"unknown"}`,
|
||||
fmt.Errorf(""),
|
||||
},
|
||||
} {
|
||||
req, err := DecodeClientPollRequest([]byte(test.data))
|
||||
if test.err == nil {
|
||||
So(req.NAT, ShouldResemble, test.natType)
|
||||
So(req.Offer, ShouldResemble, test.offer)
|
||||
}
|
||||
So(err, ShouldHaveSameTypeAs, test.err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncodeClientPollRequests(t *testing.T) {
|
||||
Convey("Context", t, func() {
|
||||
req1 := &ClientPollRequest{
|
||||
NAT: "unknown",
|
||||
Offer: "fake",
|
||||
}
|
||||
b, err := req1.EncodePollRequest()
|
||||
So(err, ShouldEqual, nil)
|
||||
fmt.Println(string(b))
|
||||
parts := bytes.SplitN(b, []byte("\n"), 2)
|
||||
So(string(parts[0]), ShouldEqual, "1.0")
|
||||
b = parts[1]
|
||||
req2, err := DecodeClientPollRequest(b)
|
||||
So(err, ShouldEqual, nil)
|
||||
So(req2, ShouldResemble, req1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecodeClientPollResponse(t *testing.T) {
|
||||
Convey("Context", t, func() {
|
||||
for _, test := range []struct {
|
||||
answer string
|
||||
msg string
|
||||
data string
|
||||
}{
|
||||
{
|
||||
"fake answer",
|
||||
"",
|
||||
`{"answer":"fake answer"}`,
|
||||
},
|
||||
{
|
||||
"",
|
||||
"no snowflakes",
|
||||
`{"error":"no snowflakes"}`,
|
||||
},
|
||||
} {
|
||||
resp, err := DecodeClientPollResponse([]byte(test.data))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.Answer, ShouldResemble, test.answer)
|
||||
So(resp.Error, ShouldResemble, test.msg)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncodeClientPollResponse(t *testing.T) {
|
||||
Convey("Context", t, func() {
|
||||
resp1 := &ClientPollResponse{
|
||||
Answer: "fake answer",
|
||||
}
|
||||
b, err := resp1.EncodePollResponse()
|
||||
So(err, ShouldEqual, nil)
|
||||
resp2, err := DecodeClientPollResponse(b)
|
||||
So(err, ShouldEqual, nil)
|
||||
So(resp1, ShouldResemble, resp2)
|
||||
|
||||
resp1 = &ClientPollResponse{
|
||||
Error: "failed",
|
||||
}
|
||||
b, err = resp1.EncodePollResponse()
|
||||
So(err, ShouldEqual, nil)
|
||||
resp2, err = DecodeClientPollResponse(b)
|
||||
So(err, ShouldEqual, nil)
|
||||
So(resp1, ShouldResemble, resp2)
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue