sidorares/dbus-native

Problem with setting a property from connman

DLoT opened this issue · 9 comments

DLoT commented

Hi,

i'am having a problem with setting a property from connman.

The service function looks like this

<method name="SetProperty">
  <arg name="name" type="s" direction="in"/>
  <arg name="value" type="v" direction="in"/>
</method>

The signature for the SetProperty method should be sv

I tried several body configurations. This is the one i suppose should work

const dbus = require('dbus-native'), conn = dbus.systemBus();
conn.invoke({
  type:        1,
  path:        '/net/connman/service/ethernet_9059af8f4854_cable',
  destination: 'net.connman',
  interface:   'net.connman.Service',
  member:      'SetProperty',
  signature:   'sv',
  body:        ['IPv4.Configuration', ['a{sv}', [['Method', ['s', 'dhcp']]]]]

}, (err, result) => {
  console.log(err, result);
});

#132 lead me to my current solution that throws an Error: Invalid struct data

Can someone please give me a hint how to structure the body correctly?

hey @DLoT this is right structure:

var m = require('./lib/marshall.js')

const data = [ // top level struct
    'IPv4.Configuration',  // s 
    [                      // v
      'a{sv}',             // v signature
      [                    // { start of the a{sv} struct  
        [                  // a
          [                // s
            'Method',
            ['s', 'dhcp']  // v
          ]
        ]
      ]
    ]
]
console.log(m('sv', data, 0));

confusing bit probably is that 'a{sv}' variant is array of arrays and not just array, but if you imagine that it could be 'ia{sv}' for example that would be [int, [ a{asv} ]] - top level signature is structure itself, even when only single element in the structure

I really want to allow plain javascript objects where a{sv} used but this is still not implemented. When added you'll be able to do ['IPv4.Configuration', {Method: 'dhcp'}] instead

DLoT commented

Hi @sidorares,

It is working!

Thank you for your help, much appreciated!

I have another question, is there any easy way to parse the return value to an JS object?

I have written my own parser but its fragile...

What was your return value looked like @DLoT ? Can you put here your "convert to js object" code?

DLoT commented

hey @sidorares

this is a network configuration from connman looks like.
It's basically an array of dict containers

