package backend import ( _ "embed" "strings" "sync" ) // IEEE OUI registry as a compact text file: each line is 6 hex characters // (uppercase, no separators) followed immediately by the organization name. // Sourced from https://standards-oui.ieee.org/oui/oui.csv and pre-processed // to strip the address column and common corporate suffixes (Inc, Ltd, ...). // //go:embed oui_data.txt var ouiData string var ( ouiOnce sync.Once ouiTable map[string]string ) func loadOUITable() { ouiTable = make(map[string]string, 40000) for line := range strings.SplitSeq(ouiData, "\n") { if len(line) < 7 { continue } ouiTable[line[:6]] = line[6:] } } // LookupVendor returns the organization assigned to the OUI prefix of mac, // or an empty string when the prefix is unknown or mac is malformed. Locally // administered MACs (the U/L bit is set) are explicitly skipped — they are // randomized by the device, not assigned to a vendor, so any match would be // a false positive. func LookupVendor(mac string) string { ouiOnce.Do(loadOUITable) prefix := normalizeOUI(mac) if prefix == "" { return "" } return ouiTable[prefix] } // normalizeOUI extracts the 6-hex-char OUI from a MAC address (any of // "aa:bb:cc:...", "aa-bb-cc-...", "aabb.ccdd.eeff", "aabbcc...") and returns // it uppercased. Returns "" for randomized MACs (locally administered bit // set) or anything that doesn't parse. func normalizeOUI(mac string) string { var hex strings.Builder hex.Grow(12) for i := 0; i < len(mac) && hex.Len() < 6; i++ { c := mac[i] switch { case c >= '0' && c <= '9', c >= 'A' && c <= 'F': hex.WriteByte(c) case c >= 'a' && c <= 'f': hex.WriteByte(c - 32) } } if hex.Len() < 6 { return "" } out := hex.String() // Skip locally administered MACs. The U/L bit is bit 1 of the first // octet — when set, the MAC was generated by the device (private // address randomization on iOS/Android, virtual interfaces, ...), not // assigned by IEEE, so OUI lookup is meaningless. first, ok := hexByte(out[0], out[1]) if !ok || first&0x02 != 0 { return "" } return out } func hexByte(hi, lo byte) (byte, bool) { h, ok1 := hexNibble(hi) l, ok2 := hexNibble(lo) if !ok1 || !ok2 { return 0, false } return h<<4 | l, true } func hexNibble(c byte) (byte, bool) { switch { case c >= '0' && c <= '9': return c - '0', true case c >= 'A' && c <= 'F': return c - 'A' + 10, true case c >= 'a' && c <= 'f': return c - 'a' + 10, true } return 0, false }