273 lines
7.7 KiB
Go
273 lines
7.7 KiB
Go
package wire
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
)
|
|
|
|
const (
|
|
RequestHeaderSize = 29
|
|
ResponseHeaderSize = 5
|
|
MaxPayload = 2 * 1024 * 1024
|
|
|
|
ModeProbe byte = 0
|
|
ModeOpen byte = 1
|
|
ModeUpload byte = 2
|
|
ModeDownload byte = 3
|
|
ModeClose byte = 4
|
|
|
|
StatusOK byte = 0
|
|
StatusError byte = 1
|
|
StatusData byte = 2
|
|
StatusWait byte = 3
|
|
StatusEOF byte = 4
|
|
|
|
ProbeUpload byte = 1
|
|
ProbeDownload byte = 2
|
|
ProbeKeepalive byte = 3
|
|
ProbeBatch byte = 4
|
|
ProbeIperfUpload byte = 5
|
|
ProbeIperfDownload byte = 6
|
|
)
|
|
|
|
var ProbeMagic = [4]byte{'D', 'T', 'P', '2'}
|
|
|
|
// ProbeBurstCount is deliberately fixed at one. Startup calibration measures
|
|
// the safe record size of a single DragonTCP lane, not aggregate throughput.
|
|
// Multiple outstanding calibration records can make a constrained carrier look
|
|
// artificially better or worse and can produce a false ceiling. Confirmation
|
|
// retries are performed sequentially on fresh connections by the client.
|
|
func ProbeBurstCount(chunk int) int {
|
|
return 1
|
|
}
|
|
|
|
type SessionID [16]byte
|
|
|
|
type Request struct {
|
|
Mode byte
|
|
Session SessionID
|
|
Seq uint64
|
|
Payload []byte
|
|
}
|
|
|
|
func MaskInPlace(data []byte, sid SessionID, mode byte, seq uint64, response bool) {
|
|
if len(data) == 0 {
|
|
return
|
|
}
|
|
|
|
var seed [30]byte
|
|
copy(seed[:16], sid[:])
|
|
seed[16] = mode
|
|
binary.BigEndian.PutUint64(seed[17:25], seq)
|
|
if response {
|
|
seed[25] = 1
|
|
}
|
|
|
|
var counter uint32
|
|
for off := 0; off < len(data); {
|
|
binary.BigEndian.PutUint32(seed[26:30], counter)
|
|
block := sha256.Sum256(seed[:])
|
|
n := len(data) - off
|
|
if n > len(block) {
|
|
n = len(block)
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
data[off+i] ^= block[i]
|
|
}
|
|
off += n
|
|
counter++
|
|
}
|
|
}
|
|
|
|
func WriteRequest(w io.Writer, mode byte, sid SessionID, seq uint64, plaintext []byte) error {
|
|
return WriteRequestProfile(w, mode, sid, seq, plaintext, 0)
|
|
}
|
|
|
|
// WriteRequestProfile writes a binary request whose first byte is XORed with
|
|
// headerMask. The remaining framing and payload encoding stay unchanged.
|
|
// Masks are selected once at client startup and then remain fixed.
|
|
func WriteRequestProfile(w io.Writer, mode byte, sid SessionID, seq uint64, plaintext []byte, headerMask byte) error {
|
|
return WriteRequestProfileEncoding(w, mode, sid, seq, plaintext, headerMask, false)
|
|
}
|
|
|
|
// WriteRequestProfileEncoding optionally leaves the payload clear. Clear mode
|
|
// is signalled by the connection cover preface, so legacy peers continue to use
|
|
// the SHA-256 compatibility mask unchanged.
|
|
func WriteRequestProfileEncoding(w io.Writer, mode byte, sid SessionID, seq uint64, plaintext []byte, headerMask byte, clear bool) error {
|
|
if len(plaintext) > MaxPayload {
|
|
return fmt.Errorf("request payload too large: %d", len(plaintext))
|
|
}
|
|
if clear {
|
|
var header [RequestHeaderSize]byte
|
|
header[0] = mode ^ headerMask
|
|
copy(header[1:17], sid[:])
|
|
binary.BigEndian.PutUint64(header[17:25], seq)
|
|
binary.BigEndian.PutUint32(header[25:29], uint32(len(plaintext)))
|
|
buffers := net.Buffers{header[:], plaintext}
|
|
_, err := buffers.WriteTo(w)
|
|
return err
|
|
}
|
|
|
|
packet := make([]byte, RequestHeaderSize+len(plaintext))
|
|
packet[0] = mode ^ headerMask
|
|
copy(packet[1:17], sid[:])
|
|
binary.BigEndian.PutUint64(packet[17:25], seq)
|
|
binary.BigEndian.PutUint32(packet[25:29], uint32(len(plaintext)))
|
|
copy(packet[29:], plaintext)
|
|
MaskInPlace(packet[29:], sid, mode, seq, false)
|
|
return writeAll(w, packet)
|
|
}
|
|
|
|
func ReadRequest(r io.Reader) (Request, error) {
|
|
return ReadRequestProfile(r, 0)
|
|
}
|
|
|
|
// ReadRequestProfile decodes a request written with WriteRequestProfile.
|
|
func ReadRequestProfile(r io.Reader, headerMask byte) (Request, error) {
|
|
return ReadRequestProfileEncoding(r, headerMask, false)
|
|
}
|
|
|
|
func ReadRequestProfileEncoding(r io.Reader, headerMask byte, clear bool) (Request, error) {
|
|
var req Request
|
|
var header [RequestHeaderSize]byte
|
|
if _, err := io.ReadFull(r, header[:]); err != nil {
|
|
return req, err
|
|
}
|
|
|
|
req.Mode = header[0] ^ headerMask
|
|
if req.Mode > ModeClose {
|
|
return req, errors.New("unknown request mode")
|
|
}
|
|
copy(req.Session[:], header[1:17])
|
|
req.Seq = binary.BigEndian.Uint64(header[17:25])
|
|
n := binary.BigEndian.Uint32(header[25:29])
|
|
if n > MaxPayload {
|
|
return req, errors.New("request payload too large")
|
|
}
|
|
|
|
if n > 0 {
|
|
req.Payload = make([]byte, int(n))
|
|
if _, err := io.ReadFull(r, req.Payload); err != nil {
|
|
return req, err
|
|
}
|
|
if !clear {
|
|
MaskInPlace(req.Payload, req.Session, req.Mode, req.Seq, false)
|
|
}
|
|
}
|
|
return req, nil
|
|
}
|
|
|
|
func WriteResponse(w io.Writer, status byte, body []byte) error {
|
|
return WriteResponseProfile(w, status, body, writerHeaderMask(w))
|
|
}
|
|
|
|
// WriteResponseProfile writes a response using the selected first-byte mask.
|
|
func WriteResponseProfile(w io.Writer, status byte, body []byte, headerMask byte) error {
|
|
if len(body) > MaxPayload {
|
|
return fmt.Errorf("response body too large: %d", len(body))
|
|
}
|
|
packet := make([]byte, ResponseHeaderSize+len(body))
|
|
packet[0] = status ^ headerMask
|
|
binary.BigEndian.PutUint32(packet[1:5], uint32(len(body)))
|
|
copy(packet[5:], body)
|
|
return writeAll(w, packet)
|
|
}
|
|
|
|
func WriteMaskedResponse(w io.Writer, status byte, body []byte, sid SessionID, mode byte, seq uint64) error {
|
|
return WriteMaskedResponseProfileEncoding(w, status, body, sid, mode, seq, writerHeaderMask(w), writerClearPayload(w))
|
|
}
|
|
|
|
// WriteMaskedResponseProfile combines the normal payload mask with the
|
|
// selected first-byte header mask.
|
|
func WriteMaskedResponseProfile(w io.Writer, status byte, body []byte, sid SessionID, mode byte, seq uint64, headerMask byte) error {
|
|
return WriteMaskedResponseProfileEncoding(w, status, body, sid, mode, seq, headerMask, false)
|
|
}
|
|
|
|
func WriteMaskedResponseProfileEncoding(w io.Writer, status byte, body []byte, sid SessionID, mode byte, seq uint64, headerMask byte, clear bool) error {
|
|
if len(body) > MaxPayload {
|
|
return fmt.Errorf("response body too large: %d", len(body))
|
|
}
|
|
if clear {
|
|
var header [ResponseHeaderSize]byte
|
|
header[0] = status ^ headerMask
|
|
binary.BigEndian.PutUint32(header[1:5], uint32(len(body)))
|
|
buffers := net.Buffers{header[:], body}
|
|
_, err := buffers.WriteTo(w)
|
|
return err
|
|
}
|
|
packet := make([]byte, ResponseHeaderSize+len(body))
|
|
packet[0] = status ^ headerMask
|
|
binary.BigEndian.PutUint32(packet[1:5], uint32(len(body)))
|
|
copy(packet[5:], body)
|
|
MaskInPlace(packet[5:], sid, mode, seq, true)
|
|
return writeAll(w, packet)
|
|
}
|
|
|
|
func ReadResponse(r io.Reader) (byte, []byte, error) {
|
|
return ReadResponseProfile(r, 0)
|
|
}
|
|
|
|
// ReadResponseProfile decodes a response written with a header profile.
|
|
func ReadResponseProfile(r io.Reader, headerMask byte) (byte, []byte, error) {
|
|
var header [ResponseHeaderSize]byte
|
|
if _, err := io.ReadFull(r, header[:]); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
n := binary.BigEndian.Uint32(header[1:5])
|
|
if n > MaxPayload {
|
|
return 0, nil, errors.New("response body too large")
|
|
}
|
|
var body []byte
|
|
if n > 0 {
|
|
body = make([]byte, int(n))
|
|
if _, err := io.ReadFull(r, body); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
}
|
|
status := header[0] ^ headerMask
|
|
if status > StatusEOF {
|
|
return 0, nil, errors.New("unknown response status")
|
|
}
|
|
return status, body, nil
|
|
}
|
|
|
|
func DecodeMaskedResponse(status byte, body []byte, sid SessionID, mode byte, seq uint64) []byte {
|
|
if len(body) == 0 || status == StatusError {
|
|
return body
|
|
}
|
|
MaskInPlace(body, sid, mode, seq, true)
|
|
return body
|
|
}
|
|
|
|
func writeAll(w io.Writer, b []byte) error {
|
|
for len(b) > 0 {
|
|
n, err := w.Write(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n <= 0 {
|
|
return io.ErrShortWrite
|
|
}
|
|
b = b[n:]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writerHeaderMask(w io.Writer) byte {
|
|
if profiled, ok := w.(interface{ HeaderMask() byte }); ok {
|
|
return profiled.HeaderMask()
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func writerClearPayload(w io.Writer) bool {
|
|
if profiled, ok := w.(interface{ ClearPayload() bool }); ok {
|
|
return profiled.ClearPayload()
|
|
}
|
|
return false
|
|
}
|