mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
122 lines
3.5 KiB
Go
122 lines
3.5 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package controlapi
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"quad4/reticulum-go/pkg/identity"
|
|
)
|
|
|
|
// FuzzWSReadMessageExploratory feeds masked client frames into readMessage.
|
|
// Oversize lengths must error, and successful payloads must stay within
|
|
// wsMaxMessageBytes.
|
|
func FuzzWSReadMessageExploratory(f *testing.F) {
|
|
f.Add(maskedTextFrame([]byte(`{"type":"subscribe_announces"}`)))
|
|
f.Add(maskedTextFrame([]byte(`{}`)))
|
|
f.Add([]byte{0x81, 0x80})
|
|
f.Add([]byte{0x81, 0xfe, 0xff, 0xff})
|
|
f.Add([]byte{0x81, 0xff, 0, 0, 0, 0, 0, 0, 0, 1})
|
|
f.Add([]byte{0x89, 0x80, 0, 0, 0, 0})
|
|
f.Add([]byte{0x88, 0x80, 0, 0, 0, 0})
|
|
f.Add([]byte{0x01, 0x81, 0, 0, 0, 0, 'x'})
|
|
|
|
f.Fuzz(func(t *testing.T, raw []byte) {
|
|
if len(raw) > 1<<16 {
|
|
t.Skip()
|
|
}
|
|
conn := &wsConn{conn: discardConn{}, reader: bufio.NewReader(bytes.NewReader(raw))}
|
|
payload, err := conn.readMessage()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(payload) > wsMaxMessageBytes {
|
|
t.Fatalf("payload len=%d exceeds cap", len(payload))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWSOutboxDropWhenFull(t *testing.T) {
|
|
srv, _ := newTestServer(t)
|
|
ident, err := identity.NewIdentity()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sess := newSession("exploratory-outbox", ident)
|
|
c := newWSClient(srv, sess, &wsConn{conn: discardConn{}, reader: bufio.NewReader(bytes.NewReader(nil))})
|
|
|
|
for i := range wsClientOutboxSize {
|
|
c.send(map[string]any{"type": "fill", "i": i})
|
|
}
|
|
c.send(map[string]any{"type": "overflow"})
|
|
if len(c.outbox) != wsClientOutboxSize {
|
|
t.Fatalf("outbox len=%d want %d after drop", len(c.outbox), wsClientOutboxSize)
|
|
}
|
|
}
|
|
|
|
func TestWSWriteLoopDoneBeforeEnable(t *testing.T) {
|
|
srv, _ := newTestServer(t)
|
|
ident, err := identity.NewIdentity()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sess := newSession("exploratory-done", ident)
|
|
rc := newRecordConn()
|
|
c := newWSClient(srv, sess, &wsConn{conn: rc, reader: bufio.NewReader(rc)})
|
|
c.startWriter()
|
|
payload := []byte(`{"type":"should-not-flush"}`)
|
|
select {
|
|
case c.outbox <- payload:
|
|
default:
|
|
t.Fatal("outbox full")
|
|
}
|
|
c.close()
|
|
time.Sleep(20 * time.Millisecond)
|
|
got := rc.snapshot()
|
|
if bytes.Contains(got, payload) {
|
|
t.Fatalf("queued payload flushed after close before enableWrites: %q", got)
|
|
}
|
|
}
|
|
|
|
func maskedTextFrame(payload []byte) []byte {
|
|
mask := [4]byte{1, 2, 3, 4}
|
|
masked := make([]byte, len(payload))
|
|
for i, b := range payload {
|
|
masked[i] = b ^ mask[i%4]
|
|
}
|
|
var hdr []byte
|
|
switch {
|
|
case len(payload) < 126:
|
|
hdr = []byte{0x81, byte(0x80 | len(payload))}
|
|
default:
|
|
hdr = make([]byte, 4)
|
|
hdr[0] = 0x81
|
|
hdr[1] = 0x80 | 126
|
|
binary.BigEndian.PutUint16(hdr[2:], uint16(len(payload)))
|
|
}
|
|
out := append(hdr, mask[:]...)
|
|
return append(out, masked...)
|
|
}
|
|
|
|
type discardConn struct{}
|
|
|
|
func (discardConn) Read([]byte) (int, error) { return 0, io.EOF }
|
|
func (discardConn) Write(b []byte) (int, error) { return len(b), nil }
|
|
func (discardConn) Close() error { return nil }
|
|
func (discardConn) LocalAddr() net.Addr { return discardAddr("local") }
|
|
func (discardConn) RemoteAddr() net.Addr { return discardAddr("remote") }
|
|
func (discardConn) SetDeadline(time.Time) error { return nil }
|
|
func (discardConn) SetReadDeadline(time.Time) error { return nil }
|
|
func (discardConn) SetWriteDeadline(time.Time) error { return nil }
|
|
|
|
type discardAddr string
|
|
|
|
func (a discardAddr) Network() string { return "discard" }
|
|
func (a discardAddr) String() string { return string(a) }
|