mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-11 07:52:50 -05:00
switch hto monorepo structure
This commit is contained in:
341
backend/internal/server/bluez/agent.go
Normal file
341
backend/internal/server/bluez/agent.go
Normal file
@@ -0,0 +1,341 @@
|
||||
package bluez
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/backend/internal/errdefs"
|
||||
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log"
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
bluezService = "org.bluez"
|
||||
agentManagerPath = "/org/bluez"
|
||||
agentManagerIface = "org.bluez.AgentManager1"
|
||||
agent1Iface = "org.bluez.Agent1"
|
||||
device1Iface = "org.bluez.Device1"
|
||||
agentPath = "/com/danklinux/bluez/agent"
|
||||
agentCapability = "KeyboardDisplay"
|
||||
)
|
||||
|
||||
const introspectXML = `
|
||||
<node>
|
||||
<interface name="org.bluez.Agent1">
|
||||
<method name="Release"/>
|
||||
<method name="RequestPinCode">
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="out" type="s" name="pincode"/>
|
||||
</method>
|
||||
<method name="RequestPasskey">
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="out" type="u" name="passkey"/>
|
||||
</method>
|
||||
<method name="DisplayPinCode">
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="s" name="pincode"/>
|
||||
</method>
|
||||
<method name="DisplayPasskey">
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="u" name="passkey"/>
|
||||
<arg direction="in" type="q" name="entered"/>
|
||||
</method>
|
||||
<method name="RequestConfirmation">
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="u" name="passkey"/>
|
||||
</method>
|
||||
<method name="RequestAuthorization">
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
</method>
|
||||
<method name="AuthorizeService">
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="s" name="uuid"/>
|
||||
</method>
|
||||
<method name="Cancel"/>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg direction="out" type="s" name="data"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>`
|
||||
|
||||
type BluezAgent struct {
|
||||
conn *dbus.Conn
|
||||
broker PromptBroker
|
||||
}
|
||||
|
||||
func NewBluezAgent(broker PromptBroker) (*BluezAgent, error) {
|
||||
conn, err := dbus.ConnectSystemBus()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("system bus connection failed: %w", err)
|
||||
}
|
||||
|
||||
agent := &BluezAgent{
|
||||
conn: conn,
|
||||
broker: broker,
|
||||
}
|
||||
|
||||
if err := conn.Export(agent, dbus.ObjectPath(agentPath), agent1Iface); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("agent export failed: %w", err)
|
||||
}
|
||||
|
||||
if err := conn.Export(agent, dbus.ObjectPath(agentPath), "org.freedesktop.DBus.Introspectable"); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("introspection export failed: %w", err)
|
||||
}
|
||||
|
||||
mgr := conn.Object(bluezService, dbus.ObjectPath(agentManagerPath))
|
||||
if err := mgr.Call(agentManagerIface+".RegisterAgent", 0, dbus.ObjectPath(agentPath), agentCapability).Err; err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("agent registration failed: %w", err)
|
||||
}
|
||||
|
||||
if err := mgr.Call(agentManagerIface+".RequestDefaultAgent", 0, dbus.ObjectPath(agentPath)).Err; err != nil {
|
||||
log.Debugf("[BluezAgent] not default agent: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("[BluezAgent] registered at %s with capability %s", agentPath, agentCapability)
|
||||
return agent, nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) Close() {
|
||||
if a.conn == nil {
|
||||
return
|
||||
}
|
||||
mgr := a.conn.Object(bluezService, dbus.ObjectPath(agentManagerPath))
|
||||
mgr.Call(agentManagerIface+".UnregisterAgent", 0, dbus.ObjectPath(agentPath))
|
||||
a.conn.Close()
|
||||
}
|
||||
|
||||
func (a *BluezAgent) Release() *dbus.Error {
|
||||
log.Infof("[BluezAgent] Release called")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) RequestPinCode(device dbus.ObjectPath) (string, *dbus.Error) {
|
||||
log.Infof("[BluezAgent] RequestPinCode: device=%s", device)
|
||||
|
||||
secrets, err := a.promptFor(device, "pin", []string{"pin"}, nil)
|
||||
if err != nil {
|
||||
log.Warnf("[BluezAgent] RequestPinCode failed: %v", err)
|
||||
return "", a.errorFrom(err)
|
||||
}
|
||||
|
||||
pin := secrets["pin"]
|
||||
log.Infof("[BluezAgent] RequestPinCode returning PIN (len=%d)", len(pin))
|
||||
return pin, nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) RequestPasskey(device dbus.ObjectPath) (uint32, *dbus.Error) {
|
||||
log.Infof("[BluezAgent] RequestPasskey: device=%s", device)
|
||||
|
||||
secrets, err := a.promptFor(device, "passkey", []string{"passkey"}, nil)
|
||||
if err != nil {
|
||||
log.Warnf("[BluezAgent] RequestPasskey failed: %v", err)
|
||||
return 0, a.errorFrom(err)
|
||||
}
|
||||
|
||||
passkey, err := strconv.ParseUint(secrets["passkey"], 10, 32)
|
||||
if err != nil {
|
||||
log.Warnf("[BluezAgent] invalid passkey format: %v", err)
|
||||
return 0, dbus.MakeFailedError(fmt.Errorf("invalid passkey: %w", err))
|
||||
}
|
||||
|
||||
log.Infof("[BluezAgent] RequestPasskey returning: %d", passkey)
|
||||
return uint32(passkey), nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) DisplayPinCode(device dbus.ObjectPath, pincode string) *dbus.Error {
|
||||
log.Infof("[BluezAgent] DisplayPinCode: device=%s, pin=%s", device, pincode)
|
||||
|
||||
_, err := a.promptFor(device, "display-pin", []string{}, &pincode)
|
||||
if err != nil {
|
||||
log.Warnf("[BluezAgent] DisplayPinCode acknowledgment failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, entered uint16) *dbus.Error {
|
||||
log.Infof("[BluezAgent] DisplayPasskey: device=%s, passkey=%06d, entered=%d", device, passkey, entered)
|
||||
|
||||
if entered == 0 {
|
||||
pk := passkey
|
||||
_, err := a.promptFor(device, "display-passkey", []string{}, nil)
|
||||
if err != nil {
|
||||
log.Warnf("[BluezAgent] DisplayPasskey acknowledgment failed: %v", err)
|
||||
}
|
||||
_ = pk
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
|
||||
log.Infof("[BluezAgent] RequestConfirmation: device=%s, passkey=%06d", device, passkey)
|
||||
|
||||
secrets, err := a.promptFor(device, "confirm", []string{"decision"}, nil)
|
||||
if err != nil {
|
||||
log.Warnf("[BluezAgent] RequestConfirmation failed: %v", err)
|
||||
return a.errorFrom(err)
|
||||
}
|
||||
|
||||
if secrets["decision"] != "yes" && secrets["decision"] != "accept" {
|
||||
log.Debugf("[BluezAgent] RequestConfirmation rejected by user")
|
||||
return dbus.NewError("org.bluez.Error.Rejected", nil)
|
||||
}
|
||||
|
||||
log.Infof("[BluezAgent] RequestConfirmation accepted")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) RequestAuthorization(device dbus.ObjectPath) *dbus.Error {
|
||||
log.Infof("[BluezAgent] RequestAuthorization: device=%s", device)
|
||||
|
||||
secrets, err := a.promptFor(device, "authorize", []string{"decision"}, nil)
|
||||
if err != nil {
|
||||
log.Warnf("[BluezAgent] RequestAuthorization failed: %v", err)
|
||||
return a.errorFrom(err)
|
||||
}
|
||||
|
||||
if secrets["decision"] != "yes" && secrets["decision"] != "accept" {
|
||||
log.Debugf("[BluezAgent] RequestAuthorization rejected by user")
|
||||
return dbus.NewError("org.bluez.Error.Rejected", nil)
|
||||
}
|
||||
|
||||
log.Infof("[BluezAgent] RequestAuthorization accepted")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) AuthorizeService(device dbus.ObjectPath, uuid string) *dbus.Error {
|
||||
log.Infof("[BluezAgent] AuthorizeService: device=%s, uuid=%s", device, uuid)
|
||||
|
||||
secrets, err := a.promptFor(device, "authorize-service:"+uuid, []string{"decision"}, nil)
|
||||
if err != nil {
|
||||
log.Warnf("[BluezAgent] AuthorizeService failed: %v", err)
|
||||
return a.errorFrom(err)
|
||||
}
|
||||
|
||||
if secrets["decision"] != "yes" && secrets["decision"] != "accept" {
|
||||
log.Debugf("[BluezAgent] AuthorizeService rejected by user")
|
||||
return dbus.NewError("org.bluez.Error.Rejected", nil)
|
||||
}
|
||||
|
||||
log.Infof("[BluezAgent] AuthorizeService accepted")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) Cancel() *dbus.Error {
|
||||
log.Infof("[BluezAgent] Cancel called")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) Introspect() (string, *dbus.Error) {
|
||||
return introspectXML, nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) promptFor(device dbus.ObjectPath, requestType string, fields []string, displayValue *string) (map[string]string, error) {
|
||||
if a.broker == nil {
|
||||
return nil, fmt.Errorf("broker not initialized")
|
||||
}
|
||||
|
||||
deviceName, deviceAddr := a.getDeviceInfo(device)
|
||||
hints := []string{}
|
||||
if displayValue != nil {
|
||||
hints = append(hints, *displayValue)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
var passkey *uint32
|
||||
if requestType == "confirm" || requestType == "display-passkey" {
|
||||
if displayValue != nil {
|
||||
if pk, err := strconv.ParseUint(*displayValue, 10, 32); err == nil {
|
||||
pk32 := uint32(pk)
|
||||
passkey = &pk32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
token, err := a.broker.Ask(ctx, PromptRequest{
|
||||
DevicePath: string(device),
|
||||
DeviceName: deviceName,
|
||||
DeviceAddr: deviceAddr,
|
||||
RequestType: requestType,
|
||||
Fields: fields,
|
||||
Hints: hints,
|
||||
Passkey: passkey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prompt creation failed: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("[BluezAgent] waiting for user response (token=%s)", token)
|
||||
reply, err := a.broker.Wait(ctx, token)
|
||||
if err != nil {
|
||||
if errors.Is(err, errdefs.ErrSecretPromptTimeout) {
|
||||
return nil, err
|
||||
}
|
||||
if reply.Cancel || errors.Is(err, errdefs.ErrSecretPromptCancelled) {
|
||||
return nil, errdefs.ErrSecretPromptCancelled
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !reply.Accept && len(fields) > 0 {
|
||||
return nil, errdefs.ErrSecretPromptCancelled
|
||||
}
|
||||
|
||||
return reply.Secrets, nil
|
||||
}
|
||||
|
||||
func (a *BluezAgent) getDeviceInfo(device dbus.ObjectPath) (string, string) {
|
||||
obj := a.conn.Object(bluezService, device)
|
||||
|
||||
var name, alias, addr string
|
||||
|
||||
nameVar, err := obj.GetProperty(device1Iface + ".Name")
|
||||
if err == nil {
|
||||
if n, ok := nameVar.Value().(string); ok {
|
||||
name = n
|
||||
}
|
||||
}
|
||||
|
||||
aliasVar, err := obj.GetProperty(device1Iface + ".Alias")
|
||||
if err == nil {
|
||||
if a, ok := aliasVar.Value().(string); ok {
|
||||
alias = a
|
||||
}
|
||||
}
|
||||
|
||||
addrVar, err := obj.GetProperty(device1Iface + ".Address")
|
||||
if err == nil {
|
||||
if a, ok := addrVar.Value().(string); ok {
|
||||
addr = a
|
||||
}
|
||||
}
|
||||
|
||||
if alias != "" {
|
||||
return alias, addr
|
||||
}
|
||||
if name != "" {
|
||||
return name, addr
|
||||
}
|
||||
return addr, addr
|
||||
}
|
||||
|
||||
func (a *BluezAgent) errorFrom(err error) *dbus.Error {
|
||||
if errors.Is(err, errdefs.ErrSecretPromptTimeout) {
|
||||
return dbus.NewError("org.bluez.Error.Canceled", nil)
|
||||
}
|
||||
if errors.Is(err, errdefs.ErrSecretPromptCancelled) {
|
||||
return dbus.NewError("org.bluez.Error.Canceled", nil)
|
||||
}
|
||||
return dbus.MakeFailedError(err)
|
||||
}
|
||||
Reference in New Issue
Block a user