- Flatten project structure: remove Models/, Services/, ViewModels/, Views/ subdirs - Replace APIService actor with simpler Api class, IS_DEV flag controls dev vs prod URL - Rewrite BeaconScanner to use CoreLocation (CLBeaconRegion ranging) instead of CoreBluetooth — iOS blocks iBeacon data from CBCentralManager - Add SVG logo on login page with proper scaling (was showing green square) - Make login page scrollable, add "enter 6-digit code" OTP instruction - Fix text input visibility (white on white) with .foregroundColor(.primary) - Add diagonal orange DEV ribbon banner (lower-left corner), gated on Api.IS_DEV - Update app icon: logo 10% larger, wifi icon closer - Add en.lproj/InfoPlist.strings for display name localization - Fix scan flash: keep isScanning=true until enrichment completes - Add Podfile with SVGKit, Kingfisher, CocoaLumberjack dependencies Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
79 lines
3.2 KiB
Swift
79 lines
3.2 KiB
Swift
import Foundation
|
|
|
|
enum BeaconBanList {
|
|
|
|
/// Known default UUID prefixes (first 8 hex chars of the 32-char UUID).
|
|
private static let BANNED_PREFIXES: [String: String] = [
|
|
// Apple AirLocate / Minew factory default
|
|
"E2C56DB5": "Apple AirLocate / Minew factory default",
|
|
// Kontakt.io default
|
|
"F7826DA6": "Kontakt.io factory default",
|
|
// Radius Networks default
|
|
"2F234454": "Radius Networks default",
|
|
// Estimote default
|
|
"B9407F30": "Estimote factory default",
|
|
// Generic Chinese bulk manufacturer defaults (also Feasycom)
|
|
"FDA50693": "Generic bulk / Feasycom factory default",
|
|
"74278BDA": "Generic bulk manufacturer default",
|
|
"8492E75F": "Generic bulk manufacturer default",
|
|
"A0B13730": "Generic bulk manufacturer default",
|
|
// JAALEE default
|
|
"EBEFD083": "JAALEE factory default",
|
|
// April Brother default
|
|
"B5B182C7": "April Brother factory default",
|
|
// BlueCharm / unconfigured
|
|
"00000000": "Unconfigured / zeroed UUID",
|
|
"FFFFFFFF": "Unconfigured / max UUID",
|
|
]
|
|
|
|
/// Full UUIDs that are known defaults (exact match on 32-char uppercase hex).
|
|
private static let BANNED_FULL_UUIDS: [String: String] = [
|
|
"E2C56DB5DFFB48D2B060D0F5A71096E0": "Apple AirLocate sample UUID",
|
|
"B9407F30F5F8466EAFF925556B57FE6D": "Estimote factory default",
|
|
"2F234454CF6D4A0FADF2F4911BA9FFA6": "Radius Networks default",
|
|
"FDA50693A4E24FB1AFCFC6EB07647825": "Generic Chinese bulk default",
|
|
"74278BDAB64445208F0C720EAF059935": "Generic bulk default",
|
|
"00000000000000000000000000000000": "Zeroed UUID \u{2014} unconfigured hardware",
|
|
]
|
|
|
|
/// Check if a UUID is on the ban list.
|
|
static func isBanned(_ uuid: String) -> Bool {
|
|
let normalized = uuid.replacingOccurrences(of: "-", with: "").uppercased()
|
|
|
|
// Check full UUID match
|
|
if BANNED_FULL_UUIDS[normalized] != nil { return true }
|
|
|
|
// Check prefix match (first 8 chars)
|
|
let prefix = String(normalized.prefix(8))
|
|
if BANNED_PREFIXES[prefix] != nil { return true }
|
|
|
|
return false
|
|
}
|
|
|
|
/// Get the reason a UUID is banned, or nil if not banned.
|
|
static func getBanReason(_ uuid: String) -> String? {
|
|
let normalized = uuid.replacingOccurrences(of: "-", with: "").uppercased()
|
|
|
|
// Check full UUID match first
|
|
if let reason = BANNED_FULL_UUIDS[normalized] { return reason }
|
|
|
|
// Check prefix
|
|
let prefix = String(normalized.prefix(8))
|
|
if let reason = BANNED_PREFIXES[prefix] { return reason }
|
|
|
|
return nil
|
|
}
|
|
|
|
/// Format a raw UUID string (32 hex chars) into standard UUID format with dashes.
|
|
static func formatUuid(_ uuid: String) -> String {
|
|
let hex = uuid.replacingOccurrences(of: "-", with: "").uppercased()
|
|
guard hex.count == 32 else { return uuid }
|
|
let s = hex
|
|
let i0 = s.startIndex
|
|
let i8 = s.index(i0, offsetBy: 8)
|
|
let i12 = s.index(i0, offsetBy: 12)
|
|
let i16 = s.index(i0, offsetBy: 16)
|
|
let i20 = s.index(i0, offsetBy: 20)
|
|
return "\(s[i0..<i8])-\(s[i8..<i12])-\(s[i12..<i16])-\(s[i16..<i20])-\(s[i20...])"
|
|
}
|
|
}
|