Skip to content

Commit

Permalink
fix(dbusx): 🐛 introspect a method before calling to santize arguments
Browse files Browse the repository at this point in the history
- add an introspection type which can be used to perform D-Bus introspection
- before calling a method, introspect it to retrieve its argument details
- sanitize a methods arguments to ensure they are the correct D-Bus type
  • Loading branch information
joshuar committed Sep 6, 2024
1 parent 2588436 commit 9bbf0d9
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 6 deletions.
51 changes: 51 additions & 0 deletions pkg/linux/dbusx/introspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2024 Joshua Rich <joshua.rich@gmail.com>
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

package dbusx

import (
"errors"
"fmt"
"slices"
"strings"

"github.com/godbus/dbus/v5/introspect"
)

var ErrMethodNotFound = errors.New("method not found")

type Introspection introspect.Node

func (i Introspection) GetMethod(name string) (*Method, error) {
for _, intr := range i.Interfaces {
found := slices.IndexFunc(intr.Methods, func(e introspect.Method) bool {
return strings.HasSuffix(name, e.Name)
})

if found != -1 {
return &Method{
name: name,
intr: intr.Name,
path: i.Name,
obj: &intr.Methods[found],
}, nil
}
}

return nil, ErrMethodNotFound
}

func NewIntrospection(bus *Bus, intr, path string) (*Introspection, error) {
obj := bus.getObject(intr, path)

node, err := introspect.Call(obj)
if err != nil {
return nil, fmt.Errorf("unable to introspect: %w", err)
}

nodeObj := Introspection(*node)

return &nodeObj, nil
}
107 changes: 101 additions & 6 deletions pkg/linux/dbusx/method.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ package dbusx

import (
"context"
"errors"
"fmt"
"log/slog"
"slices"

"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
)

var ErrIntrospectionNotAvail = errors.New("introspection not available on object")

//revive:disable:struct-tag
type Method struct {
obj *introspect.Method
bus *Bus `validate:"required"`
path string `validate:"required"`
intr string `validate:"required"`
Expand All @@ -25,19 +34,105 @@ func (m *Method) Call(ctx context.Context, args ...any) error {

obj := m.bus.getObject(m.intr, m.path)

var err error

if len(args) > 0 {
err = obj.CallWithContext(ctx, m.name, 0, args...).Err
cleanArgs, warnings := m.sanitizeArgs(args)
if warnings != nil {
m.bus.traceLog("Sanitized method arguments with warnings", slog.Any("warnings", warnings))
}

err := obj.CallWithContext(ctx, m.name, 0, cleanArgs...).Err
if err != nil {
return fmt.Errorf("%s: unable to call method %s (args: %v): %w", m.bus.busType.String(), m.name, cleanArgs, err)
}
} else {
err = obj.CallWithContext(ctx, m.name, 0).Err
err := obj.CallWithContext(ctx, m.name, 0).Err
if err != nil {
return fmt.Errorf("%s: unable to call method %s: %w", m.bus.busType.String(), m.name, err)
}
}

return nil
}

func (m *Method) IntrospectArgs() ([]introspect.Arg, error) {
if m.obj == nil {
return nil, ErrIntrospectionNotAvail
}

return m.obj.Args, nil
}

//nolint:gocognit
func (m *Method) sanitizeArgs(args []any) ([]any, error) {
introspection, err := NewIntrospection(m.bus, m.intr, m.path)
if err != nil {
return fmt.Errorf("%s: unable to call method %s (args: %v): %w", m.bus.busType.String(), m.name, args, err)
return nil, fmt.Errorf("could not introspect: %w", err)
}

return nil
method, err := introspection.GetMethod(m.name)
if err != nil {
return nil, fmt.Errorf("could not retrieve method details: %w", err)
}

methodArgs, err := method.IntrospectArgs()
if err != nil {
return nil, fmt.Errorf("could not retrieve method arguments: %w", err)
}

methodArgs = slices.DeleteFunc(methodArgs, func(e introspect.Arg) bool {
return e.Direction == "out"
})
cleanArgs := make([]any, len(args))

var warnings error

for idx, arg := range methodArgs {
var variant dbus.Variant

sig, err := dbus.ParseSignature(arg.Type)
if err != nil {
m.bus.traceLog("Could not parse argument signature. Attempting to guess signature.", slog.Any("error", err))

variant = dbus.MakeVariant(args[idx])
} else {
variant = dbus.MakeVariantWithSignature(args[idx], sig)
}

switch arg.Type {
case "u":
value, err := VariantToValue[uint32](variant)
if err != nil {
warnings = errors.Join(warnings, fmt.Errorf("could not convert argument %d, using default value: %w", idx, err))
}

cleanArgs[idx] = value
case "i":
value, err := VariantToValue[int32](variant)
if err != nil {
warnings = errors.Join(warnings, fmt.Errorf("could not convert argument %d, using default value: %w", idx, err))
}

cleanArgs[idx] = value
case "a{sv}":
value, err := VariantToValue[map[string]any](variant)
if err != nil {
warnings = errors.Join(warnings, fmt.Errorf("could not convert argument %d, using default value: %w", idx, err))
}

cleanArgs[idx] = value
case "as":
value, err := VariantToValue[[]string](variant)
if err != nil {
warnings = errors.Join(warnings, fmt.Errorf("could not convert argument %d, using default value: %w", idx, err))
}

cleanArgs[idx] = value
default:
cleanArgs[idx] = variant.Value()
}
}

return cleanArgs, warnings
}

func NewMethod(bus *Bus, intr, path, name string) *Method {
Expand Down

0 comments on commit 9bbf0d9

Please sign in to comment.