diff --git a/src/main.cpp b/src/main.cpp index 0f3a976..6ce4b03 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -58,28 +58,32 @@ // DETECTION PATTERNS // ============================================================================ -// Known Flock Safety MAC address prefixes (OUIs) -static const char* mac_prefixes[] = { +// MAC address prefixes (OUIs) + +// Flock Safety — high-confidence OUIs (direct registration or exclusive use) +static const char* flock_mac_prefixes[] = { // FS Ext Battery devices "58:8e:81", "cc:cc:cc", "ec:1b:bd", "90:35:ea", "04:0d:84", "f0:82:c0", "1c:34:f1", "38:5b:44", "94:34:69", "b4:e3:f9", - // Flock WiFi devices (Liteon Technology + USI) - // These OUIs belong to Liteon Technology and USI (Universal Scientific - // Industrial) — contract manufacturers that produce Flock Safety's - // WiFi-enabled cameras. Sourced from OUI-SPY firmware ecosystem table - // cross-referenced with IEEE OUI registry and field observations. + // Flock WiFi devices "70:c9:4e", "3c:91:80", "d8:f3:bc", "80:30:49", "14:5a:fc", "74:4c:a1", "08:3a:88", "9c:2f:9d", "94:08:53", "e4:aa:ea", - "f4:6a:dd", "f8:a2:d6", "e0:0a:f6", "00:f4:8d", "d0:39:57", - "e8:d0:fc", // Flock Safety (direct IEEE registration) - // b4:1e:52 is registered directly to "Flock Safety" in the IEEE OUI - // database — this is their own prefix, not a contract manufacturer. - "b4:1e:52", - // ShotSpotter / SoundThinking - // d4:11:d6 is registered to SoundThinking (formerly ShotSpotter) in - // the IEEE OUI database. Their acoustic gunshot detection sensors use - // BLE for local diagnostics and provisioning. + "b4:1e:52" +}; + +// Flock Safety contract manufacturers — lower confidence alone. +// These OUIs belong to Liteon Technology and USI (Universal Scientific +// Industrial), which produce Flock hardware but also ship unrelated +// consumer/enterprise devices. MAC match alone may be a false positive. +static const char* flock_mfr_mac_prefixes[] = { + "f4:6a:dd", "f8:a2:d6", "e0:0a:f6", "00:f4:8d", "d0:39:57", + "e8:d0:fc" +}; + +// SoundThinking (formerly ShotSpotter) — acoustic gunshot detection sensors. +// d4:11:d6 is registered to SoundThinking in the IEEE OUI database. +static const char* soundthinking_mac_prefixes[] = { "d4:11:d6" }; @@ -251,11 +255,23 @@ static void fyHeartbeat() { // DETECTION HELPERS // ============================================================================ -static bool checkMACPrefix(const uint8_t* mac) { - char mac_str[9]; - snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x", mac[0], mac[1], mac[2]); - for (size_t i = 0; i < sizeof(mac_prefixes)/sizeof(mac_prefixes[0]); i++) { - if (strncasecmp(mac_str, mac_prefixes[i], 8) == 0) return true; +static bool checkFlockMAC(const char* mac_str) { + for (size_t i = 0; i < sizeof(flock_mac_prefixes)/sizeof(flock_mac_prefixes[0]); i++) { + if (strncasecmp(mac_str, flock_mac_prefixes[i], 8) == 0) return true; + } + return false; +} + +static bool checkFlockMfrMAC(const char* mac_str) { + for (size_t i = 0; i < sizeof(flock_mfr_mac_prefixes)/sizeof(flock_mfr_mac_prefixes[0]); i++) { + if (strncasecmp(mac_str, flock_mfr_mac_prefixes[i], 8) == 0) return true; + } + return false; +} + +static bool checkSoundThinkingMAC(const char* mac_str) { + for (size_t i = 0; i < sizeof(soundthinking_mac_prefixes)/sizeof(soundthinking_mac_prefixes[0]); i++) { + if (strncasecmp(mac_str, soundthinking_mac_prefixes[i], 8) == 0) return true; } return false; } @@ -392,34 +408,45 @@ class FYBLECallbacks : public NimBLEAdvertisedDeviceCallbacks { NimBLEAddress addr = dev->getAddress(); std::string addrStr = addr.toString(); - // Safe MAC byte extraction - unsigned int m[6]; - sscanf(addrStr.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", - &m[0], &m[1], &m[2], &m[3], &m[4], &m[5]); - uint8_t mac[6] = {(uint8_t)m[0], (uint8_t)m[1], (uint8_t)m[2], - (uint8_t)m[3], (uint8_t)m[4], (uint8_t)m[5]}; + // Extract MAC prefix string for OUI checks + char macPrefix[9]; + snprintf(macPrefix, sizeof(macPrefix), "%.8s", addrStr.c_str()); int rssi = dev->getRSSI(); std::string name = dev->haveName() ? dev->getName() : ""; bool detected = false; + bool highConfidence = true; const char* method = ""; bool isRaven = false; const char* ravenFW = ""; - // 1. Check MAC prefix against known Flock Safety OUIs - if (checkMACPrefix(mac)) { + // 1. Check Flock Safety direct OUIs (high confidence) + if (checkFlockMAC(macPrefix)) { detected = true; method = "mac_prefix"; } - // 2. Check BLE device name patterns + // 2. Check SoundThinking/ShotSpotter OUIs (high confidence) + if (!detected && checkSoundThinkingMAC(macPrefix)) { + detected = true; + method = "mac_prefix_soundthinking"; + } + + // 3. Check Flock contract manufacturer OUIs (low confidence) + if (!detected && checkFlockMfrMAC(macPrefix)) { + detected = true; + method = "mac_prefix_mfr"; + highConfidence = false; + } + + // 4. Check BLE device name patterns if (!detected && !name.empty() && checkDeviceName(name.c_str())) { detected = true; method = "device_name"; } - // 3. Check BLE manufacturer company IDs (from wgreenberg/flock-you) + // 5. Check BLE manufacturer company IDs (from wgreenberg/flock-you) if (!detected) { for (int i = 0; i < (int)dev->getManufacturerDataCount(); i++) { std::string data = dev->getManufacturerData(i); @@ -435,7 +462,7 @@ class FYBLECallbacks : public NimBLEAdvertisedDeviceCallbacks { } } - // 4. Check Raven gunshot detector service UUIDs + // 6. Check Raven gunshot detector service UUIDs if (!detected) { char detUUID[41] = {0}; if (checkRavenUUID(dev, detUUID)) { @@ -475,11 +502,13 @@ class FYBLECallbacks : public NimBLEAdvertisedDeviceCallbacks { method, addrStr.c_str(), name.c_str(), rssi, gpsBuf); } - if (!fyTriggered) { + if (!fyTriggered && highConfidence) { fyTriggered = true; fyDetectBeep(); } - fyDeviceInRange = true; + if (highConfidence) { + fyDeviceInRange = true; + } fyLastDetTime = millis(); fyLastHB = millis(); } @@ -685,7 +714,9 @@ function card(d){return '