package iwd import ( "fmt" "github.com/godbus/dbus/v5" "github.com/nemunaire/repeater/internal/wifi/backend" ) const ( AgentPath = "/com/github/nemunaire/repeater/agent" ) // IWDBackend implements the WiFiBackend interface for iwd (Intel Wireless Daemon) type IWDBackend struct { conn *dbus.Conn manager *Manager station *Station agent *Agent agentManager *AgentManager signalMonitor *SignalMonitor interfaceName string callbacks backend.EventCallbacks } // NewIWDBackend creates a new IWD backend instance func NewIWDBackend() *IWDBackend { return &IWDBackend{} } // Initialize initializes the iwd backend with the given interface name func (b *IWDBackend) Initialize(interfaceName string) error { b.interfaceName = interfaceName var err error // Connect to D-Bus b.conn, err = dbus.SystemBus() if err != nil { return fmt.Errorf("échec de connexion à D-Bus: %v", err) } // Find station for interface b.manager = NewManager(b.conn) b.station, err = b.manager.FindStation(interfaceName) if err != nil { return fmt.Errorf("impossible de trouver la station pour %s: %v", interfaceName, err) } // Create and register agent for credential callbacks b.agent = NewAgent(b.conn, dbus.ObjectPath(AgentPath)) if err := b.agent.Export(); err != nil { return fmt.Errorf("échec de l'export de l'agent: %v", err) } b.agentManager = NewAgentManager(b.conn) if err := b.agentManager.RegisterAgent(dbus.ObjectPath(AgentPath)); err != nil { b.agent.Unexport() return fmt.Errorf("échec de l'enregistrement de l'agent: %v", err) } return nil } // Close closes the D-Bus connection and unregisters the agent func (b *IWDBackend) Close() error { if b.agentManager != nil && b.agent != nil { b.agentManager.UnregisterAgent(dbus.ObjectPath(AgentPath)) b.agent.Unexport() } if b.conn != nil { b.conn.Close() } return nil } // ScanNetworks triggers a network scan func (b *IWDBackend) ScanNetworks() error { err := b.station.Scan() if err != nil { return fmt.Errorf("erreur lors du scan: %v", err) } return nil } // GetOrderedNetworks returns networks sorted by signal strength in backend-agnostic format func (b *IWDBackend) GetOrderedNetworks() ([]backend.BackendNetwork, error) { networkInfos, err := b.station.GetOrderedNetworks() if err != nil { return nil, fmt.Errorf("erreur lors de la récupération des réseaux: %v", err) } var networks []backend.BackendNetwork seenSSIDs := make(map[string]bool) for _, netInfo := range networkInfos { network := NewNetwork(b.conn, netInfo.Path) props, err := network.GetProperties() if err != nil { continue } if props.Name == "" || seenSSIDs[props.Name] { continue } seenSSIDs[props.Name] = true // Convert iwd network to backend-agnostic format backendNet := backend.BackendNetwork{ SSID: props.Name, SignalDBm: netInfo.Signal / 100, // iwd provides 100*dBm, convert to dBm SecurityType: props.Type, BSSID: generateSyntheticBSSID(props.Name), // iwd doesn't expose BSSID Frequency: 0, // iwd doesn't expose frequency in GetOrderedNetworks } networks = append(networks, backendNet) } return networks, nil } // IsScanning checks if a scan is currently in progress func (b *IWDBackend) IsScanning() (bool, error) { return b.station.IsScanning() } // Connect connects to a WiFi network func (b *IWDBackend) Connect(ssid, password string) error { // Store passphrase in agent for callback if password != "" { b.agent.SetPassphrase(ssid, password) } // Ensure passphrase is cleared after connection attempt defer func() { if password != "" { b.agent.ClearPassphrase(ssid) } }() // Get network object network, err := b.station.GetNetwork(ssid) if err != nil { return fmt.Errorf("réseau '%s' non trouvé: %v", ssid, err) } // Connect - iwd will call agent.RequestPassphrase() if needed if err := network.Connect(); err != nil { return fmt.Errorf("erreur lors de la connexion: %v", err) } return nil } // Disconnect disconnects from the current WiFi network func (b *IWDBackend) Disconnect() error { if err := b.station.Disconnect(); err != nil { return fmt.Errorf("erreur lors de la déconnexion: %v", err) } return nil } // GetConnectionState returns the current WiFi connection state func (b *IWDBackend) GetConnectionState() (backend.ConnectionState, error) { state, err := b.station.GetState() if err != nil { return backend.StateDisconnected, err } return mapIWDState(state), nil } // GetConnectedSSID returns the SSID of the currently connected network func (b *IWDBackend) GetConnectedSSID() string { network, err := b.station.GetConnectedNetwork() if err != nil { return "" } props, err := network.GetProperties() if err != nil { return "" } return props.Name } // StartEventMonitoring starts monitoring WiFi events func (b *IWDBackend) StartEventMonitoring(callbacks backend.EventCallbacks) error { b.callbacks = callbacks // Create signal monitor b.signalMonitor = NewSignalMonitor(b.conn, b.station) // Register callbacks - wrap to convert iwd types to backend types b.signalMonitor.OnStateChange(func(state StationState, ssid string) { if b.callbacks.OnStateChange != nil { b.callbacks.OnStateChange(mapIWDState(state), ssid) } }) b.signalMonitor.OnScanComplete(func() { if b.callbacks.OnScanComplete != nil { b.callbacks.OnScanComplete() } }) // Start monitoring return b.signalMonitor.Start() } // StopEventMonitoring stops monitoring WiFi events func (b *IWDBackend) StopEventMonitoring() { if b.signalMonitor != nil { b.signalMonitor.Stop() } } // mapIWDState maps iwd-specific states to backend-agnostic states func mapIWDState(state StationState) backend.ConnectionState { switch state { case StateConnected: return backend.StateConnected case StateConnecting: return backend.StateConnecting case StateDisconnecting: return backend.StateDisconnecting case StateDisconnected: return backend.StateDisconnected case StateRoaming: // Map roaming to connected since we're still connected during roaming return backend.StateConnected default: return backend.StateDisconnected } } // generateSyntheticBSSID generates a consistent fake BSSID from SSID // (iwd doesn't expose real BSSID) func generateSyntheticBSSID(ssid string) string { // Use a simple hash approach - consistent per SSID hash := 0 for _, c := range ssid { hash = ((hash << 5) - hash) + int(c) } // Generate 6 bytes for MAC address b1 := byte((hash >> 0) & 0xff) b2 := byte((hash >> 8) & 0xff) b3 := byte((hash >> 16) & 0xff) b4 := byte((hash >> 24) & 0xff) b5 := byte(len(ssid) & 0xff) b6 := byte((len(ssid) >> 8) & 0xff) return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", b1, b2, b3, b4, b5, b6) }