mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
6ba06acdcb
* make backoff explicit API * fix tests to use explicit backoff * fix examples with explicit tcp
249 lines
5.3 KiB
Go
249 lines
5.3 KiB
Go
package udp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/soypat/lneto"
|
|
"github.com/soypat/lneto/internal"
|
|
)
|
|
|
|
// makeUDPFrame builds a minimal UDP frame with the given ports and payload.
|
|
func makeUDPFrame(src, dst uint16, payload []byte) []byte {
|
|
buf := make([]byte, 8+len(payload))
|
|
binary.BigEndian.PutUint16(buf[0:2], src)
|
|
binary.BigEndian.PutUint16(buf[2:4], dst)
|
|
binary.BigEndian.PutUint16(buf[4:6], uint16(8+len(payload)))
|
|
copy(buf[8:], payload)
|
|
return buf
|
|
}
|
|
|
|
func newTestConn(t *testing.T) *Conn {
|
|
t.Helper()
|
|
var conn Conn
|
|
err := conn.Configure(ConnConfig{
|
|
RxBuf: make([]byte, 256),
|
|
TxBuf: make([]byte, 256),
|
|
RxQueueSize: 4,
|
|
TxQueueSize: 4,
|
|
RWBackoff: backoffYield,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = conn.Open(1234, netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 0, 0, 1}), 8080))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return &conn
|
|
}
|
|
|
|
func TestConn_WriteEncapsulateRoundtrip(t *testing.T) {
|
|
conn := newTestConn(t)
|
|
payload := []byte("hello udp")
|
|
n, err := conn.Write(payload)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != len(payload) {
|
|
t.Fatalf("wrote %d, want %d", n, len(payload))
|
|
}
|
|
|
|
var buf [128]byte
|
|
n, err = conn.Encapsulate(buf[:], -1, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wantLen := 8 + len(payload) // UDP header + payload
|
|
if n != wantLen {
|
|
t.Fatalf("encapsulated %d, want %d", n, wantLen)
|
|
}
|
|
ufrm, err := NewFrame(buf[:n])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !internal.BytesEqual(ufrm.Payload(), payload) {
|
|
t.Fatalf("encapsulated payload mismatch")
|
|
}
|
|
|
|
// No more data pending.
|
|
n, err = conn.Encapsulate(buf[:], -1, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("expected no pending data, got %d", n)
|
|
}
|
|
}
|
|
|
|
func TestConn_DemuxReadRoundtrip(t *testing.T) {
|
|
conn := newTestConn(t)
|
|
payload := []byte("incoming datagram")
|
|
frame := makeUDPFrame(8080, 1234, payload) // remote:8080 -> local:1234
|
|
err := conn.Demux(frame, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var buf [64]byte
|
|
n, err := conn.Read(buf[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != len(payload) {
|
|
t.Fatalf("read %d, want %d", n, len(payload))
|
|
}
|
|
if !internal.BytesEqual(buf[:n], payload) {
|
|
t.Fatal("read data mismatch")
|
|
}
|
|
}
|
|
|
|
func TestConn_MultipleDatagrams(t *testing.T) {
|
|
conn := newTestConn(t)
|
|
messages := []string{"first", "second", "third"}
|
|
for _, msg := range messages {
|
|
frame := makeUDPFrame(8080, 1234, []byte(msg))
|
|
err := conn.Demux(frame, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Read back in order.
|
|
var buf [64]byte
|
|
for _, want := range messages {
|
|
n, err := conn.Read(buf[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := string(buf[:n])
|
|
if got != want {
|
|
t.Fatalf("got %q, want %q", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConn_ReadTruncates(t *testing.T) {
|
|
conn := newTestConn(t)
|
|
payload := []byte("a_longer_datagram")
|
|
frame := makeUDPFrame(8080, 1234, payload)
|
|
err := conn.Demux(frame, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Read into small buffer: truncates, discards remainder.
|
|
var buf [4]byte
|
|
n, err := conn.Read(buf[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != len(buf) {
|
|
t.Fatalf("read %d, want %d", n, len(buf))
|
|
}
|
|
if !internal.BytesEqual(buf[:], payload[:4]) {
|
|
t.Fatal("truncated data mismatch")
|
|
}
|
|
|
|
// Next Demux+Read should work cleanly after truncation.
|
|
payload2 := []byte("ok")
|
|
frame2 := makeUDPFrame(8080, 1234, payload2)
|
|
err = conn.Demux(frame2, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var buf2 [64]byte
|
|
n, err = conn.Read(buf2[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !internal.BytesEqual(buf2[:n], payload2) {
|
|
t.Fatal("post-truncation read mismatch")
|
|
}
|
|
}
|
|
|
|
func TestConn_DemuxExhausted(t *testing.T) {
|
|
conn := newTestConn(t) // queue size 4
|
|
for i := range 4 {
|
|
frame := makeUDPFrame(8080, 1234, []byte{byte(i)})
|
|
err := conn.Demux(frame, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// 5th should fail.
|
|
frame := makeUDPFrame(8080, 1234, []byte{0xff})
|
|
err := conn.Demux(frame, 0)
|
|
if err == nil {
|
|
t.Fatal("expected error on exhausted rx queue")
|
|
}
|
|
}
|
|
|
|
func TestConn_ClosedBehavior(t *testing.T) {
|
|
conn := newTestConn(t)
|
|
conn.Close()
|
|
|
|
_, err := conn.Write([]byte("data"))
|
|
if err == nil {
|
|
t.Fatal("expected error writing to closed conn")
|
|
}
|
|
|
|
err = conn.Demux([]byte("data"), 0)
|
|
if err == nil {
|
|
t.Fatal("expected error demuxing to closed conn")
|
|
}
|
|
}
|
|
|
|
func TestConn_EncapsulateMultiple(t *testing.T) {
|
|
conn := newTestConn(t)
|
|
msgs := []string{"aaa", "bbb"}
|
|
for _, msg := range msgs {
|
|
_, err := conn.Write([]byte(msg))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
var buf [128]byte
|
|
for _, want := range msgs {
|
|
n, err := conn.Encapsulate(buf[:], -1, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ufrm, err := NewFrame(buf[:n])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := string(ufrm.Payload())
|
|
if got != want {
|
|
t.Fatalf("got %q, want %q", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConn_FrameOffset(t *testing.T) {
|
|
conn := newTestConn(t)
|
|
// Demux with an offset simulating IP header before the UDP frame.
|
|
udpFrame := makeUDPFrame(8080, 1234, []byte("hi"))
|
|
carrier := make([]byte, 8+len(udpFrame)) // 8 bytes of "IP header" prefix
|
|
copy(carrier[8:], udpFrame)
|
|
err := conn.Demux(carrier, 8)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var buf [8]byte
|
|
n, err := conn.Read(buf[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(buf[:n]) != "hi" {
|
|
t.Fatalf("got %q, want %q", string(buf[:n]), "hi")
|
|
}
|
|
}
|
|
|
|
func backoffYield(backoffs uint) time.Duration {
|
|
return lneto.BackoffFlagGosched
|
|
}
|