Files
2026-08-16 19:02:48 -03:00

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)
}
})
}
}