mirror of
https://github.com/soypat/lneto.git
synced 2026-08-31 20:09:05 +00:00
123 lines
3.7 KiB
Go
123 lines
3.7 KiB
Go
package tcp
|
|
|
|
import (
|
|
"math/rand"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/soypat/lneto/ethernet"
|
|
)
|
|
|
|
// TestHandlerStreamIntegrityUnderReorder asserts byte identity of a reassembled
|
|
// stream whose segments arrive out of order: arrival order is randomised within
|
|
// each block of shuffleWindow segments, and nothing is lost or retransmitted, so
|
|
// several segments sit staged in the receive ring at once. Reordering may cost
|
|
// throughput; it may not change the bytes.
|
|
func TestHandlerStreamIntegrityUnderReorder(t *testing.T) {
|
|
const (
|
|
mtu = ethernet.MaxMTU
|
|
maxpackets = 8
|
|
segSize = 100
|
|
nsegs = 8 // per round; 800 bytes through a 1500-byte ring
|
|
rounds = 40 // enough for the ring to wrap many times
|
|
shuffleWindow = 4 // segments that may arrive in any order among themselves
|
|
)
|
|
rng := rand.New(rand.NewSource(3))
|
|
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
|
setupClientServer(t, rng, client, server)
|
|
var rawbuf [mtu]byte
|
|
establish(t, client, server, rawbuf[:])
|
|
|
|
var want, got []byte
|
|
rb := make([]byte, mtu)
|
|
letter := byte('A')
|
|
for round := range rounds {
|
|
// Capture this round's segments on the wire, one segment per write.
|
|
segs := make([][]byte, 0, nsegs)
|
|
for range nsegs {
|
|
payload := make([]byte, segSize)
|
|
for j := range payload {
|
|
payload[j] = letter
|
|
}
|
|
letter++
|
|
if letter > 'Z' {
|
|
letter = 'A'
|
|
}
|
|
if n, err := client.Write(payload); err != nil || n != segSize {
|
|
t.Fatalf("round %d: client write: %d %v", round, n, err)
|
|
}
|
|
clear(rawbuf[:])
|
|
n, err := client.Send(rawbuf[:])
|
|
if err != nil {
|
|
t.Fatalf("round %d: client send: %v", round, err)
|
|
}
|
|
segs = append(segs, append([]byte(nil), rawbuf[:n]...))
|
|
want = append(want, payload...)
|
|
}
|
|
|
|
order := make([]int, 0, nsegs)
|
|
for i := 0; i < nsegs; i += shuffleWindow {
|
|
block := make([]int, 0, shuffleWindow)
|
|
for j := i; j < min(i+shuffleWindow, nsegs); j++ {
|
|
block = append(block, j)
|
|
}
|
|
rng.Shuffle(len(block), func(a, b int) { block[a], block[b] = block[b], block[a] })
|
|
order = append(order, block...)
|
|
}
|
|
|
|
for _, idx := range order {
|
|
if err := server.Recv(append([]byte(nil), segs[idx]...)); err != nil {
|
|
t.Logf("round %d segment %d refused: %v", round, idx, err)
|
|
}
|
|
// Drain as an application would, keeping the ring from filling.
|
|
for {
|
|
n, err := server.Read(rb)
|
|
if n > 0 {
|
|
got = append(got, rb[:n]...)
|
|
}
|
|
if n == 0 || err != nil {
|
|
break
|
|
}
|
|
}
|
|
// Feed ACKs back so the sender's window keeps opening; without this
|
|
// the test stalls on flow control instead of exercising reassembly.
|
|
clear(rawbuf[:])
|
|
if n, err := server.Send(rawbuf[:]); err == nil && n > 0 {
|
|
client.Recv(rawbuf[:n])
|
|
}
|
|
}
|
|
|
|
if string(got) != string(want) {
|
|
// Report the first divergence; later rounds only add noise.
|
|
i := 0
|
|
for i < len(got) && i < len(want) && got[i] == want[i] {
|
|
i++
|
|
}
|
|
t.Errorf("stream diverges in round %d at byte %d of %d; arrival order %v",
|
|
round, i, len(want), order)
|
|
lo := max(0, i-200)
|
|
t.Errorf("got %s", summarizeRuns(got[lo:min(len(got), i+200)]))
|
|
t.Errorf("want %s", summarizeRuns(want[lo:min(len(want), i+200)]))
|
|
t.FailNow()
|
|
}
|
|
}
|
|
t.Logf("%d bytes intact across %d rounds of reordering (window %d)", len(got), rounds, shuffleWindow)
|
|
}
|
|
|
|
// summarizeRuns renders a byte stream as run-length pairs ("A*100 B*100") so a
|
|
// duplicated or missing segment is visible at a glance.
|
|
func summarizeRuns(b []byte) string {
|
|
out := make([]byte, 0, 64)
|
|
for i := 0; i < len(b); {
|
|
j := i
|
|
for j < len(b) && b[j] == b[i] {
|
|
j++
|
|
}
|
|
out = append(out, b[i], '*')
|
|
out = append(out, strconv.Itoa(j-i)...)
|
|
out = append(out, ' ')
|
|
i = j
|
|
}
|
|
return string(out)
|
|
}
|