repeater/internal/station/backend/oui_test.go
Pierre-Olivier Mercier 70140bc289 station: Identify devices by MAC OUI vendor lookup
Embed the IEEE OUI registry (~1MB pre-processed text file) and resolve
the vendor for every station MAC. Locally administered MACs (U/L bit
set, used by iOS/Android private addresses and virtual interfaces) are
skipped so we don't return spurious matches against randomized prefixes.

The vendor name shows up in the device card as a secondary line, and
falls back to the title position when no DHCP hostname is available —
"Apple" with the IP and MAC is far more useful than "Sans nom".

The lookup table loads lazily (sync.Once) on the first call so the
~40k-entry parse only runs when the station discovery code is exercised.
2026-05-01 22:32:57 +08:00

57 lines
1.6 KiB
Go

package backend
import "testing"
func TestLookupVendor(t *testing.T) {
cases := []struct {
mac string
want string
exact bool // when true, want must match exactly; otherwise non-empty is enough
comment string
}{
{"00:1c:b3:11:22:33", "Apple", true, "Apple OUI"},
{"f4:0f:24:11:22:33", "Apple", true, "Apple OUI uppercase"},
{"B8:27:EB:AA:BB:CC", "", false, "Raspberry Pi Foundation"},
{"dc:a6:32:11:22:33", "", false, "Raspberry Pi Trading"},
{"02:11:22:33:44:55", "", true, "locally administered (U/L bit set)"},
{"aa:bb:cc:dd:ee:ff", "", true, "locally administered randomized"},
{"", "", true, "empty mac"},
{"not a mac", "", true, "garbage"},
}
for _, tc := range cases {
got := LookupVendor(tc.mac)
if tc.exact {
if got != tc.want {
t.Errorf("%s: LookupVendor(%q) = %q, want %q", tc.comment, tc.mac, got, tc.want)
}
continue
}
if got == "" {
t.Errorf("%s: LookupVendor(%q) returned empty, expected non-empty", tc.comment, tc.mac)
}
}
}
func TestNormalizeOUI(t *testing.T) {
cases := []struct {
in string
want string
}{
{"00:1c:b3:11:22:33", "001CB3"},
{"00-1c-b3-11-22-33", "001CB3"},
{"001c.b311.2233", "001CB3"},
{"001cb3112233", "001CB3"},
{"02:11:22:33:44:55", ""}, // U/L bit set
{"03:11:22:33:44:55", ""}, // U/L bit set
{"06:11:22:33:44:55", ""}, // U/L bit set
{"01:00:5e:00:00:00", "01005E"}, // bit 0 (multicast) is fine, only bit 1 matters
{"abcd", ""}, // too short
}
for _, tc := range cases {
if got := normalizeOUI(tc.in); got != tc.want {
t.Errorf("normalizeOUI(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}