package logger import ( "strings" "testing" ) func TestParseLogSaZaglavljem(t *testing.T) { raw := "#TERMINIA TERM-001 1C:DB:D4:A6:A4:60 0.13.4\n" + "[stampac] opet se javlja na 192.168.123.100 (ping)\n" + "[izvestaj] dan 20659: 14 karata, 2100 RSD\n" p := ParsePacket([]byte(raw), "192.168.0.20") if p.Kind != KindLog { t.Fatalf("vrsta = %v, ocekivano KindLog", p.Kind) } if !p.Identified { t.Error("paket sa zaglavljem mora biti identifikovan") } if p.Name != "TERM-001" || p.MAC != "1C:DB:D4:A6:A4:60" || p.FW != "0.13.4" { t.Errorf("identitet = %q / %q / %q", p.Name, p.MAC, p.FW) } if len(p.Lines) != 2 { t.Fatalf("linija = %d, ocekivano 2: %q", len(p.Lines), p.Lines) } if !strings.HasPrefix(p.Lines[0], "[stampac]") { t.Errorf("prva linija = %q", p.Lines[0]) } if got := DeviceKey(p.MAC, p.SourceIP); got != "1CDBD4A6A460" { t.Errorf("kljuc = %q", got) } } func TestParseZaglavljeSaRazmakomUNazivu(t *testing.T) { // Naziv uredjaja kuca korisnik i sme da ima razmake. p := ParsePacket([]byte("#TERMINIA Bazen Ulaz A 1c-db-d4-a6-a4-60 0.13.4\nzdravo\n"), "10.0.0.5") if p.Name != "Bazen Ulaz A" { t.Errorf("naziv = %q", p.Name) } if p.MAC != "1C:DB:D4:A6:A4:60" { t.Errorf("mac = %q", p.MAC) } if p.FW != "0.13.4" { t.Errorf("fw = %q", p.FW) } } func TestParseHeartbeat(t *testing.T) { raw := `{"app":"TERMINIA","name":"TERM-001","mac":"1C:DB:D4:A6:A4:60",` + `"ip":"192.168.0.20","fw":"0.13.4","serial":"1CDBD4A6A460","hb":1}` p := ParsePacket([]byte(raw), "192.168.0.20") if p.Kind != KindHeartbeat { t.Fatalf("vrsta = %v, ocekivano KindHeartbeat", p.Kind) } if !p.Identified || p.MAC != "1C:DB:D4:A6:A4:60" || p.IP != "192.168.0.20" || p.FW != "0.13.4" || p.Serial != "1CDBD4A6A460" || p.Name != "TERM-001" { t.Errorf("identitet = %+v", p) } if len(p.Lines) != 0 { t.Errorf("heartbeat ne sme imati linije: %q", p.Lines) } } func TestParseHeartbeatSaIdPoljem(t *testing.T) { // Stariji/drugaciji klijent salje serijski pod imenom "id". p := ParsePacket([]byte(`{"name":"T","mac":"AABBCCDDEEFF","id":"XYZ","hb":1}`), "10.0.0.1") if p.Kind != KindHeartbeat || p.Serial != "XYZ" { t.Errorf("rezultat = %+v", p) } } func TestParseBezZaglavlja(t *testing.T) { // FW stariji od 0.13.4: goli tekst, znamo samo izvornu adresu. p := ParsePacket([]byte("nesto se desilo\ndrugi red\n"), "192.168.0.77") if p.Kind != KindLog { t.Fatalf("vrsta = %v", p.Kind) } if p.Identified { t.Error("paket bez zaglavlja NE SME da vazi za identifikovan") } if p.MAC != "" { t.Errorf("mac = %q, ocekivano prazno", p.MAC) } key := DeviceKey(p.MAC, p.SourceIP) if key != "nepoznat-192_168_0_77" { t.Errorf("kljuc = %q", key) } } func TestParseSmeceNeObaraServis(t *testing.T) { slucajevi := [][]byte{ nil, {}, []byte("\n\n\n"), []byte("{"), // krnj JSON []byte(`{"app":"TERMINIA"`), // presecen paket []byte(`{"nesto":"tudje"}`), // JSON bez identiteta {0x00, 0x01, 0x02, 0xff, 0xfe}, // binarno smece []byte("#TERMINIA\n"), // zaglavlje bez ijednog polja []byte(strings.Repeat("A", MaxPacketBytes*3)), // predugacak paket } for i, c := range slucajevi { func() { defer func() { if r := recover(); r != nil { t.Fatalf("slucaj %d: panika: %v", i, r) } }() p := ParsePacket(c, "1.2.3.4") // Bilo koji ishod je prihvatljiv osim pada; ako je nesto proslo, // mora biti u granicama. if len(p.Lines) > MaxLinesPerPacket { t.Errorf("slucaj %d: previse linija %d", i, len(p.Lines)) } for _, ln := range p.Lines { if len([]rune(ln)) > MaxLineLen { t.Errorf("slucaj %d: linija duza od granice", i) } } }() } } func TestParseSeceIKontrolneZnake(t *testing.T) { dugacko := strings.Repeat("x", 1000) raw := "#TERMINIA " + strings.Repeat("N", 200) + " 1C:DB:D4:A6:A4:60 " + strings.Repeat("9", 100) + "\n" + "linija\x1b[31m sa ESC \x07 i \x00 nulom\n" + dugacko + "\n" p := ParsePacket([]byte(raw), "1.2.3.4") if len([]rune(p.Name)) > MaxNameLen { t.Errorf("naziv nije skracen: %d", len([]rune(p.Name))) } if len([]rune(p.FW)) > MaxFWLen { t.Errorf("fw nije skracen: %d", len([]rune(p.FW))) } if strings.ContainsAny(p.Lines[0], "\x1b\x07\x00") { t.Errorf("kontrolni znaci nisu uklonjeni: %q", p.Lines[0]) } if len([]rune(p.Lines[1])) > MaxLineLen { t.Errorf("linija nije skracena: %d", len([]rune(p.Lines[1]))) } } func TestParsePreviseLinija(t *testing.T) { var b strings.Builder b.WriteString("#TERMINIA T 1C:DB:D4:A6:A4:60 0.13.4\n") for i := 0; i < 500; i++ { b.WriteString("linija\n") } p := ParsePacket([]byte(b.String()), "1.2.3.4") if len(p.Lines) != MaxLinesPerPacket { t.Errorf("linija = %d, ocekivano %d", len(p.Lines), MaxLinesPerPacket) } } func TestNormalizeMAC(t *testing.T) { dobri := map[string]string{ "1C:DB:D4:A6:A4:60": "1C:DB:D4:A6:A4:60", "1c:db:d4:a6:a4:60": "1C:DB:D4:A6:A4:60", "1cdbd4a6a460": "1C:DB:D4:A6:A4:60", "1C-DB-D4-A6-A4-60": "1C:DB:D4:A6:A4:60", } for in, want := range dobri { got, ok := NormalizeMAC(in) if !ok || got != want { t.Errorf("NormalizeMAC(%q) = %q,%v", in, got, ok) } } losi := []string{"", "1C:DB:D4:A6:A4", "1C:DB:D4:A6:A4:6G", "../../etc/passwd", strings.Repeat("A", 200), "1C:DB:D4:A6:A4:60:70"} for _, in := range losi { if _, ok := NormalizeMAC(in); ok { t.Errorf("NormalizeMAC(%q) je prosao, a ne bi smeo", in) } } } func TestSanitizeSegmentNemaIzlaskaIzFoldera(t *testing.T) { slucajevi := map[string]string{ "../../etc/passwd": "etc_passwd", "/apsolutno/putanja": "apsolutno_putanja", "C:\\Windows\\win": "C__Windows_win", "": "nepoznat", "...": "nepoznat", "1CDBD4A6A460": "1CDBD4A6A460", } for in, want := range slucajevi { got := SanitizeSegment(in) if got != want { t.Errorf("SanitizeSegment(%q) = %q, ocekivano %q", in, got, want) } if strings.ContainsAny(got, `/\`) || got == ".." { t.Errorf("SanitizeSegment(%q) = %q — pusta izlazak iz foldera", in, got) } } if n := len(SanitizeSegment(strings.Repeat("a", 500))); n > MaxKeyLen { t.Errorf("kljuc nije ogranicen: %d", n) } } func TestDeviceKeyPoIPKadNemaMAC(t *testing.T) { if got := DeviceKey("", "10.20.30.40"); got != "nepoznat-10_20_30_40" { t.Errorf("kljuc = %q", got) } if got := DeviceKey("", ""); got != "nepoznat-bez-adrese" { t.Errorf("kljuc bez adrese = %q", got) } // Lazna izvorna adresa ne sme da napravi putanju. if got := DeviceKey("", "../../root"); strings.ContainsAny(got, `/\`) { t.Errorf("kljuc = %q", got) } }