package client import ( "bytes" "testing" ) // The retained region is what a reattach replays from, so an off-by-one here is // not a dropped byte but a spliced stream: the peer resumes mid-packet and the // session dies in a way no round-trip test would attribute to this code. func TestUnackedTracksOffsets(t *testing.T) { var u unackedBuf u.append([]byte("hello")) u.append([]byte("world")) if got := u.length(); got != 10 { t.Fatalf("length = %d, want 10", got) } if got := u.end(); got != 10 { t.Fatalf("end = %d, want 10", got) } if got := u.from(0); !bytes.Equal(got, []byte("helloworld")) { t.Fatalf("from(0) = %q", got) } // A reattach replays from wherever the peer got to, which lands anywhere — // including the middle of a chunk boundary. if got := u.from(3); !bytes.Equal(got, []byte("loworld")) { t.Fatalf("from(3) = %q", got) } if got := u.from(10); len(got) != 0 { t.Fatalf("from(end) = %q, want empty", got) } } func TestUnackedAdvanceDropsCreditedBytes(t *testing.T) { var u unackedBuf u.append([]byte("abcdefghij")) u.advance(4) if got := u.length(); got != 6 { t.Fatalf("length after advance = %d, want 6", got) } if got := u.end(); got != 10 { t.Fatalf("end must not move when bytes are dropped: got %d, want 10", got) } if got := u.from(4); !bytes.Equal(got, []byte("efghij")) { t.Fatalf("from(4) = %q", got) } // Below the retained region: the peer named an offset we can no longer // satisfy, which must be reported rather than silently clamped — replaying // the wrong range is worse than refusing to replay. if got := u.from(3); got != nil { t.Fatalf("from(3) below base = %q, want nil", got) } if got := u.from(11); got != nil { t.Fatalf("from(11) past end = %q, want nil", got) } } // Interleaving appends and advances is the steady-state pattern: credit arrives // every half window while the sender keeps writing. The buffer must stay exact // across the compaction that eventually triggers. func TestUnackedSurvivesInterleavedAppendAndAdvance(t *testing.T) { var u unackedBuf var sent []byte var acked int64 for i := 0; i < 200; i++ { chunk := bytes.Repeat([]byte{byte(i)}, 97) sent = append(sent, chunk...) // emit's order: reclaim what has been credited so far, then retain the // new chunk. The base therefore trails the credit that arrived since. base := acked u.advance(base) u.append(chunk) if got, want := u.end(), int64(len(sent)); got != want { t.Fatalf("round %d: end = %d, want %d", i, got, want) } if got, want := u.length(), len(sent)-int(base); got != want { t.Fatalf("round %d: length = %d, want %d", i, got, want) } if got, want := u.from(base), sent[base:]; !bytes.Equal(got, want) { t.Fatalf("round %d: retained region diverges from what was sent", i) } // The peer can only ever credit bytes it has actually received. if i%3 == 0 { if acked += 61; acked > int64(len(sent)) { acked = int64(len(sent)) } } } } // Compaction reuses the backing array, so a stream that runs for hours must not // grow one: this is a full window per stream, on both sides. func TestUnackedReclaimsBackingArray(t *testing.T) { var u unackedBuf chunk := bytes.Repeat([]byte{7}, 4096) for i := 0; i < 500; i++ { u.advance(u.end()) // fully credited every round u.append(chunk) } if u.length() != len(chunk) { t.Fatalf("length = %d, want %d", u.length(), len(chunk)) } if cap(u.buf) > 8*len(chunk) { t.Fatalf("backing array grew to %d bytes for a %d-byte window", cap(u.buf), len(chunk)) } }