feat(03-01): extend TrafficClass to 14 classes with hash-bucketed unknowns
- Replace ClassUnknown with ClassUnknown1-4 (unknown-1 through unknown-4) - AllClasses() now returns 14 elements - Add hashBucket() function routing unrecognized traffic deterministically - Update synth/config.go: NumLayers=14, 4 dissonant unknown tone configs at 862-1047 Hz - Add TestAllClassesCount and TestHashBucketDistribution tests - Add TestClassFreqConfigsComplete and TestNumLayersMatchesAllClasses to config_test.go - Update TestNewBankHas14Layers, TestMixerNoClip for 14 classes - All classify and synth tests pass
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package classify_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gopacket/gopacket"
|
||||
@@ -220,8 +221,8 @@ func TestClassify(t *testing.T) {
|
||||
t.Run("TestClassifyUnknown", func(t *testing.T) {
|
||||
pkt := buildUnknownPacket(t)
|
||||
got := c.Classify(pkt)
|
||||
if got.Class != classify.ClassUnknown {
|
||||
t.Errorf("Unknown packet: got class %q, want %q", got.Class, classify.ClassUnknown)
|
||||
if !strings.HasPrefix(string(got.Class), "unknown-") {
|
||||
t.Errorf("Unknown packet: got class %q, want unknown-N bucket", got.Class)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -241,3 +242,58 @@ func TestClassify(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAllClassesCount(t *testing.T) {
|
||||
classes := classify.AllClasses()
|
||||
if len(classes) != 14 {
|
||||
t.Errorf("AllClasses() returned %d classes, want 14", len(classes))
|
||||
}
|
||||
// No plain "unknown" should exist
|
||||
for _, c := range classes {
|
||||
if c == "unknown" {
|
||||
t.Error("AllClasses() still contains plain \"unknown\" — should be removed per D-04")
|
||||
}
|
||||
}
|
||||
// All 4 buckets must be present
|
||||
buckets := map[classify.TrafficClass]bool{
|
||||
classify.ClassUnknown1: false, classify.ClassUnknown2: false,
|
||||
classify.ClassUnknown3: false, classify.ClassUnknown4: false,
|
||||
}
|
||||
for _, c := range classes {
|
||||
if _, ok := buckets[c]; ok {
|
||||
buckets[c] = true
|
||||
}
|
||||
}
|
||||
for bucket, found := range buckets {
|
||||
if !found {
|
||||
t.Errorf("AllClasses() missing bucket %q", bucket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashBucketDistribution(t *testing.T) {
|
||||
// hashBucket is unexported, test through Classify with a minimal rule set
|
||||
// that has no catch-all TCP/UDP rules, so unmatched ports reach hashBucket.
|
||||
minimalRules := []classify.Rule{
|
||||
{Protocol: "icmp", DstPort: 0, Class: classify.ClassICMP},
|
||||
{Protocol: "tcp", DstPort: 443, Class: classify.ClassHTTPS},
|
||||
}
|
||||
c := classify.NewClassifier(minimalRules)
|
||||
seen := make(map[classify.TrafficClass]bool)
|
||||
// Try a range of unmatched TCP ports to hit all 4 buckets
|
||||
for port := uint16(10000); port < 11000; port++ {
|
||||
pkt := buildTCPPacket(t, port)
|
||||
got := c.Classify(pkt)
|
||||
if strings.HasPrefix(string(got.Class), "unknown-") {
|
||||
seen[got.Class] = true
|
||||
}
|
||||
}
|
||||
for _, bucket := range []classify.TrafficClass{
|
||||
classify.ClassUnknown1, classify.ClassUnknown2,
|
||||
classify.ClassUnknown3, classify.ClassUnknown4,
|
||||
} {
|
||||
if !seen[bucket] {
|
||||
t.Errorf("hashBucket never produced %q across ports 10000-10999", bucket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user