mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 20:11:19 -04:00
This continues to asserts the known version while decoding. The client will only ever generate the latest version while encoding and if the response needs to change, the impetus will be a new feature, set in the deserialized request, which can be used as a distinguisher.
127 lines
2.9 KiB
Go
127 lines
2.9 KiB
Go
//Package for communication with the snowflake broker
|
|
|
|
//import "git.torproject.org/pluggable-transports/snowflake.git/v2/common/messages"
|
|
package messages
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"git.torproject.org/pluggable-transports/snowflake.git/v2/common/nat"
|
|
)
|
|
|
|
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) EncodeClientPollRequest() ([]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) {
|
|
parts := bytes.SplitN(data, []byte("\n"), 2)
|
|
|
|
if len(parts) < 2 {
|
|
// no version number found
|
|
return nil, fmt.Errorf("unsupported message version")
|
|
}
|
|
|
|
var message ClientPollRequest
|
|
|
|
if string(parts[0]) != ClientVersion {
|
|
return nil, fmt.Errorf("unsupported message version")
|
|
}
|
|
|
|
err := json.Unmarshal(parts[1], &message)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if message.Offer == "" {
|
|
return nil, fmt.Errorf("no supplied offer")
|
|
}
|
|
|
|
switch message.NAT {
|
|
case "":
|
|
message.NAT = nat.NATUnknown
|
|
case nat.NATUnknown:
|
|
case nat.NATRestricted:
|
|
case nat.NATUnrestricted:
|
|
default:
|
|
return nil, fmt.Errorf("invalid NAT type")
|
|
}
|
|
|
|
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
|
|
}
|