Files
mrambossekandClaude Opus 5 8118e213ae server: upstream trains, observed TTL/DSCP/ECN, rate limits, action ids
Types 0x03/0x04/0x05 land with a bounded columnar train buffer (head kept,
truncation declared) and grant-free multi-part reports - a report row is
smaller than the packet it answers, so $3.4 holds without a grant. The
read loop now collects TTL/TOS cmsgs on Linux, replacing the 0xFF stubs in
the observation block with what the kernel saw; downtrain gained a dscp
parameter, so DSCP survival is measurable in both directions.

Rate limiting ($2.5) exists now: per-credential AND per-source buckets,
429 on the control plane, silent drop on the data plane after the HMAC
gate and before the replay window. UDP ceilings default above the largest
legitimate run - a limit that clips a real measurement produces a
confidently wrong number.

Every granted packet carries its action_id at payload[8:16]; overlapping
actions were unattributable before. Canary DNS logs now honor the stated
24h privacy default. /admin/enroll-tokens answers the spec's JSON shape.
protocol_version 1.0.1 (additive).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 13:04:54 +02:00

106 lines
2.5 KiB
Go

// SPDX-FileCopyrightText: 2026 Echolot contributors
// SPDX-License-Identifier: GPL-3.0-or-later
package ratelimit
import (
"testing"
"time"
)
// clockAt pins the limiter to a fake clock so refill is a function of arithmetic, not sleeping.
func clockAt(l *Limiter) *time.Time {
t := time.Unix(1000, 0)
l.now = func() time.Time { return t }
return &t
}
func TestBurstThenRefusalThenRefill(t *testing.T) {
l := New(1, 3) // 1 token/s, burst 3
now := clockAt(l)
for i := 0; i < 3; i++ {
if ok, _ := l.Allow("k"); !ok {
t.Fatalf("token %d of the burst refused", i)
}
}
ok, wait := l.Allow("k")
if ok {
t.Fatal("fourth token inside the same instant should be refused")
}
if wait <= 0 || wait > time.Second {
t.Fatalf("retry-after = %v, want (0, 1s]", wait)
}
*now = now.Add(2 * time.Second) // refills 2 tokens
if ok, _ := l.Allow("k"); !ok {
t.Fatal("refused after refill")
}
if ok, _ := l.Allow("k"); !ok {
t.Fatal("second refilled token refused")
}
if ok, _ := l.Allow("k"); ok {
t.Fatal("third token allowed but only two seconds elapsed")
}
}
func TestRefusalConsumesNothing(t *testing.T) {
l := New(1, 1)
now := clockAt(l)
l.Allow("k")
// Hammering while empty must not push the refill out.
for i := 0; i < 10; i++ {
if ok, _ := l.Allow("k"); ok {
t.Fatal("allowed while empty")
}
}
*now = now.Add(time.Second)
if ok, _ := l.Allow("k"); !ok {
t.Fatal("the refused attempts ate the refill")
}
}
func TestKeysAreIndependent(t *testing.T) {
l := New(1, 1)
clockAt(l)
l.Allow("a")
if ok, _ := l.Allow("b"); !ok {
t.Fatal("draining key a refused key b")
}
}
func TestAllowNChargesBytes(t *testing.T) {
l := New(1000, 1000) // e.g. bytes/s
clockAt(l)
if ok, _ := l.AllowN("k", 900); !ok {
t.Fatal("900 of 1000 refused")
}
if ok, _ := l.AllowN("k", 200); ok {
t.Fatal("1100 of 1000 allowed")
}
if ok, _ := l.AllowN("k", 100); !ok {
t.Fatal("the refused 200 consumed the remaining 100")
}
}
func TestNilLimiterAllowsEverything(t *testing.T) {
var l *Limiter
if ok, wait := l.AllowN("k", 1e12); !ok || wait != 0 {
t.Fatal("nil limiter must be a no-op")
}
}
func TestSweepDropsIdleBucketsOnly(t *testing.T) {
l := New(1, 3)
now := clockAt(l)
l.Allow("idle")
*now = now.Add(2 * time.Minute)
l.Allow("busy") // triggers the sweep; "idle" refilled long ago
if _, held := l.buckets["idle"]; held {
t.Fatal("idle bucket survived the sweep")
}
if _, held := l.buckets["busy"]; !held {
t.Fatal("active bucket was swept")
}
}