package tcp import ( "testing" ) func TestPopCount(t *testing.T) { tests := []struct { input byte want int }{ {0x00, 0}, {0x01, 1}, {0x02, 1}, {0x03, 2}, {0x07, 3}, {0x0f, 4}, {0xff, 8}, {0x55, 4}, // 01010101 {0xaa, 4}, // 10101010 } for _, tt := range tests { if got := popCount(tt.input); got != tt.want { t.Errorf("popCount(0x%02x) = %d, want %d", tt.input, got, tt.want) } } } func TestAveragePopCount(t *testing.T) { tests := []struct { name string input []byte want float32 }{ {"empty", []byte{}, 0}, {"all zeros", []byte{0x00, 0x00, 0x00}, 0}, {"all ones", []byte{0xff, 0xff}, 8.0}, {"mixed", []byte{0x00, 0xff}, 4.0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := averagePopCount(tt.input) if got != tt.want { t.Errorf("averagePopCount() = %v, want %v", got, tt.want) } }) } } func TestIsFirstSixPrintable(t *testing.T) { tests := []struct { name string input []byte want bool }{ {"too short", []byte("abc"), false}, {"all printable", []byte("abcdef"), true}, {"non-printable at pos 0", []byte{0x00, 'a', 'b', 'c', 'd', 'e'}, false}, {"non-printable at pos 5", []byte{'a', 'b', 'c', 'd', 'e', 0x1f}, false}, {"exactly 6 printable", []byte("123456"), true}, {"spaces", []byte(" "), true}, {"non-printable middle", []byte{'a', 'b', 0x01, 'd', 'e', 'f'}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := isFirstSixPrintable(tt.input) if got != tt.want { t.Errorf("isFirstSixPrintable(%q) = %v, want %v", tt.input, got, tt.want) } }) } } func TestPrintablePercentage(t *testing.T) { tests := []struct { name string input []byte want float32 }{ {"empty", []byte{}, 0}, {"all printable", []byte("hello"), 1.0}, {"none printable", []byte{0x00, 0x01, 0x02, 0x03}, 0}, {"half printable", []byte{'a', 0x00, 'b', 0x00}, 0.5}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := printablePercentage(tt.input) if got != tt.want { t.Errorf("printablePercentage() = %v, want %v", got, tt.want) } }) } } func TestContiguousPrintable(t *testing.T) { tests := []struct { name string input []byte want int }{ {"empty", []byte{}, 0}, {"all printable", []byte("hello world"), 11}, {"none printable", []byte{0x00, 0x01, 0x02}, 0}, {"start printable", []byte{'a', 'b', 'c', 0x00, 'd', 'e', 'f'}, 3}, {"end printable", []byte{0x00, 'a', 'b', 'c', 'd', 'e', 'f'}, 6}, {"middle printable", []byte{0x00, 'a', 'b', 'c', 0x00}, 3}, {"two segments", []byte{'a', 'b', 0x00, 'c', 'd', 'e'}, 3}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := contiguousPrintable(tt.input) if got != tt.want { t.Errorf("contiguousPrintable() = %d, want %d", got, tt.want) } }) } } func TestIsTLSorHTTP(t *testing.T) { tests := []struct { name string input []byte want bool }{ {"too short", []byte("AB"), false}, // TLS ClientHello: 0x16 0x03 0x01 {"tls 1.0", []byte{0x16, 0x03, 0x01}, true}, // TLS 0x17 is application data record {"tls app data", []byte{0x17, 0x03, 0x03}, true}, {"tls max content type", []byte{0x17, 0x03, 0x09}, true}, {"bad tls content type", []byte{0x15, 0x03, 0x01}, false}, {"bad tls version", []byte{0x16, 0x04, 0x01}, false}, {"bad tls length", []byte{0x16, 0x03, 0x0a}, false}, // HTTP methods {"GET", []byte("GET / HTTP/1.1..."), true}, {"HEAD", []byte("HEAD /index.html..."), true}, {"POST", []byte("POST /api..."), true}, {"PUT", []byte("PUT /data..."), true}, {"DELETE", []byte("DELETE /..."), true}, {"CONNECT", []byte("CONNECT proxy..."), true}, {"OPTIONS", []byte("OPTIONS *..."), true}, {"TRACE", []byte("TRACE /..."), true}, {"PATCH", []byte("PATCH /..."), true}, {"random data", []byte{0x00, 0x01, 0x02, 0x03}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := isTLSorHTTP(tt.input) if got != tt.want { t.Errorf("isTLSorHTTP(%v) = %v, want %v", tt.input, got, tt.want) } }) } } func TestIsPrintable(t *testing.T) { if !isPrintable('a') { t.Error("'a' should be printable") } if isPrintable(0x00) { t.Error("0x00 should not be printable") } if !isPrintable(0x20) { t.Error("0x20 (space) should be printable") } if !isPrintable(0x7e) { t.Error("0x7e (~) should be printable") } if isPrintable(0x7f) { t.Error("0x7f (DEL) should not be printable") } } func TestFETStream_Feed(t *testing.T) { s := newFETStream(nil) u, done := s.Feed(false, false, false, 0, []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")) if u == nil { t.Fatal("Feed returned nil update") } if !done { t.Error("FET should be done after first packet") } m := u.M if _, ok := m["ex1"]; !ok { t.Error("ex1 missing") } if _, ok := m["ex2"]; !ok { t.Error("ex2 missing") } if _, ok := m["ex3"]; !ok { t.Error("ex3 missing") } if _, ok := m["ex4"]; !ok { t.Error("ex4 missing") } if _, ok := m["ex5"]; !ok { t.Error("ex5 missing") } if yes, ok := m["yes"].(bool); ok && yes { t.Error("HTTP should be exempt (yes=false)") } if u.Type != 2 { t.Errorf("prop update type = %d, want PropUpdateReplace", u.Type) } } func TestFETStream_Feed_EncryptedLike(t *testing.T) { s := newFETStream(nil) data := make([]byte, 100) for i := range data { data[i] = byte(i % 256) } u, done := s.Feed(false, false, false, 0, data) if u == nil { t.Fatal("Feed returned nil update") } if !done { t.Error("should be done") } } func TestFETStream_Feed_Skip(t *testing.T) { s := newFETStream(nil) _, done := s.Feed(false, false, false, 5, []byte("data")) if !done { t.Error("skip != 0 should return done=true") } } func TestFETStream_Feed_Empty(t *testing.T) { s := newFETStream(nil) u, done := s.Feed(false, false, false, 0, []byte{}) if u != nil || done { t.Error("empty data should return nil, false") } } func TestFETAnalyzer_Name(t *testing.T) { a := &FETAnalyzer{} if a.Name() != "fet" { t.Errorf("Name() = %q, want fet", a.Name()) } } func TestFETStream_Close(t *testing.T) { s := newFETStream(nil) if u := s.Close(false); u != nil { t.Error("Close should return nil") } }