const testNetwork: any =
          [
            ['/net/connman/service/ethernet_9059af8f4854_cable', [
              ['Type', [[{ type: 's', child: [] }], ['ethernet']]],
              ['Security', [[{ type: 'a', child: [{ type: 's', child: [] }] }], [[]]]],
              ['State', [[{ type: 's', child: [] }], ['online']]],
              ['Favorite', [[{ type: 'b', child: [] }], [true]]],
              ['Immutable', [[{ type: 'b', child: [] }], [false]]],
              ['AutoConnect', [[{ type: 'b', child: [] }], [true]]],
              ['Name', [[{ type: 's', child: [] }], ['Wired']]],
              ['Ethernet',
                [[{ type: 'a', child: [{ type: '{', child: [{ type: 's', child: [] }, { type: 'v', child: [] }] }] }],
                  [[['Method', [[{ type: 's', child: [] }], ['auto']]],
                    ['Interface', [[{ type: 's', child: [] }], ['eth0']]],
                    ['Address', [[{ type: 's', child: [] }], ['AB:CD:EF:01:02:03']]],
                    ['MTU', [[{ type: 'q', child: [] }], [1500]]]]]]],
              ['IPv4',
                [[{
                  type: 'a',
                  child:
                        [{
                          type:  '{',
                          child: [{ type: 's', child: [] }, { type: 'v', child: [] }],
                        }],
                }],
                  [[['Method', [[{ type: 's', child: [] }], ['dhcp']]],
                    ['Address',
                      [[{ type: 's', child: [] }], ['192.168.1.162']]],
                    ['Netmask',
                      [[{ type: 's', child: [] }], ['255.255.254.0']]],
                    ['Gateway',
                      [[{ type: 's', child: [] }], ['192.168.1.4']]]]]]],
              ['IPv4.Configuration',
                [[{
                  type: 'a',
                  child:
                        [{
                          type:  '{',
                          child: [{ type: 's', child: [] }, { type: 'v', child: [] }],
                        }],
                }],
                  [[['Method', [[{ type: 's', child: [] }], ['dhcp']]]]]]],
              ['IPv6',
                [[{
                  type: 'a',
                  child:
                        [{
                          type:  '{',
                          child: [{ type: 's', child: [] }, { type: 'v', child: [] }],
                        }],
                }],
                  [[['Method', [[{ type: 's', child: [] }], ['auto']]],
                    ['Address',
                      [[{ type: 's', child: [] }],
                        ['someIpV6']]],
                    ['PrefixLength', [[{ type: 'y', child: [] }], [64]]],
                    ['Privacy', [[{ type: 's', child: [] }], ['disabled']]]]]]],
              ['IPv6.Configuration',
                [[{
                  type: 'a',
                  child:
                        [{
                          type:  '{',
                          child: [{ type: 's', child: [] }, { type: 'v', child: [] }],
                        }],
                }],
                  [[['Method', [[{ type: 's', child: [] }], ['auto']]],
                    ['Privacy', [[{ type: 's', child: [] }], ['disabled']]]]]]],
              ['Nameservers',
                [[{ type: 'a', child: [{ type: 's', child: [] }] }],
                  [['192.168.1.2', '8.8.8.8']]]],
              ['Nameservers.Configuration',
                [[{ type: 'a', child: [{ type: 's', child: [] }] }],
                  [['192.168.1.2', '8.8.8.8']]]],
              ['Timeservers',
                [[{ type: 'a', child: [{ type: 's', child: [] }] }],
                  [['192.168.1.4']]]],
              ['Timeservers.Configuration',
                [[{ type: 'a', child: [{ type: 's', child: [] }] }],
                  [[]]]],
              ['Domains',
                [[{ type: 'a', child: [{ type: 's', child: [] }] }],
                  [['some.domain']]]],
              ['Domains.Configuration',
                [[{ type: 'a', child: [{ type: 's', child: [] }] }],
                  [[]]]],
              ['Proxy',
                [[{
                  type: 'a',
                  child:
                        [{
                          type:  '{',
                          child: [{ type: 's', child: [] }, { type: 'v', child: [] }],
                        }],
                }],
                  [[['Method', [[{ type: 's', child: [] }], ['direct']]]]]]],
              ['Proxy.Configuration',
                [[{
                  type: 'a',
                  child:
                        [{
                          type:  '{',
                          child: [{ type: 's', child: [] }, { type: 'v', child: [] }],
                        }],
                }],
                  [[]]]],
              ['Provider',
                [[{
                  type: 'a',
                  child:
                        [{
                          type:  '{',
                          child: [{ type: 's', child: [] }, { type: 'v', child: [] }],
                        }],
                }], [[]]]],
            ]],
          ];

this is what it looks like when parsed

 const expectedObject: any = {
        '/net/connman/service/ethernet_9059af8f4854_cable': {
          Type:                        'ethernet',
          Security:                    [],
          State:                       'online',
          Favorite:                    true,
          Immutable:                   false,
          AutoConnect:                 true,
          Name:                        'Wired',
          Ethernet:                    {
            Method:    'auto',
            Interface: 'eth0',
            Address:   'AB:CD:EF:01:02:03',
            MTU:       1500,
          },
          IPv4:                        {
            Method:  'dhcp',
            Address: '192.168.1.162',
            Netmask: '255.255.254.0',
            Gateway: '192.168.1.4',
          },
          'IPv4.Configuration':        { Method: 'dhcp' },
          IPv6:                        {
            Method:       'auto',
            Address:      '',
            PrefixLength: 64,
            Privacy:      'disabled',
          },
          'IPv6.Configuration':        { Method: 'auto', Privacy: 'disabled' },
          Nameservers:                 ['192.168.1.1', '8.8.8.8'],
          'Nameservers.Configuration': ['192.168.1.1', '8.8.8.8'],
          Timeservers:                 ['192.168.1.4'],
          'Timeservers.Configuration': [],
          Domains:                     ['some.domain'],
          'Domains.Configuration':     [],
          Proxy:                       { Method: 'direct' },
          'Proxy.Configuration':       {},
          Provider:                    {},
        },
      };

