236 lines
5.2 KiB
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
|
|
}
|
|
}
|