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.
127 lines
2.6 KiB
Go
127 lines
2.6 KiB
Go
package station
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/nemunaire/repeater/internal/models"
|
|
"github.com/nemunaire/repeater/internal/station/backend"
|
|
)
|
|
|
|
var (
|
|
currentBackend backend.StationBackend
|
|
mu sync.RWMutex
|
|
)
|
|
|
|
// Initialize initializes the station discovery backend
|
|
func Initialize(backendName string, config backend.BackendConfig) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
// Close existing backend if any
|
|
if currentBackend != nil {
|
|
currentBackend.Close()
|
|
}
|
|
|
|
// Create new backend
|
|
b, err := createBackend(backendName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Initialize the backend
|
|
if err := b.Initialize(config); err != nil {
|
|
return err
|
|
}
|
|
|
|
currentBackend = b
|
|
return nil
|
|
}
|
|
|
|
// GetStations returns all connected stations as ConnectedDevice models
|
|
func GetStations() ([]models.ConnectedDevice, error) {
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
if currentBackend == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
stations, err := currentBackend.GetStations()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
devices := make([]models.ConnectedDevice, len(stations))
|
|
for i, s := range stations {
|
|
devices[i] = toConnectedDevice(s)
|
|
}
|
|
|
|
return devices, nil
|
|
}
|
|
|
|
// toConnectedDevice converts a backend Station to the API model. Centralised
|
|
// so handlers, the periodic refresh and the event callbacks all serialise the
|
|
// same shape.
|
|
func toConnectedDevice(s backend.Station) models.ConnectedDevice {
|
|
return models.ConnectedDevice{
|
|
Name: s.Hostname,
|
|
Type: s.Type,
|
|
MAC: s.MAC,
|
|
IP: s.IP,
|
|
Vendor: s.Vendor,
|
|
SignalDbm: s.Signal,
|
|
RxBytes: s.RxBytes,
|
|
TxBytes: s.TxBytes,
|
|
ConnectedAt: s.ConnectedAt,
|
|
}
|
|
}
|
|
|
|
// ToConnectedDevice exposes the conversion for callers in other packages.
|
|
func ToConnectedDevice(s backend.Station) models.ConnectedDevice {
|
|
return toConnectedDevice(s)
|
|
}
|
|
|
|
// StartEventMonitoring starts monitoring for station events
|
|
func StartEventMonitoring(callbacks backend.EventCallbacks) error {
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
if currentBackend == nil {
|
|
return nil
|
|
}
|
|
|
|
return currentBackend.StartEventMonitoring(callbacks)
|
|
}
|
|
|
|
// StopEventMonitoring stops monitoring for station events
|
|
func StopEventMonitoring() {
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
if currentBackend != nil {
|
|
currentBackend.StopEventMonitoring()
|
|
}
|
|
}
|
|
|
|
// Close closes the current backend
|
|
func Close() {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if currentBackend != nil {
|
|
currentBackend.Close()
|
|
currentBackend = nil
|
|
}
|
|
}
|
|
|
|
// SupportsRealTimeEvents returns true if the current backend supports real-time events
|
|
func SupportsRealTimeEvents() bool {
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
if currentBackend == nil {
|
|
return false
|
|
}
|
|
|
|
return currentBackend.SupportsRealTimeEvents()
|
|
}
|