and this is my code to parse dbus-native message responses. First i tired to do it in a single function but it turned out that having different functions are much more suitable. The magic happens in
DbusMessageParseService.container

import { injectable } from 'inversify';
import { IDbusMessageParseService } from './IDbusMessageParseService';

/**
 * Service for parsing dbus native messages
 */
@injectable()
export class DbusMessageParseService implements IDbusMessageParseService {

  /**
   * Parse a single dbus value
   *
   * @param {any} data The data from dbus
   * @returns {T}
   */
  public single<T>(data: any): T {
    return data[1][0];
  }

  /**
   * Parse an array of dbus containers
   *
   * @param {any} data The data from dbus
   * @returns {T}
   */
  public containerList<T>(data: any): T {
    const result: any = {} as T;
    data.forEach((value: any) => {
      result[value[0]] = this.container(value[1]);
    });

    return result;
  }

  /**
   * Parse a dbus container
   *
   * @param {any} data The data from dbus
   * @returns {T}
   */
  public container<T>(data: any): T {
    const result: any = {} as T;
    data.forEach((item: any) => {
      if (this.isArray(item) === false) {
        result[item[0]] = item[1][1][0];
        return;
      }
      if (this.isDictionary(item) === true) {
        result[item[0]] = this.container(item[1][1][0]);
        return;
      }

      result[item[0]] = item[1][1][0];
    });

    return result;
  }

  /**
   * Check if data is an array
   *
   * @param data The Dbus data
   * @returns {boolean}
   */
  private isArray(data: any): boolean {
    return data[1][0][0].type === 'a';
  }

  /**
   * Check if data is a dictionary container
   *
   * @param data The Dbus data
   * @returns {boolean}
   */
  private isDictionary(data: any): boolean {
    return data[1][0][0].child[0].type === '{';
  }
}

@DLoT : sorry to butt in, but are there now typescript definitions for dbus-native? or are you just importing as any?

DLoT commented

we have written some ;)
they are not complete yet, but may be a starting point.

we decided to just use the invoke method, and use neither the inferface nor the service creation stuff
so i'm not sure if we can provide proper definitions in the future though

declare module 'dbus-native' {
  import { EventEmitter } from 'events';

  export enum messageType {
    invalid      = 0,
    methodCall   = 1,
    methodReturn = 2,
    error        = 3,
    signal       = 4,
  }

  export interface IDbusNativeMessage {
    type?: messageType;
    serial?: number;
    path?: string;
    destination?: string;
    member: string;
    interface?: string;
    replySerial?: number;
    signature?: string;
    body?: Array<any>;
  }

  export interface IDbusNativeConnection extends EventEmitter {
  }

  export interface IDbusNativeSignal {
    connection: IDbusNativeConnection;
    bus: IDBusNative;
    message: IDbusNativeMessage;
    signature: string;
  }

  export interface IDbusNativeCallback<T> {
    (err: Error, arguments: T): void;
  }

  export interface IDBusNative {
    connection: IDbusNativeConnection;
    serial: number;
    cookies: Object;
    methodCallHandlers: Object;
    signals: EventEmitter;
    exportedObjects: Object;

    invoke<T>(message: IDbusNativeMessage, callback: IDbusNativeCallback<T>): void;

    invokeDbus<T>(message: IDbusNativeMessage, callback: IDbusNativeCallback<T>): void;

    mangle(): void;

    sendSignal(path: any, iface: any, name: any, signature: any, args: any): void;

    sendError(msg: any, errorName: any, errorText: any): void;

    setMethodCallHandler(objectPath: any, iface: any, member: any, handler: any): void;
  }

  export interface IDbusClientOptions {
    host: string;
    port?: string;
    socket: any;
    direct: boolean;
    busAddress: string;
  }

  export function createClient(params: IDbusClientOptions): IDBusNative;

  export function sessionBus(): IDBusNative;

  export function systemBus(): IDBusNative;
}
DLoT commented

@sidorares are you ok with a PR for the typings? Then i would polish them a little more

yes, definitely

Did those types ever make it in? Would be a great addition!