Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 99 additions & 1 deletion subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

"errors"

"github.com/godbus/dbus"
"github.com/google/uuid"
errw "github.com/pkg/errors"
"go.uber.org/multierr"
Expand Down Expand Up @@ -78,7 +79,7 @@
return bwp.stopAdvertisingBLE()
}

// Update updates the list of networks that are advertised via bluetooth as available.

Check failure on line 82 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

ST1020: comment on exported method RefreshAvailableNetworks should be of the form "RefreshAvailableNetworks ..." (stylecheck)
func (bwp *BluetoothWiFiProvisioner) RefreshAvailableNetworks(ctx context.Context, awns *AvailableWiFiNetworks) error {
return bwp.writeAvailableNetworks(ctx, awns)
}
Expand Down Expand Up @@ -129,7 +130,7 @@

/** Unexported helper methods for low-level system calls and read/write requests to/from bluetooth characteristics **/

func (bwp *BluetoothWiFiProvisioner) startAdvertisingBLE(ctx context.Context) error {

Check failure on line 133 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

`(*BluetoothWiFiProvisioner).startAdvertisingBLE` - `ctx` is unused (unparam)
bwp.mu.Lock()
defer bwp.mu.Unlock()

Expand Down Expand Up @@ -165,9 +166,106 @@
return nil
}

func (bwp *BluetoothWiFiProvisioner) enableAutoAcceptPairRequest() {}
func (bwp *BluetoothWiFiProvisioner) enableAutoAcceptPairRequest() {
var err error
utils.ManagedGo(func() {
conn, err := dbus.SystemBus()
if err != nil {
err = errw.WithMessage(err, "failed to connect to system DBus")

Check failure on line 174 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

ineffectual assignment to err (ineffassign)
return
}

// Export agent methods
reply := conn.Export(nil, BluezAgentPath, BluezAgent)
if reply != nil {
err = errw.WithMessage(reply, "failed to export Bluez agent")

Check failure on line 181 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

ineffectual assignment to err (ineffassign)
return
}

// Register the agent
obj := conn.Object(BluezDBusService, "/org/bluez")
call := obj.Call("org.bluez.AgentManager1.RegisterAgent", 0, dbus.ObjectPath(BluezAgentPath), "NoInputNoOutput")
if err := call.Err; err != nil {
err = errw.WithMessage(err, "failed to register Bluez agent")

Check failure on line 189 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

ineffectual assignment to err (ineffassign)
return
}

// Set as the default agent
call = obj.Call("org.bluez.AgentManager1.RequestDefaultAgent", 0, dbus.ObjectPath(BluezAgentPath))
if err := call.Err; err != nil {
err = errw.WithMessage(err, "failed to set default Bluez agent")

Check failure on line 196 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

ineffectual assignment to err (ineffassign)
return
}

bwp.logger.Info("Bluez agent registered!")

// Listen for properties changed events
signalChan := make(chan *dbus.Signal, 10)
conn.Signal(signalChan)

// Add a match rule to listen for DBus property changes
matchRule := "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
err = conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule).Err
if err != nil {
err = errw.WithMessage(err, "failed to add DBus match rule")

Check failure on line 210 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

ineffectual assignment to err (ineffassign)
return
}

bwp.logger.Info("waiting for a BLE pairing request...")

for signal := range signalChan {
// Check if the signal is from a BlueZ device
if len(signal.Body) < 3 {
continue
}

iface, ok := signal.Body[0].(string)
if !ok || iface != "org.bluez.Device1" {
continue
}

// Check if the "Paired" property is in the event
changedProps, ok := signal.Body[1].(map[string]dbus.Variant)
if !ok {
continue
}

// TODO [APP-7613]: Pairing attempts from an iPhone connect first
// before pairing, so listen for a "Connected" event on the system
// D-Bus. This should be tested against Android.
connected, exists := changedProps["Connected"]
if !exists || connected.Value() != true {
continue
}

// Extract device path from the signal sender
devicePath := string(signal.Path)

// Convert DBus object path to MAC address
deviceMAC := convertDBusPathToMAC(devicePath)
if deviceMAC == "" {
continue
}

bwp.logger.Infof("device %s initiated pairing!", deviceMAC)

// Mark device as trusted
if err = trustDevice(bwp.logger, devicePath); err != nil {
err = errw.WithMessage(err, "failed to trust device")

Check failure on line 254 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

ineffectual assignment to err (ineffassign)
return
} else {
bwp.logger.Info("device successfully trusted!")
}
}
}, nil)
if err != nil {

Check failure on line 261 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

nilness: impossible condition: nil != nil (govet)
bwp.logger.Errorw(
"failed to listen for pairing request (will have to manually accept pairing request on device)",
"err", err)
}
}

func (bwp *BluetoothWiFiProvisioner) writeAvailableNetworks(ctx context.Context, networks *AvailableWiFiNetworks) error {

Check failure on line 268 in subsystems/provisioning/bluetooth/bluetooth_wifi_provisioner.go

View workflow job for this annotation

GitHub Actions / Test lint and build

`(*BluetoothWiFiProvisioner).writeAvailableNetworks` - `ctx` is unused (unparam)
bwp.availableWiFiNetworksChannelWriteOnly <- networks
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package ble
import (
"context"
"fmt"
"strings"
"time"

"github.com/godbus/dbus"
"github.com/pkg/errors"
"go.viam.com/rdk/logging"
)

// emptyBluetoothCharacteristicError represents the error which is raised when we attempt to read from an empty BLE characteristic.
Expand Down Expand Up @@ -48,3 +51,42 @@ func waitForBLEValue(
return v, nil
}
}

const (
BluezDBusService = "org.bluez"
BluezAgentPath = "/custom/agent"
BluezAgent = "org.bluez.Agent1"
)

// trustDevice sets the device as trusted and connects to it.
func trustDevice(logger logging.Logger, devicePath string) error {
conn, err := dbus.SystemBus()
if err != nil {
return fmt.Errorf("failed to connect to DBus: %w", err)
}

obj := conn.Object(BluezDBusService, dbus.ObjectPath(devicePath))

// Set Trusted = true
call := obj.Call("org.freedesktop.DBus.Properties.Set", 0,
"org.bluez.Device1", "Trusted", dbus.MakeVariant(true))
if call.Err != nil {
return fmt.Errorf("failed to set Trusted property: %w", call.Err)
}
logger.Info("device marked as trusted.")

return nil
}

// convertDBusPathToMAC converts a DBus object path to a Bluetooth MAC address.
func convertDBusPathToMAC(path string) string {
parts := strings.Split(path, "/")
if len(parts) < 4 {
return ""
}

// Extract last part and convert underscores to colons
macPart := parts[len(parts)-1]
mac := strings.ReplaceAll(macPart, "_", ":")
return mac
}
Loading