diff --git a/.gitignore b/.gitignore index c16649f..1223d4f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ pubspec.lock test/.test_cov.dart coverage/ +/test/widget_test.dart diff --git a/lib/src/dbus_read_buffer.dart b/lib/src/dbus_read_buffer.dart index fc5085e..1412f86 100644 --- a/lib/src/dbus_read_buffer.dart +++ b/lib/src/dbus_read_buffer.dart @@ -171,10 +171,28 @@ class DBusReadBuffer extends DBusBuffer { return null; } + //RSC reading an aay signature + // In the case of a type aay coming from the dbus, the data will occur like this. + // Assuming a single "ay" byte array (len 10 of char 'x') inside an "a" we will get + // 14,0,0,0,10,0,0,0,x,x,x,x,x,x,x,x,x,x + // This makes sense. + // If there were multiple 'ay' inside the a it would still work... + // e.g. if we had 2 ay's in the a (1 len 4), the other (len 6) + // 18,0,0,0,4,0,0,0,x,x,x,x,6,0,0,0,y,y,y,y,y,y + + //print(' <<<< RSC dbus_read_buffer.readMessage signature [${signature}]\n ${_data}\n'); + + // The issue here is that we need to calculate that initial length after + // iterating over the possible nested array elements to build our DBusValueType's correctly. + // e.g. we want DBusArray(DBusSignature('a'),DBusArray.bytes(bytevalues)) + // instead of DBusArray(DBusSignature('a'),[]), DBusArray.byte[byte values] + // which is what it currently returns (although this works) + var dataEnd = readOffset + dataLength; var values = []; if (signature != null) { var signatures = signature.split(); + List valueStack = []; for (var s in signatures) { var value = readDBusValue(s, endian, fdCount); if (value == null) { @@ -183,8 +201,37 @@ class DBusReadBuffer extends DBusBuffer { if (readOffset > dataEnd) { throw 'Message data of size $dataLength too small to contain ${signature.value}'; } - values.add(value); + if (s.value == 'a') { + // Specifically, this indicates an array of arrays. e.g. type='aay' + // which we need to translate into + // DBusArray(DBusSignature('a'),[DBusArray(DBusSignature(y),[DBusByte(42),DBusByte(99)])]) + // -or- more succinctly + // DBusArray(DBusSignature('a'),[DBusArray.byte([42,99])]) + // + // The 'a' sig value returned above is an empty DBusArray(DBusSignature('a'),[]) + // This will become the outter array later when they are all combined. + valueStack.add(value); + } else { + //If valueStack has anything in it, this is the second array in an array of arrays. + if ((value is DBusArray)&&(valueStack.length>0)) { + valueStack.add(value); + } + //Unwind any array of array values before adding the value to the values + if (valueStack.length>0) { + //print(' RSC COMBINING ARRAY OF ARRAYS DATA'); + DBusArray outterArr = valueStack[0] as DBusArray; + for (int ndx=1;ndx0) throw "SOMEONE DID NOT CLEAN THE valueStack"; + //RSC REVISIT if (readOffset != dataEnd) { throw 'Message data of size $dataLength too large to contain ${signature.value}'; } @@ -450,12 +497,15 @@ class DBusReadBuffer extends DBusBuffer { var end = readOffset + length.value; var children = []; - while (readOffset < end) { - var child = readDBusValue(childSignature, endian, fdCount); - if (child == null) { - return null; + //RSC If it is an a then just return the empty children and they will be filled in later. + if (childSignature.value != 'a') { + while (readOffset < end) { + var child = readDBusValue(childSignature, endian, fdCount); + if (child == null) { + return null; + } + children.add(child); } - children.add(child); } return DBusArray(childSignature, children); @@ -532,8 +582,14 @@ class DBusReadBuffer extends DBusBuffer { } return readDBusDict(keySignature, valueSignature, endian, fdCount); } else if (s.startsWith('a')) { - return readDBusArray( - DBusSignature(s.substring(1, s.length)), endian, fdCount); + // RSC aay should come in as an a then an ay + // So if this is just the 'a' then return just read that. + if (s.length==1) { + return readDBusArray( + DBusSignature(s.substring(0, s.length)), endian, fdCount); + } else + return readDBusArray( + DBusSignature(s.substring(1, s.length)), endian, fdCount); } else if (s.startsWith('(') && s.endsWith(')')) { return readDBusStruct( DBusSignature(s.substring(1, s.length - 1)).split(), endian, fdCount); diff --git a/lib/src/dbus_value.dart b/lib/src/dbus_value.dart index fe62588..0112163 100644 --- a/lib/src/dbus_value.dart +++ b/lib/src/dbus_value.dart @@ -629,6 +629,13 @@ class DBusSignature extends DBusValue { /// Check [value] contains a valid signature and return the index of the end of the current child signature. int _validate(String value, int index) { + //RSC REVISIT where this can happen + //print(' _validate called val:'+value+' index:'+index.toString()); + if (index>=value.length) { + print('RSC: INDEX EXCEEDED because aa passed to constructor of DBusSignature'); + return value.length-1; + } + if (value.startsWith('(', index)) { // Struct. var end = _findClosing(value, index, '(', ')'); @@ -659,10 +666,17 @@ class DBusSignature extends DBusValue { } return end; } else if (value.startsWith('a', index)) { - // Array. - if (index >= value.length - 1) { - throw ArgumentError.value(value, 'value', 'Array missing child type'); + //RSC commented + //if (index >= value.length - 1) { + // throw ArgumentError.value(value, 'value', 'Array missing child type'); + //} + if (value.length==1) { + //RSC So it can be just an "a" with no follow on type + //print(' RSC _validate HACK ALLOW "a"'); + return 1; } + //RSC REVISIT: + // Shouldnt we else with the original code??? dbus_write_buffer.writeMessage return _validate(value, index + 1); } else if (value.startsWith('m', index)) { // Maybe. @@ -687,6 +701,14 @@ class DBusSignature extends DBusValue { } else if (value.startsWith('a{', index)) { return _findClosing(value, index, '{', '}'); } else if (value.startsWith('a', index)) { + //RSC check for aa as in 'aay' or 'aas' + String rest = value.substring(index); + if (rest.length>1) { + if (rest.substring(1,2)=='a') { + //print('RSC: dbus_value._findChildSignatureEnd found an array of arrays in signature [${value}] index: ${index}'); + return index; //only return index because caller will add 1 to it + } + } return _findChildSignatureEnd(value, index + 1); } else if (value.startsWith('m', index)) { return _findChildSignatureEnd(value, index + 1); @@ -891,13 +913,16 @@ class DBusArray extends DBusValue { /// An exception will be thrown if a DBusValue in [children] doesn't have a signature matching [childSignature]. DBusArray(this.childSignature, [Iterable children = const []]) : children = children.toList() { - if (!childSignature.isSingleCompleteType) { + //RSC it could be just an 'a' + // if (!childSignature.isSingleCompleteType) { + if ((childSignature.value != 'a')&&(!childSignature.isSingleCompleteType)) { throw ArgumentError.value(childSignature, 'childSignature', 'Array value type must be a single complete type'); } for (var child in children) { - if (child.signature.value != childSignature.value) { + //RSC same as above + if ((childSignature.value != 'a')&&(child.signature.value != childSignature.value)) { throw ArgumentError.value(children, 'children', "Provided children don't match array signature ${childSignature.value}"); } diff --git a/lib/src/dbus_write_buffer.dart b/lib/src/dbus_write_buffer.dart index c166c55..425a490 100644 --- a/lib/src/dbus_write_buffer.dart +++ b/lib/src/dbus_write_buffer.dart @@ -68,7 +68,10 @@ class DBusWriteBuffer extends DBusBuffer { if (message.values.isNotEmpty) { var signature = ''; for (var value in message.values) { - signature += value.signature.value; + //RSC Build sig recursively in the case of aay + String walkVal = walkForSignature(value); + //signature += value.signature.value; + signature = signature + walkVal; } headers.add(_makeHeader(8, DBusSignature(signature))); } @@ -80,6 +83,23 @@ class DBusWriteBuffer extends DBusBuffer { align(8); writeBytes(valueBuffer.data); _resourceHandles.addAll(valueBuffer.resourceHandles); + //RSC + //print(' >>>>dbus_write_buffer.writeMessage data\n[${data}]\n'); + } + + //RSC recurse for signature (but should only do 1 extra level) REVISIT + String walkForSignature(DBusValue dval) { + //print(' ## RSC:walk runtimeType: ${dval.runtimeType}'); + if ((dval is DBusArray)&&(dval.childSignature.value == 'a')) { + //array of array..e.g. aay or aas would hit here first. + //print(' ## RSC:walk adding "a" (array of arrays)'); + //In writeMessage the DBusValue array of arrays would have children. + //if not then there is an error. + if ((dval.children).isEmpty) throw "Empty Array of Arrays found...${dval}"; + return 'a' + walkForSignature(dval.children[0]); + } + //Something like ay (byte array) or as (string array) + return dval.signature.value; } /// Makes a new message header. diff --git a/test/dbus_test.dart b/test/dbus_test.dart index 06427b3..4f6a844 100644 --- a/test/dbus_test.dart +++ b/test/dbus_test.dart @@ -518,9 +518,14 @@ void main() { // Too long. expect(() => DBusSignature('s' * 256), throwsArgumentError); // Missing array type. - expect(() => DBusSignature('a'), throwsArgumentError); + //RSC now allow 'a' signature in array. DBus signature aa is + //still bad but the way this test is run it fails because of the + //way _verify walks the signature. + //REVISIT + //expect(() => DBusSignature('a'), throwsArgumentError); + //expect(() => DBusSignature('aa'), throwsArgumentError); + expect(() => DBusSignature('(a)'), throwsArgumentError); - expect(() => DBusSignature('aa'), throwsArgumentError); expect(() => DBusSignature('a{sa}'), throwsArgumentError); // Missing maybe type. expect(() => DBusSignature('m'), throwsArgumentError); diff --git a/test/test-aay.dart b/test/test-aay.dart new file mode 100644 index 0000000..00df0ae --- /dev/null +++ b/test/test-aay.dart @@ -0,0 +1,374 @@ +import 'package:dbus/code_generator.dart'; +import 'package:dbus/dbus.dart'; +import 'package:dbus/src/dbus_bus_name.dart'; +import 'package:dbus/src/dbus_error_name.dart'; +import 'package:dbus/src/dbus_interface_name.dart'; +import 'package:dbus/src/dbus_match_rule.dart'; +import 'package:dbus/src/dbus_member_name.dart'; +import 'package:dbus/src/dbus_message.dart'; +import 'package:dbus/src/dbus_write_buffer.dart'; +import 'package:dbus/src/dbus_uuid.dart'; +import 'package:dbus/src/getuid.dart'; +import 'package:dbus/src/dbus_value.dart'; + +import 'dart:convert'; +import 'dart:io'; + +DBusClient systemDBusClient = DBusClient.system(); +//These get filled in in +late DBusRemoteObject serverObject; +late DBusRemoteObject entryGroupObject; +AvahiServiceConfig serviceConfig = AvahiServiceConfig('trixy',8765); + +void main() async { + + print('TEST Googles NearbyShare (Advertising) using Avahi via DBus on Linux.'); + print(" *This requires DBus data type='aay' (array of array of bytes).\nSteps."); + + print('1. Attach to Avahi Server using the Dart DBus lib.'); + print('2. Create an Avahi EntryGroup object.'); + print('3. Add a NearbyShare Service definition to the new EntryGroup.'); + print('4. Poll the EntryGroup status until the new service is ESTABLISHED'); + print('5. Call Avahi ResolveService via DBus to print content of our'); + print(' newly added NearbyShare (advertising) service.\n'); + print('BEGIN TEST ON Platform.hostname: ${Platform.localHostname}'); + + await attachAvahiObjects(); + + await testAddAvahiService(); + + await testResolveAvahiService(); + + print('DONE\n'); + print(''' + If there were no errors, You should be able see the Nearby Share named + ${serviceConfig.fullAdvertisedName} + from an android device or any Nearby Share client'''); + print('\nPRESS ENTER TO QUIT (removes service)...'); + + var line = stdin.readLineSync(encoding: utf8); + print('line: ${line}'); +} + +/* SAMPLE AVAHI SERVICE FILE + + + + + + + + I1NISVT8n14AAA + + + + + _FC9F5ED42C8A._tcp + 8088 + + n="BgYJBgkGCQYJBgkGCQYJBgkIcm94eXB1a2U" + + + +*/ + +// This class AvahiServiceConfig Config is a placeholder for props used to Advertise +// our Nearby Share service using mDNS on Avahi daemon on local LAN. + +class AvahiServiceConfig { + AvahiServiceConfig(this.nearbyAdvertisedName,this.port, + {this.domain='local', + this.host='', + this.interface=-1, + this.protocol=0, + this.flags=0} + ); + + final String serviceType = '_FC9F5ED42C8A._tcp'; + //The service Type is a fixed string. Check the PROTOCOL for why. + final String serviceName = 'I1NISVT8n14AAA'; + // The service Name is fixed here but we could allow the configuration of the + // four random characters...its not really worth it so I just use "SHIT" + // Hex or Char Base64 | Base64URLSafe + // 0x23, IW== | Iw + // 4 random chars "SHIT", U0hJVA== | U0hJVA + // 3 chars 0xFC, 0x9F, 0x5E, /J9e | _J9e + // 2 0x00 chars AAA= | AAA + // Full Hex: 23 53 48 49 54 FC 9F 5E 00 00 + // As Base64: I1NISVT8n14AAA== + // As Base64url: I1NISVT8n14AAA + + int port; //Port number for nearby share server to listen on. + String domain; //On a local linux lan this would be "local" + String host; //fully qualified e.g. roxy.local + int interface; //-1 all interfaces + int protocol; //0 unspecified + int flags; //0 unspecified + + String nearbyAdvertisedName; // see ctor. + + //If you pass a host name in, it needs to be fully qualified...e.g. myhost.local + String get nearbyAdvertisedHost { + if (this.host.trim().length == 0) { + if (Platform.localHostname.contains('.')) return Platform.localHostname; + else return Platform.localHostname+'.'+this.domain; + } else return this.host.trim(); + } + + String get fullAdvertisedName { + String s = this.nearbyAdvertisedHost+':'+this.nearbyAdvertisedName; //TODO add HOST + //String s = this.nearbyAdvertisedName; //Dont include host + return s; + } + // Return base64url encoded string of byte(val=0x06) + random bytes + nearbyAdvertisedName + // byte content: + // b1:000-version,0-visibility,011-laptop,0-reserved (00000110)=0x06 + // b2..b17 random hex 0x06,0x09,0x06,0x09,0x06,0x09,0x06,0x09,0x06,0x09,0x06,0x09,0x06,0x09,0x06,0x09 + // b18 (length of name of service as it will show up on the client nearby share screen "roxypuke") + // b19..(b19+b18) length = user visible device name...nearbyAdvertisedHost:nearbyAdvertisedName + // Note that a TXT record is an array of arrays but for a NearbyShare service it is only + // one string. + // e.g. n="base64url encoded string based on byte(val=0x06) + random bytes + nearbyAdvertisedName" + // There is no occassion (in this service definition) for actual array of array. + // e.g. x=y,a=b + // This method returns the string based on byte(val=0x06) + random bytes + nearbyAdvertisedName" + + String getSpecialEncodedFullAdvertisedName() { + + List fan = utf8.encode(this.fullAdvertisedName); + //cant be more than 256 bytes since length is single byte... + List byteArr = [6,6,9,6,9,6,9,6,9,6,9,6,9,6,9,6,9,fan.length]; + byteArr.addAll(fan); + // Should have something like this.... + // 'BgYJBgkGCQYJBgkGCQYJBgkIcm94eXB1a2U==' + return base64Url.encode(byteArr); + } + + // TXT Record notes. 1st and ONLY byte array of the TXT record. + //As per PROTOCOL.md from github NearDrop repo. + //The TXT record of the mdns record contains the "Advertised Name" plus other + //odd data as described above all globbed together and base64url'd. + //We combine nearbyAdvertisedHost+':'+nearbyAdvertisedName as the + // "Full Advertised Name" that a user connecting to us would see + // e.g. "myhost.local:My Cool Nearby Share Name" + // Should return something like n="BgYJBgkGCQYJBgkGCQYJBgkIcm94eXB1a2U==" + String get txt { + return('n="'+this.getSpecialEncodedFullAdvertisedName()+'"'); + } + + @override String toString() { + return + '''AvahiServiceConfig( + fullAdvertiseName: '${fullAdvertisedName}', + port: ${port}, serviceName: '${serviceName}', serviceType: '${serviceType}', + domain: '${domain}', host: '${nearbyAdvertisedHost}', + interface: ${interface}, protocol: ${protocol}, flags: ${flags}, + txt: '${this.txt}' + ) + '''; + } + List asAvahiAddServiceParamList() { + List txtAsBytes = utf8.encode(this.txt); + List txtAsByteList = []; + txtAsBytes.forEach((bv) { + txtAsByteList.add(DBusByte(bv)); + }); + DBusArray txtArr = DBusArray(DBusSignature('y'),txtAsByteList); + + var parmValues = [ + DBusInt32(this.interface), //interface 3 + DBusInt32(this.protocol), //protocol + DBusUint32(this.flags), //flags 13 + DBusString(this.serviceName), //name + DBusString(this.serviceType), //type [DO NOT CHANGE] + DBusString(this.domain), // domain + DBusString(this.nearbyAdvertisedHost), // host (TODO add domain?) + DBusUint16(this.port), //8019), //port + DBusArray(DBusSignature('a'),[txtArr]), + ]; + + return parmValues; + } + + List asAvahiResolveServiceParamList() { + var parmValues = [ + DBusInt32(this.interface), //interface 3 + DBusInt32(this.protocol), //protocol + DBusString(this.serviceName), //name + DBusString(this.serviceType), //type [DO NOT CHANGE] + DBusString(this.domain), // domain + DBusInt32(-1), //aprotocol? + DBusUint32(this.flags), //flags + ]; + return parmValues; + } + +} + + +Future testResolveAvahiService() async { + print(" --< testResolveAvahiService"); + //Test ResolveService call + //dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.Avahi / + // org.freedesktop.Avahi.Server.ResolveService int32:-1 int32:-1 + // string:"I1NISVT8n14AAA" string:"_FC9F5ED42C8A._tcp" string:"" int32:-1 uint32:0 + + try { + + var res = await serverObjectInvoke('ResolveService',serviceConfig.asAvahiResolveServiceParamList()); + print(' --< ResolveService results: ${res}'); + + } catch (ee) { + print('--< RESOLVE SERVICE ERROR ${ee}'); + } +} + +//From avahi source interface org.freedesktop.Avahi.EntryGroup +// +// +// +// +// +// +// +// +// +// +// +//Test adding a service to the Avahi daemon using DBus. +//This assumes that attachAvahiObjects has already run. + +Future testAddAvahiService() async { + + print(' --* Testing AddService to Entry Group ${entryGroupObject.path}'); + print(' --* AVAHI SERVICE CONFIG: ${serviceConfig}'); + print(' --* AVAHI ADD SERVICE PARMS: ${serviceConfig.asAvahiAddServiceParamList()}'); + + try { + //assume that an EntryGroup was already added in attachAvahiObjects() + + var aRes = await entryGroupObjectInvoke('GetState',[]); + print(' --* GetState results: ${aRes}'); + + var bRes = await entryGroupObjectInvoke('AddService',serviceConfig.asAvahiAddServiceParamList()); + print(' --* AddService results: ${bRes}'); + + var cRes = await entryGroupObjectInvoke('Commit',[]); + print(' --* Commit results: ${cRes}'); + + var dRes = await entryGroupObjectInvoke('GetState',[]); + print(' --* GetState results: ${dRes}'); + + var eRes = await entryGroupObjectInvoke('IsEmpty',[]); + print(' --* IsEmpty results: ${eRes}'); + + //typedef enum { + // AVAHI_ENTRY_GROUP_UNCOMMITED, /**< The group has not yet been committed, the user must still call avahi_entry_group_commit() */ + // AVAHI_ENTRY_GROUP_REGISTERING, /**< The entries of the group are currently being registered */ + // AVAHI_ENTRY_GROUP_ESTABLISHED, /**< The entries have successfully been established */ + // AVAHI_ENTRY_GROUP_COLLISION, /**< A name collision for one of the entries in the group has been detected, the entries have been withdrawn */ + // AVAHI_ENTRY_GROUP_FAILURE /**< Some kind of failure happened, the entries have been withdrawn */ + //} AvahiEntryGroupState; + int curState = (dRes[0] as DBusInt32).value; + while (curState<2) { + print(' --* LOOP curState: ${curState}'); + var lRes = await entryGroupObjectInvoke('GetState',[]); + curState = (lRes[0] as DBusInt32).value; + await Future.delayed(Duration(seconds: 2)); + } + print(' --* LOOP FINISHED STATE: ${curState}'); + print(' --* SERVICE ESTABLISHED'); + } catch (ee) { + print('--* ADD SERVICE ERROR ${ee}'); + } +} + + +Future attachAvahiObjects() async { + try { + print('--> Attaching Avahi Server and EntryGroup objects'); + serverObject = DBusRemoteObject(systemDBusClient, + name: 'org.freedesktop.Avahi', + path: DBusObjectPath('/') + ); + + //EntryGroupNew call + //dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.Avahi / + // org.freedesktop.Avahi.Server.EntryGroupNew + + var nbs_results = await serverObjectInvoke('EntryGroupNew',[]); + print(' -->EntryGroupNew results: ${nbs_results}'); + + DBusObjectPath entryGroupPath = nbs_results[0] as DBusObjectPath; + print(' -->New EntryGroupPath: ${entryGroupPath}'); + + entryGroupObject = DBusRemoteObject(systemDBusClient, + name: 'org.freedesktop.Avahi', + path: entryGroupPath + ); + + var eg_results = await entryGroupObjectInvoke('GetState',[]); + int egState = (eg_results[0] as DBusInt32).value; + print(' -->GetState result: ${egState}'); + + print('-->Avahi objects attached.'); + } catch (ae) { + print('-->ATTACH ERROR: ${ae}'); + } +} + +// UTILITY FUNCTIONS BELOW + +Future> serverObjectInvoke(methodName,args) async { + final interfaceName = 'org.freedesktop.Avahi.Server'; + var result = await serverObject.callMethod( + interfaceName, methodName, args + ); + return result.returnValues; +} +Future> entryGroupObjectInvoke(methodName,args) async { + final interfaceName = 'org.freedesktop.Avahi.EntryGroup'; + var eg_result = await entryGroupObject.callMethod( + interfaceName, methodName, args + ); + //print('RAW RESULT ${eg_result}'); + return eg_result.returnValues; +} + + diff --git a/test/test-array-of-arrays.dart b/test/test-array-of-arrays.dart new file mode 100644 index 0000000..e212f28 --- /dev/null +++ b/test/test-array-of-arrays.dart @@ -0,0 +1,111 @@ +import 'package:dbus/code_generator.dart'; +import 'package:dbus/dbus.dart'; +import 'package:dbus/src/dbus_bus_name.dart'; +import 'package:dbus/src/dbus_error_name.dart'; +import 'package:dbus/src/dbus_interface_name.dart'; +import 'package:dbus/src/dbus_match_rule.dart'; +import 'package:dbus/src/dbus_member_name.dart'; +import 'package:dbus/src/dbus_message.dart'; +import 'package:dbus/src/dbus_write_buffer.dart'; +import 'package:dbus/src/dbus_uuid.dart'; +import 'package:dbus/src/getuid.dart'; +import 'package:dbus/src/dbus_value.dart'; + +import 'dart:convert'; +import 'dart:io'; + + +void main() async { + print('TEST new array of arrays DBus types'); + + //type=iaayi + var test_aay = [ + DBusInt32(42), + DBusArray(DBusSignature('a'), [ + DBusArray(DBusSignature('y'), [DBusByte(88), DBusByte(88), DBusByte(88)]) + ]), + DBusInt32(69), + ]; + print('TEST Signature [iaayi]'); + dumpAsMessage(test_aay); + + //type = iaasi + var test_aas = [ + DBusInt32(42), + DBusArray(DBusSignature('a'), [ + DBusArray(DBusSignature('s'), + [DBusString('XXX'), DBusString('YYY'), DBusString('ZZZ')]) + ]), + DBusInt32(69), + ]; + print('TEST Signature [iaasi]'); + dumpAsMessage(test_aas); + + //type = iaayaasi + var test_both = [ + DBusInt32(42), + DBusArray(DBusSignature('a'), [ + DBusArray(DBusSignature('y'), [DBusByte(88), DBusByte(88), DBusByte(88)]), + ]), + DBusArray(DBusSignature('a'), [ + DBusArray(DBusSignature('s'), + [DBusString('XXX'), DBusString('YYY'), DBusString('ZZZ')]) + ]), + DBusInt32(69), + ]; + print('TEST Signature [iaayaasi]'); + dumpAsMessage(test_both); + + // type=iasi + var test_str_only = [ + DBusInt32(42), + DBusString('XXX'), + DBusInt32(69), + ]; + print('TEST Signature [isi]'); + dumpAsMessage(test_str_only); + + // type=ias + var test_str_no_trailing = [ + DBusInt32(42), + DBusString('XXX'), + ]; + print('TEST Signature [is]'); + dumpAsMessage(test_str_no_trailing); + + + var test_fail = [ + DBusInt32(42), + DBusArray(DBusSignature('a'), [ + DBusArray(DBusSignature('y'), [DBusByte(88), DBusByte(88), DBusByte(88)]), + DBusArray(DBusSignature('y'), [DBusByte(99), DBusByte(99), DBusByte(99)]), + ]), + ]; + print('TEST Signature [iaay]'); + dumpAsMessage(test_fail); + + print('DONE\n'); +} + +void dumpAsMessage(List valArr) { + print('++++Test fake message with values\n'); + DBusMessage fakeMsg = DBusMessage( + DBusMessageType.methodCall, + path: null, + interface:null, + member:null, + errorName:null, + replySerial:null, + destination: null, + sender: null, + values: valArr, + ); + + DBusWriteBuffer dbwb = DBusWriteBuffer(); + dbwb.writeMessage(fakeMsg); + + print(' ++++Fake message with values\n'+fakeMsg.toString()); + print(' ++++RAW fake message bytes\n${dbwb.data}'); + //dbwb.data.forEach((b)=> print(b.runtimeType.toString()+' => '+b.toString())); + print('++++DONE Test fake message with values'); +}