test: improve coverage across package
This commit is contained in:
256
analyzer/tcp/fet_test.go
Normal file
256
analyzer/tcp/fet_test.go
Normal file
@@ -0,0 +1,256 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
159
analyzer/tcp/ssh_test.go
Normal file
159
analyzer/tcp/ssh_test.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.difuse.io/Difuse/Mellaris/analyzer"
|
||||
)
|
||||
|
||||
func TestSSHAnalyzer_Name(t *testing.T) {
|
||||
a := &SSHAnalyzer{}
|
||||
if a.Name() != "ssh" {
|
||||
t.Errorf("Name() = %q, want ssh", a.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_Client(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
u, done := s.Feed(false, false, false, 0, []byte("SSH-2.0-OpenSSH_8.9p1 Ubuntu-3\r\n"))
|
||||
if u == nil {
|
||||
t.Fatal("Feed returned nil update")
|
||||
}
|
||||
if done {
|
||||
t.Error("should not be done (server not yet received)")
|
||||
}
|
||||
client, ok := u.M["client"].(analyzer.PropMap)
|
||||
if !ok {
|
||||
t.Fatal("client prop missing")
|
||||
}
|
||||
if client["protocol"] != "2.0" {
|
||||
t.Errorf("protocol = %v, want 2.0", client["protocol"])
|
||||
}
|
||||
if client["software"] != "OpenSSH_8.9p1" {
|
||||
t.Errorf("software = %v, want OpenSSH_8.9p1", client["software"])
|
||||
}
|
||||
if comments, ok := client["comments"]; ok {
|
||||
if comments != "Ubuntu-3" {
|
||||
t.Errorf("comments = %v, want Ubuntu-3", comments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_ClientWithComments(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
u, done := s.Feed(false, false, false, 0, []byte("SSH-2.0-OpenSSH_7.4 Ubuntu-3\r\n"))
|
||||
if u == nil {
|
||||
t.Fatal("Feed returned nil update")
|
||||
}
|
||||
if done {
|
||||
t.Error("should not be done")
|
||||
}
|
||||
client := u.M["client"].(analyzer.PropMap)
|
||||
if client["comments"] != "Ubuntu-3" {
|
||||
t.Errorf("comments = %v, want Ubuntu-3", client["comments"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_Both(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
s.Feed(false, false, false, 0, []byte("SSH-2.0-OpenSSH_8.9\r\n"))
|
||||
u, done := s.Feed(true, false, false, 0, []byte("SSH-2.0-dropbear_2022.83\r\n"))
|
||||
if u == nil {
|
||||
t.Fatal("Feed returned nil update")
|
||||
}
|
||||
if !done {
|
||||
t.Error("should be done after both sides")
|
||||
}
|
||||
server, ok := u.M["server"].(analyzer.PropMap)
|
||||
if !ok {
|
||||
t.Fatal("server prop missing")
|
||||
}
|
||||
if server["software"] != "dropbear_2022.83" {
|
||||
t.Errorf("server software = %v", server["software"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_NotSSH(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
u, done := s.Feed(false, false, false, 0, []byte("HTTP/1.1 200 OK\r\n"))
|
||||
if u != nil {
|
||||
t.Error("should return nil for non-SSH")
|
||||
}
|
||||
if !done {
|
||||
t.Error("should be cancelled (done) for non-SSH")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_InvalidLine(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
u, done := s.Feed(false, false, false, 0, []byte("SSH-2.0-foo bar baz\r\n"))
|
||||
if u != nil {
|
||||
t.Error("should return nil for invalid line (>2 fields)")
|
||||
}
|
||||
if !done {
|
||||
t.Error("should be cancelled for invalid SSH line")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_NoLineEnd(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
u, done := s.Feed(false, false, false, 0, []byte("SSH-2.0-OpenSSH"))
|
||||
if u != nil {
|
||||
t.Error("should return nil when no EOL found yet")
|
||||
}
|
||||
if done {
|
||||
t.Error("should not be done, waiting for more data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_IncompleteThenComplete(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
u, _ := s.Feed(false, false, false, 0, []byte("SSH-2.0-"))
|
||||
if u != nil {
|
||||
t.Error("first partial feed should return nil")
|
||||
}
|
||||
u, done := s.Feed(false, false, false, 0, []byte("Dropbear\r\n"))
|
||||
if u == nil {
|
||||
t.Fatal("second feed should return update")
|
||||
}
|
||||
if done {
|
||||
t.Error("should not be done (only client)")
|
||||
}
|
||||
client := u.M["client"].(analyzer.PropMap)
|
||||
if client["software"] != "Dropbear" {
|
||||
t.Errorf("software = %v", client["software"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_Skip(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
_, done := s.Feed(false, false, false, 5, []byte("data"))
|
||||
if !done {
|
||||
t.Error("skip != 0 should return done=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Feed_Empty(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
u, done := s.Feed(false, false, false, 0, []byte{})
|
||||
if u != nil || done {
|
||||
t.Error("empty data should return nil, false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHStream_Close(t *testing.T) {
|
||||
s := newSSHStream(nil)
|
||||
s.clientBuf.Append([]byte("data"))
|
||||
s.serverBuf.Append([]byte("data"))
|
||||
s.clientMap = analyzer.PropMap{"key": "val"}
|
||||
u := s.Close(false)
|
||||
if u != nil {
|
||||
t.Error("Close should return nil")
|
||||
}
|
||||
if s.clientBuf.Len() != 0 || s.serverBuf.Len() != 0 {
|
||||
t.Error("Close should reset buffers")
|
||||
}
|
||||
if s.clientMap != nil || s.serverMap != nil {
|
||||
t.Error("Close should nil maps")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user