302 lines
9.3 KiB
Go
302 lines
9.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"dragontcp/internal/cover"
|
|
"dragontcp/internal/wire"
|
|
)
|
|
|
|
func writeBHTTPTestRequest(w io.Writer, mode byte, sid wire.SessionID, seq uint64, payload []byte, downloadHint uint32) error {
|
|
n := uint32(len(payload))
|
|
if mode == bhttpModeDownload {
|
|
n = downloadHint
|
|
payload = nil
|
|
}
|
|
packet := make([]byte, bhttpRequestHeaderSize+len(payload))
|
|
packet[0] = mode
|
|
copy(packet[1:17], sid[:])
|
|
binary.BigEndian.PutUint64(packet[17:25], seq)
|
|
binary.BigEndian.PutUint32(packet[25:29], n)
|
|
copy(packet[29:], payload)
|
|
wire.MaskInPlace(packet[29:], sid, mode, seq, false)
|
|
_, err := w.Write(packet)
|
|
return err
|
|
}
|
|
|
|
func readBHTTPTestResponse(r io.Reader, sid wire.SessionID, mode byte, seq uint64) (byte, []byte, error) {
|
|
status, body, err := wire.ReadResponse(r)
|
|
if err == nil && status != wire.StatusError {
|
|
wire.MaskInPlace(body, sid, mode, seq, true)
|
|
}
|
|
return status, body, err
|
|
}
|
|
|
|
func startBHTTPTestServer(t *testing.T, sessions *bhttpSessionManager) (net.Conn, <-chan struct{}) {
|
|
t.Helper()
|
|
server, client := net.Pipe()
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
defer server.Close()
|
|
handleBinary(
|
|
server,
|
|
0,
|
|
false,
|
|
"",
|
|
false,
|
|
newDNSCache(time.Minute, 16),
|
|
0,
|
|
newStreamManager(time.Minute, nil),
|
|
sessions,
|
|
1024*1024,
|
|
1024*1024,
|
|
10*time.Millisecond,
|
|
nil,
|
|
)
|
|
}()
|
|
return client, done
|
|
}
|
|
|
|
func TestBHTTPReferenceSessionStack(t *testing.T) {
|
|
sessions := newBHTTPSessionManager(time.Minute, 32)
|
|
client, done := startBHTTPTestServer(t, sessions)
|
|
defer func() {
|
|
client.Close()
|
|
<-done
|
|
}()
|
|
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
|
|
|
|
var sid wire.SessionID
|
|
for i := range sid {
|
|
sid[i] = byte(i + 1)
|
|
}
|
|
|
|
if err := writeBHTTPTestRequest(client, bhttpModeUpload, sid, 0, nil, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status, _, err := readBHTTPTestResponse(client, sid, bhttpModeUpload, 0); err != nil || status != wire.StatusOK {
|
|
t.Fatalf("registration status=%d err=%v", status, err)
|
|
}
|
|
|
|
if err := writeBHTTPTestRequest(client, bhttpModeUpload, sid, 1, []byte("Hello BHTTP"), 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status, _, err := readBHTTPTestResponse(client, sid, bhttpModeUpload, 1); err != nil || status != wire.StatusOK {
|
|
t.Fatalf("upload status=%d err=%v", status, err)
|
|
}
|
|
|
|
// The size is in the header but no 1,350-byte body follows. This is the
|
|
// framing difference that made the native Dragon parser wait forever.
|
|
if err := writeBHTTPTestRequest(client, bhttpModeDownload, sid, 0, nil, 1350); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status, _, err := readBHTTPTestResponse(client, sid, bhttpModeDownload, 0); err != nil || status != wire.StatusOK {
|
|
t.Fatalf("download status=%d err=%v", status, err)
|
|
}
|
|
|
|
batch := make([]byte, 6)
|
|
binary.BigEndian.PutUint32(batch[:4], 1350)
|
|
binary.BigEndian.PutUint16(batch[4:], 2)
|
|
if err := writeBHTTPTestRequest(client, bhttpModeBatchDownload, sid, 0, batch, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
if status, _, err := readBHTTPTestResponse(client, sid, bhttpModeBatchDownload, 0); err != nil || status != wire.StatusOK {
|
|
t.Fatalf("batch response %d status=%d err=%v", i, status, err)
|
|
}
|
|
}
|
|
|
|
if err := writeBHTTPTestRequest(client, bhttpModeACK, sid, 5, nil, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status, _, err := readBHTTPTestResponse(client, sid, bhttpModeACK, 5); err != nil || status != wire.StatusOK {
|
|
t.Fatalf("ack status=%d err=%v", status, err)
|
|
}
|
|
}
|
|
|
|
func TestBPExplicitCloseRemovesSession(t *testing.T) {
|
|
sessions := newBHTTPSessionManager(time.Minute, 32)
|
|
client, done := startBHTTPTestServer(t, sessions)
|
|
defer func() {
|
|
client.Close()
|
|
<-done
|
|
}()
|
|
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
|
|
|
|
var sid wire.SessionID
|
|
copy(sid[:], []byte("close-session-01"))
|
|
if err := writeBHTTPTestRequest(client, bhttpModeUpload, sid, 0, nil, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status, _, err := readBHTTPTestResponse(client, sid, bhttpModeUpload, 0); err != nil || status != wire.StatusOK {
|
|
t.Fatalf("registration status=%d err=%v", status, err)
|
|
}
|
|
if sessions.get(sid) == nil {
|
|
t.Fatal("registered session is missing")
|
|
}
|
|
|
|
if err := writeBHTTPTestRequest(client, bhttpModeACK, sid, 0, bpCloseMagic[:], 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status, _, err := readBHTTPTestResponse(client, sid, bhttpModeACK, 0); err != nil || status != wire.StatusOK {
|
|
t.Fatalf("close status=%d err=%v", status, err)
|
|
}
|
|
if sessions.get(sid) != nil {
|
|
t.Fatal("explicit close retained the session")
|
|
}
|
|
}
|
|
|
|
func TestBHTTPReferenceProbeAndBatchEcho(t *testing.T) {
|
|
sessions := newBHTTPSessionManager(time.Minute, 32)
|
|
client, done := startBHTTPTestServer(t, sessions)
|
|
defer func() {
|
|
client.Close()
|
|
<-done
|
|
}()
|
|
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
|
|
|
|
var sid wire.SessionID
|
|
copy(sid[:], []byte("probe-session-01"))
|
|
payload := make([]byte, 10)
|
|
copy(payload[:4], []byte("BHP1"))
|
|
payload[4] = 1
|
|
payload[5] = bhttpModeDownload
|
|
binary.BigEndian.PutUint32(payload[6:], 512)
|
|
if err := writeBHTTPTestRequest(client, bhttpModeProbe, sid, 0, payload, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status, body, err := readBHTTPTestResponse(client, sid, bhttpModeProbe, 0)
|
|
if err != nil || status != wire.StatusOK || len(body) != 512 || !bytes.Equal(body[:10], payload) {
|
|
t.Fatalf("download probe status=%d len=%d err=%v", status, len(body), err)
|
|
}
|
|
for i := 10; i < len(body); i++ {
|
|
if body[i] != byte(i*31) {
|
|
t.Fatalf("probe pattern byte %d=%02x", i, body[i])
|
|
}
|
|
}
|
|
|
|
payload[5] = bhttpModeACK
|
|
binary.BigEndian.PutUint32(payload[6:], 3)
|
|
if err := writeBHTTPTestRequest(client, bhttpModeProbe, sid, 0, payload, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
status, body, err := readBHTTPTestResponse(client, sid, bhttpModeProbe, 0)
|
|
if err != nil || status != wire.StatusOK || !bytes.Equal(body, payload) {
|
|
t.Fatalf("batch probe %d status=%d body=%x err=%v", i, status, body, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBHTTPUnknownSessionDownloadHasNoBody(t *testing.T) {
|
|
sessions := newBHTTPSessionManager(time.Minute, 32)
|
|
client, done := startBHTTPTestServer(t, sessions)
|
|
defer func() {
|
|
client.Close()
|
|
<-done
|
|
}()
|
|
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
|
|
|
|
var sid wire.SessionID
|
|
copy(sid[:], []byte("unknown-session!"))
|
|
if err := writeBHTTPTestRequest(client, bhttpModeDownload, sid, 0, nil, 1350); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status, _, err := wire.ReadResponse(client)
|
|
if err != nil || status == wire.StatusOK || status == wire.StatusData {
|
|
t.Fatalf("unknown session status=%d err=%v", status, err)
|
|
}
|
|
}
|
|
|
|
func TestBinaryAutoDetectionKeepsNativeDragonProbe(t *testing.T) {
|
|
sessions := newBHTTPSessionManager(time.Minute, 32)
|
|
client, done := startBHTTPTestServer(t, sessions)
|
|
defer func() {
|
|
client.Close()
|
|
<-done
|
|
}()
|
|
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
|
|
|
|
var sid wire.SessionID
|
|
payload := make([]byte, 11)
|
|
copy(payload[:4], wire.ProbeMagic[:])
|
|
payload[4] = wire.ProbeKeepalive
|
|
if err := wire.WriteRequest(client, wire.ModeProbe, sid, 1, payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status, _, err := wire.ReadResponse(client)
|
|
if err != nil || status != wire.StatusOK {
|
|
t.Fatalf("native probe status=%d err=%v", status, err)
|
|
}
|
|
}
|
|
|
|
func TestClearCoveredBinaryAndBPProfiles(t *testing.T) {
|
|
for _, bp := range []bool{false, true} {
|
|
t.Run(map[bool]string{false: "B", true: "BP"}[bp], func(t *testing.T) {
|
|
server, client := net.Pipe()
|
|
profile := cover.Profile{Enabled: true, ID: 0x8173, Padding: 32, HeaderMask: 0x9b, Clear: true}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
defer server.Close()
|
|
profiled, isXOR, mask, err := sniffWire(server)
|
|
if err != nil || isXOR {
|
|
return
|
|
}
|
|
handleBinary(
|
|
profiled, mask, true, "", false,
|
|
newDNSCache(time.Minute, 16), 0,
|
|
newStreamManager(time.Minute, nil),
|
|
newBHTTPSessionManager(time.Minute, 32),
|
|
1024*1024, 1024*1024, 10*time.Millisecond, nil,
|
|
)
|
|
}()
|
|
defer func() {
|
|
client.Close()
|
|
<-done
|
|
}()
|
|
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
|
|
if err := cover.WritePreface(client, profile); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var sid wire.SessionID
|
|
copy(sid[:], []byte("clear-profile-01"))
|
|
if bp {
|
|
payload := makeBHTTPProbe(bhttpModeDownload, 256)[:10]
|
|
packet := make([]byte, bhttpRequestHeaderSize+len(payload))
|
|
packet[0] = bhttpModeProbe ^ profile.HeaderMask
|
|
copy(packet[1:17], sid[:])
|
|
binary.BigEndian.PutUint32(packet[25:29], uint32(len(payload)))
|
|
copy(packet[29:], payload)
|
|
if _, err := client.Write(packet); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status, body, err := wire.ReadResponseProfile(client, profile.HeaderMask)
|
|
if err != nil || status != wire.StatusOK || !bytes.Equal(body, makeBHTTPProbe(bhttpModeDownload, 256)) {
|
|
t.Fatalf("clear BP status=%d len=%d err=%v", status, len(body), err)
|
|
}
|
|
return
|
|
}
|
|
|
|
payload := make([]byte, 11)
|
|
copy(payload[:4], wire.ProbeMagic[:])
|
|
payload[4] = wire.ProbeDownload
|
|
binary.BigEndian.PutUint32(payload[7:11], 256)
|
|
if err := wire.WriteRequestProfileEncoding(client, wire.ModeProbe, sid, 7, payload, profile.HeaderMask, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status, body, err := wire.ReadResponseProfile(client, profile.HeaderMask)
|
|
if err != nil || status != wire.StatusData || !bytes.Equal(body, probePattern(256)) {
|
|
t.Fatalf("clear B status=%d len=%d err=%v", status, len(body), err)
|
|
}
|
|
})
|
|
}
|
|
}
|