repeater/internal/wifi/wpasupplicant/signals.go

236 lines
5.2 KiB
Go

package wpasupplicant
import (
"log"
"sync"
"github.com/godbus/dbus/v5"
"github.com/nemunaire/repeater/internal/wifi/backend"
)
// SignalMonitor monitors D-Bus signals from wpa_supplicant
type SignalMonitor struct {
conn *dbus.Conn
iface *WPAInterface
callbacks backend.EventCallbacks
// Signal channel
signalChan chan *dbus.Signal
// Control
stopChan chan struct{}
mu sync.RWMutex
running bool
// State tracking
lastState WPAState
}
// NewSignalMonitor creates a new signal monitor
func NewSignalMonitor(conn *dbus.Conn, iface *WPAInterface) *SignalMonitor {
return &SignalMonitor{
conn: conn,
iface: iface,
signalChan: make(chan *dbus.Signal, 100),
stopChan: make(chan struct{}),
}
}
// Start begins monitoring D-Bus signals
func (sm *SignalMonitor) Start(callbacks backend.EventCallbacks) error {
sm.mu.Lock()
if sm.running {
sm.mu.Unlock()
return nil
}
sm.running = true
sm.callbacks = callbacks
sm.mu.Unlock()
interfacePath := sm.iface.GetPath()
// Add signal match for PropertiesChanged on Interface
matchOptions := []dbus.MatchOption{
dbus.WithMatchObjectPath(interfacePath),
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
dbus.WithMatchMember("PropertiesChanged"),
}
if err := sm.conn.AddMatchSignal(matchOptions...); err != nil {
sm.mu.Lock()
sm.running = false
sm.mu.Unlock()
return err
}
// Add signal match for ScanDone
scanDoneOptions := []dbus.MatchOption{
dbus.WithMatchObjectPath(interfacePath),
dbus.WithMatchInterface(InterfaceInterface),
dbus.WithMatchMember("ScanDone"),
}
if err := sm.conn.AddMatchSignal(scanDoneOptions...); err != nil {
sm.mu.Lock()
sm.running = false
sm.mu.Unlock()
return err
}
// Register signal channel
sm.conn.Signal(sm.signalChan)
// Get initial state
state, err := sm.iface.GetState()
if err == nil {
sm.lastState = state
}
// Start monitoring goroutine
go sm.monitor()
log.Printf("D-Bus signal monitoring started for wpa_supplicant interface %s", interfacePath)
return nil
}
// Stop stops monitoring D-Bus signals
func (sm *SignalMonitor) Stop() {
sm.mu.Lock()
if !sm.running {
sm.mu.Unlock()
return
}
sm.running = false
sm.mu.Unlock()
// Signal stop
close(sm.stopChan)
// Remove signal channel
sm.conn.RemoveSignal(sm.signalChan)
log.Printf("D-Bus signal monitoring stopped for wpa_supplicant")
}
// monitor is the main signal processing loop
func (sm *SignalMonitor) monitor() {
for {
select {
case sig := <-sm.signalChan:
sm.handleSignal(sig)
case <-sm.stopChan:
return
}
}
}
// handleSignal processes a D-Bus signal
func (sm *SignalMonitor) handleSignal(sig *dbus.Signal) {
// Handle ScanDone signal
if sig.Name == InterfaceInterface+".ScanDone" {
sm.handleScanDone(sig)
return
}
// Handle PropertiesChanged signals
if sig.Name != "org.freedesktop.DBus.Properties.PropertiesChanged" {
return
}
// Verify signal is from Interface
if len(sig.Body) < 2 {
return
}
interfaceName, ok := sig.Body[0].(string)
if !ok || interfaceName != InterfaceInterface {
return
}
// Parse changed properties
changedProps, ok := sig.Body[1].(map[string]dbus.Variant)
if !ok {
return
}
// Check for State property change
if stateVariant, ok := changedProps["State"]; ok {
if state, ok := stateVariant.Value().(string); ok {
sm.handleStateChange(WPAState(state))
}
}
// Check for CurrentBSS property change (connection status)
if _, ok := changedProps["CurrentBSS"]; ok {
// BSS changed, trigger state update
sm.handleConnectionChange()
}
}
// handleStateChange processes a state change
func (sm *SignalMonitor) handleStateChange(state WPAState) {
sm.lastState = state
sm.mu.RLock()
callback := sm.callbacks.OnStateChange
sm.mu.RUnlock()
if callback == nil {
return
}
// Map wpa_supplicant state to backend state
backendState := mapWPAState(state)
// Get connected SSID if connected
ssid := ""
if backendState == backend.StateConnected {
if bssPath, err := sm.iface.GetCurrentBSS(); err == nil && bssPath != "/" {
bss := NewBSS(sm.conn, bssPath)
if ssidStr, err := bss.GetSSIDString(); err == nil {
ssid = ssidStr
}
}
}
callback(backendState, ssid)
}
// handleConnectionChange processes connection changes
func (sm *SignalMonitor) handleConnectionChange() {
// Get current state and trigger state change callback
state, err := sm.iface.GetState()
if err != nil {
return
}
sm.handleStateChange(state)
}
// handleScanDone processes scan completion
func (sm *SignalMonitor) handleScanDone(sig *dbus.Signal) {
sm.mu.RLock()
callback := sm.callbacks.OnScanComplete
sm.mu.RUnlock()
if callback != nil {
callback()
}
}
// mapWPAState maps wpa_supplicant states to backend-agnostic states
func mapWPAState(state WPAState) backend.ConnectionState {
switch state {
case StateCompleted:
return backend.StateConnected
case StateAuthenticating, StateAssociating, StateAssociated, State4WayHandshake, StateGroupHandshake:
return backend.StateConnecting
case StateDisconnected, StateInactive, StateInterfaceDisabled:
return backend.StateDisconnected
case StateScanning:
// Keep as disconnected if just scanning
return backend.StateDisconnected
default:
return backend.StateDisconnected
}
}