1 /// Thin OO wrapper around DBus types
2 module ddbus.thin;
3 
4 import core.time : Duration;
5 
6 import ddbus.attributes : isAllowedField;
7 import ddbus.c_lib;
8 import ddbus.conv;
9 import ddbus.exception : TypeMismatchException;
10 import ddbus.util;
11 
12 import std.meta : ApplyRight, Filter, staticIndexOf;
13 import std..string;
14 import std.typecons;
15 import std.exception;
16 import std.traits;
17 import std.conv;
18 import std.range;
19 import std.algorithm;
20 
21 // This import is public for backwards compatibility
22 public import ddbus.exception : wrapErrors, DBusException;
23 
24 struct ObjectPath {
25   enum root = ObjectPath("/");
26 
27   private string _value;
28 
29   this(string objPath) pure @safe {
30     enforce(isValid(objPath));
31     _value = objPath;
32   }
33 
34   string toString() const {
35     return _value;
36   }
37 
38   /++
39     Returns the string representation of this ObjectPath.
40    +/
41   string value() const pure @nogc nothrow @safe {
42     return _value;
43   }
44 
45   size_t toHash() const pure @nogc nothrow @trusted {
46     return hashOf(_value);
47   }
48 
49   T opCast(T : ObjectPath)() const pure @nogc nothrow @safe {
50     return this;
51   }
52 
53   T opCast(T : string)() const pure @nogc nothrow @safe {
54     return value;
55   }
56 
57   bool opEquals(ref const typeof(this) b) const pure @nogc nothrow @safe {
58     return _value == b._value;
59   }
60 
61   bool opEquals(const typeof(this) b) const pure @nogc nothrow @safe {
62     return _value == b._value;
63   }
64 
65   bool opEquals(string b) const pure @nogc nothrow @safe {
66     return _value == b;
67   }
68 
69   ObjectPath opBinary(string op : "~")(string rhs) const pure @safe {
70     if (!rhs.startsWith("/")) {
71       return opBinary!"~"(ObjectPath("/" ~ rhs));
72     } else {
73       return opBinary!"~"(ObjectPath(rhs));
74     }
75   }
76 
77   ObjectPath opBinary(string op : "~")(ObjectPath rhs) const pure @safe
78   in {
79     assert(ObjectPath.isValid(_value) && ObjectPath.isValid(rhs._value));
80   }
81   out (v) {
82     assert(ObjectPath.isValid(v._value));
83   }
84   do {
85     ObjectPath ret;
86 
87     if (_value == "/") {
88       ret._value = rhs._value;
89     } else {
90       ret._value = _value ~ rhs._value;
91     }
92 
93     return ret;
94   }
95 
96   void opOpAssign(string op : "~")(string rhs) pure @safe {
97     _value = opBinary!"~"(rhs)._value;
98   }
99 
100   void opOpAssign(string op : "~")(ObjectPath rhs) pure @safe {
101     _value = opBinary!"~"(rhs)._value;
102   }
103 
104   bool startsWith(ObjectPath withThat) pure @nogc nothrow @safe {
105     if (withThat._value == "/")
106       return true;
107     else if (_value == "/")
108       return false;
109     else
110       return _value.representation.splitter('/').startsWith(withThat._value.representation.splitter('/'));
111   }
112 
113   /// Removes a prefix from this path and returns the remainder. Keeps leading slashes.
114   /// Returns this unmodified if the prefix doesn't match.
115   ObjectPath chompPrefix(ObjectPath prefix) pure @nogc nothrow @safe {
116     if (prefix._value == "/" || !startsWith(prefix))
117       return this;
118     else if (prefix._value == _value)
119       return ObjectPath.root;
120     else
121       return ObjectPath.assumePath(_value[prefix._value.length .. $]);
122   }
123 
124   /++
125     Returns: `false` for empty strings or strings that don't match the
126     pattern `(/[0-9A-Za-z_]+)+|/`.
127    +/
128   static bool isValid(string objPath) pure @nogc nothrow @safe {
129     import std.ascii : isAlphaNum;
130 
131     if (!objPath.length) {
132       return false;
133     }
134 
135     if (objPath == "/") {
136       return true;
137     }
138 
139     if (objPath[0] != '/' || objPath[$ - 1] == '/') {
140       return false;
141     }
142 
143     // .representation to avoid unicode exceptions -> @nogc & nothrow
144     return objPath.representation.splitter('/').drop(1).all!(a => a.length
145         && a.all!(c => c.isAlphaNum || c == '_'));
146   }
147 
148   /// Does an unsafe assignment to an ObjectPath.
149   static ObjectPath assumePath(string path) pure @nogc nothrow @safe {
150     ObjectPath ret;
151     ret._value = path;
152     return ret;
153   }
154 }
155 
156 /// Serves as typesafe alias. Instances should be created using busName instead of casting.
157 /// It prevents accidental usage of bus names in other string parameter fields and makes the API clearer.
158 enum BusName : string {
159   none = null
160 }
161 
162 /// Casts a bus name argument to a BusName type. May include additional validation in the future.
163 BusName busName(string name) pure @nogc nothrow @safe {
164   return cast(BusName) name;
165 }
166 
167 /// Serves as typesafe alias. Instances should be created using interfaceName instead of casting.
168 /// It prevents accidental usage of interface paths in other string parameter fields and makes the API clearer.
169 enum InterfaceName : string {
170   none = null
171 }
172 
173 /// Casts a interface path argument to an InterfaceName type. May include additional validation in the future.
174 InterfaceName interfaceName(string path) pure @nogc nothrow @safe {
175   return cast(InterfaceName) path;
176 }
177 
178 /// Serving as a typesafe alias for a FileDescriptor.
179 enum FileDescriptor : uint {
180   none   = uint.max,
181   stdin  = 0,
182   stdout = 1,
183   stderr = 2
184 }
185 
186 /// Casts an integer to a FileDescriptor.
187 FileDescriptor fileDescriptor(uint fileNo) pure @nogc nothrow @safe {
188   return cast(FileDescriptor) fileNo;
189 }
190 
191 unittest {
192   import dunit.toolkit;
193 
194   ObjectPath("some.invalid/object_path").assertThrow();
195   ObjectPath("/path/with/TrailingSlash/").assertThrow();
196   ObjectPath("/path/without/TrailingSlash").assertNotThrown();
197   string path = "/org/freedesktop/DBus";
198   auto obj = ObjectPath(path);
199   obj.value.assertEqual(path);
200   obj.toHash().assertEqual(path.hashOf);
201 
202   ObjectPath("/some/path").startsWith(ObjectPath("/some")).assertTrue();
203   ObjectPath("/some/path").startsWith(ObjectPath("/path")).assertFalse();
204   ObjectPath("/some/path").startsWith(ObjectPath("/")).assertTrue();
205   ObjectPath("/").startsWith(ObjectPath("/")).assertTrue();
206   ObjectPath("/").startsWith(ObjectPath("/some/path")).assertFalse();
207 
208   ObjectPath("/some/path").chompPrefix(ObjectPath("/some")).assertEqual(ObjectPath("/path"));
209   ObjectPath("/some/path").chompPrefix(ObjectPath("/bar")).assertEqual(ObjectPath("/some/path"));
210   ObjectPath("/some/path").chompPrefix(ObjectPath("/")).assertEqual(ObjectPath("/some/path"));
211   ObjectPath("/some/path").chompPrefix(ObjectPath("/some/path")).assertEqual(ObjectPath("/"));
212   ObjectPath("/some/path").chompPrefix(ObjectPath("/some/path/extra")).assertEqual(ObjectPath("/some/path"));
213   ObjectPath("/").chompPrefix(ObjectPath("/some")).assertEqual(ObjectPath("/"));
214   ObjectPath("/").chompPrefix(ObjectPath("/")).assertEqual(ObjectPath("/"));
215 }
216 
217 unittest {
218   import dunit.toolkit;
219 
220   ObjectPath a = ObjectPath("/org/freedesktop");
221   a.assertEqual(ObjectPath("/org/freedesktop"));
222   a ~= ObjectPath("/UPower");
223   a.assertEqual(ObjectPath("/org/freedesktop/UPower"));
224   a ~= "Device";
225   a.assertEqual(ObjectPath("/org/freedesktop/UPower/Device"));
226   (a ~ "0").assertEqual(ObjectPath("/org/freedesktop/UPower/Device/0"));
227   a.assertEqual(ObjectPath("/org/freedesktop/UPower/Device"));
228 }
229 
230 /// Structure allowing typeless parameters
231 struct DBusAny {
232   /// DBus type of the value (never 'v'), see typeSig!T
233   int type;
234   /// Child signature for Arrays & Tuples
235   string signature;
236   /// If true, this value will get serialized as variant value, otherwise it is serialized like it wasn't in a DBusAny wrapper.
237   /// Same functionality as Variant!T but with dynamic types if true.
238   bool explicitVariant;
239 
240   union {
241     ///
242     ubyte uint8;
243     ///
244     short int16;
245     ///
246     ushort uint16;
247     ///
248     int int32;
249     ///
250     uint uint32;
251     ///
252     long int64;
253     ///
254     ulong uint64;
255     ///
256     double float64;
257     ///
258     string str;
259     ///
260     bool boolean;
261     ///
262     ObjectPath obj;
263     ///
264     DBusAny[] array;
265     ///
266     alias tuple = array;
267     ///
268     DictionaryEntry!(DBusAny, DBusAny)* entry;
269     ///
270     ubyte[] binaryData;
271     ///
272     FileDescriptor fd;
273   }
274 
275   /++
276     Manually creates a DBusAny object using a type, signature and explicit
277     variant specifier.
278 
279     Direct use of this constructor from user code should be avoided.
280    +/
281   this(int type, string signature, bool explicit) {
282     this.type = type;
283     this.signature = signature;
284     this.explicitVariant = explicit;
285   }
286 
287   /++
288     Automatically creates a DBusAny object with fitting parameters from a D
289     type or Variant!T.
290 
291     Pass a `Variant!T` to make this an explicit variant.
292    +/
293   this(T)(T value) {
294     static if (is(T == byte) || is(T == ubyte)) {
295       this(typeCode!ubyte, null, false);
296       uint8 = cast(ubyte) value;
297     } else static if (is(T == short)) {
298       this(typeCode!short, null, false);
299       int16 = cast(short) value;
300     } else static if (is(T == ushort)) {
301       this(typeCode!ushort, null, false);
302       uint16 = cast(ushort) value;
303     } else static if (is(T == int)) {
304       this(typeCode!int, null, false);
305       int32 = cast(int) value;
306     } else static if (is(T == FileDescriptor)) {
307       this(typeCode!FileDescriptor, null, false);
308       fd = cast(FileDescriptor) value;
309     } else static if (is(T == uint)) {
310       this(typeCode!uint, null, false);
311       uint32 = cast(uint) value;
312     } else static if (is(T == long)) {
313       this(typeCode!long, null, false);
314       int64 = cast(long) value;
315     } else static if (is(T == ulong)) {
316       this(typeCode!ulong, null, false);
317       uint64 = cast(ulong) value;
318     } else static if (is(T == double)) {
319       this(typeCode!double, null, false);
320       float64 = cast(double) value;
321     } else static if (isSomeString!T) {
322       this(typeCode!string, null, false);
323       str = value.to!string;
324     } else static if (is(T == bool)) {
325       this(typeCode!bool, null, false);
326       boolean = cast(bool) value;
327     } else static if (is(T == ObjectPath)) {
328       this(typeCode!ObjectPath, null, false);
329       obj = value;
330     } else static if (is(T == Variant!R, R)) {
331       static if (is(R == DBusAny)) {
332         type = value.data.type;
333         signature = value.data.signature;
334         explicitVariant = true;
335         if (type == 'a' || type == 'r') {
336           if (signature == ['y']) {
337             binaryData = value.data.binaryData;
338           } else {
339             array = value.data.array;
340           }
341         } else if (type == 's') {
342           str = value.data.str;
343         } else if (type == 'e') {
344           entry = value.data.entry;
345         } else {
346           uint64 = value.data.uint64;
347         }
348       } else {
349         this(value.data);
350         explicitVariant = true;
351       }
352     } else static if (is(T : DictionaryEntry!(K, V), K, V)) {
353       this('e', null, false);
354       entry = new DictionaryEntry!(DBusAny, DBusAny)();
355       static if (is(K == DBusAny)) {
356         entry.key = value.key;
357       } else {
358         entry.key = DBusAny(value.key);
359       }
360       static if (is(V == DBusAny)) {
361         entry.value = value.value;
362       } else {
363         entry.value = DBusAny(value.value);
364       }
365     } else static if (is(T == ubyte[]) || is(T == byte[])) {
366       this('a', ['y'], false);
367       binaryData = cast(ubyte[]) value;
368     } else static if (isInputRange!T) {
369       this.type = 'a';
370 
371       static assert(!is(ElementType!T == DBusAny),
372           "Array must consist of the same type, use Variant!DBusAny or DBusAny(tuple(...)) instead");
373 
374       static assert(.typeSig!(ElementType!T) != "y");
375 
376       this.signature = .typeSig!(ElementType!T);
377       this.explicitVariant = false;
378 
379       foreach (elem; value) {
380         array ~= DBusAny(elem);
381       }
382     } else static if (isTuple!T) {
383       this.type = 'r';
384       this.signature = ['('];
385       this.explicitVariant = false;
386 
387       foreach (index, R; value.Types) {
388         auto var = DBusAny(value[index]);
389         tuple ~= var;
390 
391         if (var.explicitVariant) {
392           this.signature ~= 'v';
393         } else {
394           if (var.type != 'r') {
395             this.signature ~= cast(char) var.type;
396           }
397 
398           if (var.type == 'a' || var.type == 'r') {
399             this.signature ~= var.signature;
400           }
401         }
402       }
403 
404       this.signature ~= ')';
405     } else static if (is(T == struct) && canDBus!T) {
406       this.type = 'r';
407       this.signature = ['('];
408       this.explicitVariant = false;
409       foreach (index, R; Fields!T) {
410         static if (isAllowedField!(value.tupleof[index])) {
411           auto var = DBusAny(value.tupleof[index]);
412           tuple ~= var;
413           if (var.explicitVariant)
414             this.signature ~= 'v';
415           else {
416             if (var.type != 'r')
417               this.signature ~= cast(char) var.type;
418             if (var.type == 'a' || var.type == 'r')
419               this.signature ~= var.signature;
420           }
421         }
422       }
423       this.signature ~= ')';
424     } else static if (isAssociativeArray!T) {
425       this(value.byDictionaryEntries);
426     } else {
427       static assert(false, T.stringof ~ " not convertible to a Variant");
428     }
429   }
430 
431   ///
432   string toString() const {
433     string valueStr;
434     switch (type) {
435     case typeCode!ubyte:
436       valueStr = uint8.to!string;
437       break;
438     case typeCode!short:
439       valueStr = int16.to!string;
440       break;
441     case typeCode!ushort:
442       valueStr = uint16.to!string;
443       break;
444     case typeCode!int:
445       valueStr = int32.to!string;
446       break;
447     case typeCode!uint:
448       valueStr = uint32.to!string;
449       break;
450     case typeCode!long:
451       valueStr = int64.to!string;
452       break;
453     case typeCode!ulong:
454       valueStr = uint64.to!string;
455       break;
456     case typeCode!double:
457       valueStr = float64.to!string;
458       break;
459     case typeCode!string:
460       valueStr = '"' ~ str ~ '"';
461       break;
462     case typeCode!ObjectPath:
463       valueStr = '"' ~ obj.to!string ~ '"';
464       break;
465     case typeCode!bool:
466       valueStr = boolean ? "true" : "false";
467       break;
468     case 'a':
469       import std.digest : toHexString;
470 
471       if (signature == ['y']) {
472         valueStr = "binary(" ~ binaryData.toHexString ~ ')';
473       } else {
474         valueStr = '[' ~ array.map!(a => a.toString).join(", ") ~ ']';
475       }
476 
477       break;
478     case 'r':
479       valueStr = '(' ~ tuple.map!(a => a.toString).join(", ") ~ ')';
480       break;
481     case 'e':
482       valueStr = entry.key.toString ~ ": " ~ entry.value.toString;
483       break;
484     case 'h':
485       valueStr = int32.to!string;
486       break;
487     default:
488       valueStr = "unknown";
489       break;
490     }
491 
492     return "DBusAny(" ~ cast(char) type ~ ", \"" ~ signature.idup ~ "\", " ~ (explicitVariant
493         ? "explicit" : "implicit") ~ ", " ~ valueStr ~ ")";
494   }
495 
496   /++
497     Get the value stored in the DBusAny object.
498 
499     Parameters:
500       T = The requested type. The currently stored value must match the
501         requested type exactly.
502 
503     Returns:
504       The current value of the DBusAny object.
505 
506     Throws:
507       TypeMismatchException if the DBus type of the current value of the
508       DBusAny object is not the same as the DBus type used to represent T.
509   +/
510   U get(U)() @property const
511       if (staticIndexOf!(Unqual!U, BasicTypes) >= 0) {
512     alias T = Unqual!U;
513     enforce(type == typeCode!T, new TypeMismatchException(
514         "Cannot get a " ~ T.stringof ~ " from a DBusAny with" ~ " a value of DBus type '" ~ typeSig ~ "'.",
515         typeCode!T, type));
516 
517     static if (is(T == FileDescriptor)) {
518       return cast(U) fd;
519     } else  static if (isIntegral!T) {
520       enum memberName = (isUnsigned!T ? "uint" : "int") ~ (T.sizeof * 8).to!string;
521       return cast(U) __traits(getMember, this, memberName);
522     } else static if (is(T == double)) {
523       return cast(U) float64;
524     } else static if (is(T == string)) {
525       return cast(U) str;
526     } else static if (is(T == InterfaceName) || is(T == BusName)) {
527       return cast(U) str;
528     } else static if (is(T == ObjectPath)) {
529       return cast(U) obj;
530     } else static if (is(T == bool)) {
531       return cast(U) boolean;
532     } else {
533       static assert(false);
534     }
535   }
536 
537   /// ditto
538   T get(T)() @property const
539       if (is(T == const(DBusAny)[])) {
540     enforce((type == 'a' && signature != "y") || type == 'r', new TypeMismatchException(
541         "Cannot get a " ~ T.stringof ~ " from a DBusAny with" ~ " a value of DBus type '" ~ this.typeSig ~ "'.",
542         'a', type));
543 
544     return array;
545   }
546 
547   /// ditto
548   T get(T)() @property const
549       if (is(T == const(ubyte)[])) {
550     enforce(type == 'a' && signature == "y", new TypeMismatchException(
551         "Cannot get a " ~ T.stringof ~ " from a DBusAny with" ~ " a value of DBus type '" ~ this.typeSig ~ "'.",
552         'a', type));
553 
554     return binaryData;
555   }
556 
557   /// If the value is an array of DictionaryEntries this will return a HashMap
558   deprecated("Please use to!(V[K])") DBusAny[DBusAny] toAA() {
559     enforce(type == 'a' && signature && signature[0] == '{');
560     DBusAny[DBusAny] aa;
561 
562     foreach (val; array) {
563       enforce(val.type == 'e');
564       aa[val.entry.key] = val.entry.value;
565     }
566 
567     return aa;
568   }
569 
570   /++
571     Get the DBus type signature of the value stored in the DBusAny object.
572 
573     Returns:
574       The type signature of the value stored in this DBusAny object.
575    +/
576   string typeSig() @property const pure nothrow @safe {
577     if (type == 'a') {
578       return "a" ~ signature;
579     } else if (type == 'r') {
580       return signature;
581     } else if (type == 'e') {
582       return () @trusted{
583         return "{" ~ entry.key.signature ~ entry.value.signature ~ "}";
584       }();
585     } else {
586       return [cast(char) type];
587     }
588   }
589 
590   /++
591     Converts a basic type, a tuple or an array to the D type with type checking.
592 
593     Tuples can be converted to an array of DBusAny, but not to any other array.
594    +/
595   T to(T)() @property const pure {
596     // Just use `get` if possible
597     static if (canDBus!T && __traits(compiles, get!T)) {
598       if (this.typeSig == .typeSig!T)
599         return get!T;
600     }
601 
602     // If we get here, we need some type conversion
603     static if (is(T == Variant!R, R)) {
604       static if (is(R == DBusAny)) {
605         auto v = to!R;
606         v.explicitVariant = false;
607         return Variant!R(v);
608       } else {
609         return Variant!R(to!R);
610       }
611     } else static if (is(T == DBusAny)) {
612       return this;
613     } else {
614       // In here are all static if blocks that may fall through to the throw
615       // statement at the bottom of this block.
616 
617       static if (is(T == DictionaryEntry!(K, V), K, V)) {
618         if (type == 'e') {
619           static if (is(T == typeof(entry))) {
620             return entry;
621           } else {
622             return DictionaryEntry(entry.key.to!K, entry.value.to!V);
623           }
624         }
625       } else static if (isAssociativeArray!T) {
626         if (type == 'a' && (!array.length || array[0].type == 'e')) {
627           alias K = Unqual!(KeyType!T);
628           alias V = Unqual!(ValueType!T);
629           V[K] ret;
630 
631           foreach (pair; array) {
632             assert(pair.type == 'e');
633             ret[pair.entry.key.to!K] = pair.entry.value.to!V;
634           }
635 
636           return cast(T) ret;
637         }
638       } else static if (isDynamicArray!T && !isSomeString!T) {
639         alias E = Unqual!(ElementType!T);
640 
641         if (typeSig == "ay") {
642           auto data = get!(const(ubyte)[]);
643           static if (is(E == ubyte) || is(E == byte)) {
644             return cast(T) data.dup;
645           } else {
646             return cast(T) data.map!(elem => elem.to!E).array;
647           }
648         } else if (type == 'a' || (type == 'r' && is(E == DBusAny))) {
649           return cast(T) get!(const(DBusAny)[]).map!(elem => elem.to!E).array;
650         }
651       } else static if (isTuple!T) {
652         if (type == 'r') {
653           T ret;
654 
655           foreach (i, T; ret.Types) {
656             ret[i] = tuple[i].to!T;
657           }
658 
659           return ret;
660         }
661       } else static if (is(T == struct) && canDBus!T) {
662         if (type == 'r') {
663           T ret;
664           size_t j;
665 
666           foreach (i, F; Fields!T) {
667             static if (isAllowedField!(ret.tupleof[i])) {
668               ret.tupleof[i] = tuple[j++].to!F;
669             }
670           }
671 
672           return ret;
673         }
674       } else {
675         alias isPreciselyConvertible = ApplyRight!(isImplicitlyConvertible, T);
676 
677         template isUnpreciselyConvertible(S) {
678           enum isUnpreciselyConvertible = !isPreciselyConvertible!S
679               && __traits(compiles, get!S.to!T);
680         }
681 
682         // Try to be precise
683         foreach (B; Filter!(isPreciselyConvertible, BasicTypes)) {
684           if (type == typeCode!B)
685             return get!B;
686         }
687 
688         // Try to convert
689         foreach (B; Filter!(isUnpreciselyConvertible, BasicTypes)) {
690           if (type == typeCode!B)
691             return get!B.to!T;
692         }
693       }
694 
695       throw new ConvException("Cannot convert from DBus type '" ~ this.typeSig ~ "' to "
696           ~ T.stringof);
697     }
698   }
699 
700   bool opEquals(ref in DBusAny b) const {
701     if (b.type != type || b.explicitVariant != explicitVariant) {
702       return false;
703     }
704 
705     if ((type == 'a' || type == 'r') && b.signature != signature) {
706       return false;
707     }
708 
709     if (type == 'a' && signature == ['y']) {
710       return binaryData == b.binaryData;
711     }
712 
713     if (type == 'a') {
714       return array == b.array;
715     } else if (type == 'r') {
716       return tuple == b.tuple;
717     } else if (type == 's') {
718       return str == b.str;
719     } else if (type == 'o') {
720       return obj == b.obj;
721     } else if (type == 'e') {
722       return entry == b.entry || (entry && b.entry && *entry == *b.entry);
723     } else {
724       return uint64 == b.uint64;
725     }
726   }
727 
728   /// Returns: true if this variant is an integer.
729   bool typeIsIntegral() const @property {
730     return type.dbusIsIntegral;
731   }
732 
733   /// Returns: true if this variant is a floating point value.
734   bool typeIsFloating() const @property {
735     return type.dbusIsFloating;
736   }
737 
738   /// Returns: true if this variant is an integer or a floating point value.
739   bool typeIsNumeric() const @property {
740     return type.dbusIsNumeric;
741   }
742 }
743 
744 unittest {
745   import dunit.toolkit;
746 
747   DBusAny set(string member, T)(DBusAny v, T value) {
748     mixin("v." ~ member ~ " = value;");
749     return v;
750   }
751 
752   void test(bool testGet = false, T)(T value, DBusAny b) {
753     assertEqual(DBusAny(value), b);
754     assertEqual(b.to!T, value);
755     static if (testGet)
756       assertEqual(b.get!T, value);
757     b.toString();
758   }
759 
760   test!true(cast(ubyte) 184, set!"uint8"(DBusAny('y', null, false), cast(ubyte) 184));
761   test(cast(byte) 184, set!"uint8"(DBusAny('y', null, false), cast(byte) 184));
762   test!true(cast(short) 184, set!"int16"(DBusAny('n', null, false), cast(short) 184));
763   test!true(cast(ushort) 184, set!"uint16"(DBusAny('q', null, false), cast(ushort) 184));
764   test!true(cast(int) 184, set!"int32"(DBusAny('i', null, false), cast(int) 184));
765   test!true(cast(uint) 184, set!"uint32"(DBusAny('u', null, false), cast(uint) 184));
766   test!true(cast(long) 184, set!"int64"(DBusAny('x', null, false), cast(long) 184));
767   test!true(cast(ulong) 184, set!"uint64"(DBusAny('t', null, false), cast(ulong) 184));
768   test!true(1.84, set!"float64"(DBusAny('d', null, false), 1.84));
769   test!true(true, set!"boolean"(DBusAny('b', null, false), true));
770   test!true("abc", set!"str"(DBusAny('s', null, false), "abc"));
771   test!true(ObjectPath("/foo/Bar"), set!"obj"(DBusAny('o', null, false), ObjectPath("/foo/Bar")));
772   test(cast(ubyte[])[1, 2, 3], set!"binaryData"(DBusAny('a', ['y'], false),
773       cast(ubyte[])[1, 2, 3]));
774 
775   test(variant(cast(ubyte) 184), set!"uint8"(DBusAny('y', null, true), cast(ubyte) 184));
776   test(variant(cast(short) 184), set!"int16"(DBusAny('n', null, true), cast(short) 184));
777   test(variant(cast(ushort) 184), set!"uint16"(DBusAny('q', null, true), cast(ushort) 184));
778   test(variant(cast(int) 184), set!"int32"(DBusAny('i', null, true), cast(int) 184));
779   test(variant(cast(FileDescriptor) 184), set!"uint32"(DBusAny('h', null, true), cast(FileDescriptor) 184));
780   test(variant(cast(FileDescriptor) FileDescriptor.none), set!"uint32"(DBusAny('h', null, true), cast(FileDescriptor) FileDescriptor.none));
781   test(variant(cast(uint) 184), set!"uint32"(DBusAny('u', null, true), cast(uint) 184));
782   test(variant(cast(long) 184), set!"int64"(DBusAny('x', null, true), cast(long) 184));
783   test(variant(cast(ulong) 184), set!"uint64"(DBusAny('t', null, true), cast(ulong) 184));
784   test(variant(1.84), set!"float64"(DBusAny('d', null, true), 1.84));
785   test(variant(true), set!"boolean"(DBusAny('b', null, true), true));
786   test(variant("abc"), set!"str"(DBusAny('s', null, true), "abc"));
787   test(variant(ObjectPath("/foo/Bar")), set!"obj"(DBusAny('o', null, true),
788       ObjectPath("/foo/Bar")));
789   test(variant(cast(ubyte[])[1, 2, 3]), set!"binaryData"(DBusAny('a', ['y'],
790       true), cast(ubyte[])[1, 2, 3]));
791 
792   test(variant(DBusAny(5)), set!"int32"(DBusAny('i', null, true), 5));
793 
794   test([1, 2, 3], set!"array"(DBusAny('a', ['i'], false), [DBusAny(1), DBusAny(2), DBusAny(3)]));
795   test(variant([1, 2, 3]), set!"array"(DBusAny('a', ['i'], true), [DBusAny(1),
796       DBusAny(2), DBusAny(3)]));
797 
798   test(tuple("a", 4, [1, 2]), set!"tuple"(DBusAny('r', "(siai)".dup, false),
799       [DBusAny("a"), DBusAny(4), DBusAny([1, 2])]));
800   test(tuple("a", variant(4), variant([1, 2])), set!"tuple"(DBusAny('r',
801       "(svv)", false), [DBusAny("a"), DBusAny(variant(4)), DBusAny(variant([1, 2]))]));
802 
803   test(["a" : "b"], set!"array"(DBusAny('a', "{ss}", false),
804       [DBusAny(DictionaryEntry!(DBusAny, DBusAny)(DBusAny("a"), DBusAny("b")))]));
805   test([variant("a") : 4], set!"array"(DBusAny('a', "{vi}", false),
806       [DBusAny(DictionaryEntry!(DBusAny, DBusAny)(DBusAny(variant("a")), DBusAny(4)))]));
807 }
808 
809 /// Marks the data as variant on serialization
810 struct Variant(T) {
811   ///
812   T data;
813 }
814 
815 Variant!T variant(T)(T data) {
816   return Variant!T(data);
817 }
818 
819 enum MessageType {
820   Invalid = 0,
821   Call,
822   Return,
823   Error,
824   Signal
825 }
826 
827 /// Represents a message in the dbus system. Use the constructor to 
828 struct Message {
829   DBusMessage* msg;
830 
831   deprecated("Use the constructor taking a BusName, ObjectPath and InterfaceName instead")
832   this(string dest, string path, string iface, string method) {
833     msg = dbus_message_new_method_call(dest.toStringz(), path.toStringz(),
834         iface.toStringz(), method.toStringz());
835   }
836 
837   /// Prepares a new method call to an "instance" "object" "interface" "method".
838   this(BusName dest, ObjectPath path, InterfaceName iface, string method) {
839     msg = dbus_message_new_method_call(dest.toStringz(),
840         path.value.toStringz(), iface.toStringz(), method.toStringz());
841   }
842 
843   /// Wraps an existing low level message object.
844   this(DBusMessage* m) {
845     msg = m;
846   }
847 
848   this(this) {
849     dbus_message_ref(msg);
850   }
851 
852   ~this() {
853     if (msg) {
854       dbus_message_unref(msg);
855       msg = null;
856     }
857   }
858 
859   /// Creates a new iterator and puts in the arguments for calling a method.
860   void build(TS...)(TS args)
861       if (allCanDBus!TS) {
862     DBusMessageIter iter;
863     dbus_message_iter_init_append(msg, &iter);
864     buildIter(&iter, args);
865   }
866 
867   /**
868      Reads the first argument of the message.
869      Note that this creates a new iterator every time so calling it multiple times will always
870      read the first argument. This is suitable for single item returns.
871      To read multiple arguments use readTuple.
872   */
873   T read(T)()
874       if (canDBus!T) {
875     DBusMessageIter iter;
876     dbus_message_iter_init(msg, &iter);
877     return readIter!T(&iter);
878   }
879 
880   alias read to;
881 
882   Tup readTuple(Tup)()
883       if (isTuple!Tup && allCanDBus!(Tup.Types)) {
884     DBusMessageIter iter;
885     dbus_message_iter_init(msg, &iter);
886     Tup ret;
887     readIterTuple(&iter, ret);
888     return ret;
889   }
890 
891   Message createReturn() {
892     return Message(dbus_message_new_method_return(msg));
893   }
894 
895   MessageType type() {
896     return cast(MessageType) dbus_message_get_type(msg);
897   }
898 
899   bool isCall() {
900     return type() == MessageType.Call;
901   }
902 
903   // Various string members
904   // TODO: make a mixin to avoid this copy-paste
905   string signature() {
906     const(char)* cStr = dbus_message_get_signature(msg);
907     assert(cStr != null);
908     return cStr.fromStringz().idup;
909   }
910 
911   ObjectPath path() {
912     const(char)* cStr = dbus_message_get_path(msg);
913     assert(cStr != null);
914     return ObjectPath(cStr.fromStringz().idup);
915   }
916 
917   InterfaceName iface() {
918     const(char)* cStr = dbus_message_get_interface(msg);
919     assert(cStr != null);
920     return interfaceName(cStr.fromStringz().idup);
921   }
922 
923   string member() {
924     const(char)* cStr = dbus_message_get_member(msg);
925     assert(cStr != null);
926     return cStr.fromStringz().idup;
927   }
928 
929   string sender() {
930     const(char)* cStr = dbus_message_get_sender(msg);
931     assert(cStr != null);
932     return cStr.fromStringz().idup;
933   }
934 }
935 
936 ///
937 unittest {
938   import dunit.toolkit;
939 
940   auto msg = Message(busName("org.example.test"), ObjectPath("/test"), interfaceName("org.example.testing"), "testMethod");
941   msg.path().assertEqual("/test");
942 }
943 
944 struct Connection {
945   DBusConnection* conn;
946   this(DBusConnection* connection) {
947     conn = connection;
948   }
949 
950   this(this) {
951     dbus_connection_ref(conn);
952   }
953 
954   ~this() {
955     if (conn) {
956       dbus_connection_unref(conn);
957       conn = null;
958     }
959   }
960 
961   void close() {
962     if (conn) {
963       dbus_connection_close(conn);
964     }
965   }
966 
967   void send(Message msg) {
968     dbus_connection_send(conn, msg.msg, null);
969   }
970 
971   void sendBlocking(Message msg) {
972     send(msg);
973     dbus_connection_flush(conn);
974   }
975 
976   Message sendWithReplyBlocking(Message msg, int timeout = -1) {
977     DBusMessage* dbusMsg = msg.msg;
978     dbus_message_ref(dbusMsg);
979     DBusMessage* reply = wrapErrors((err) {
980       auto ret = dbus_connection_send_with_reply_and_block(conn, dbusMsg, timeout, err);
981       dbus_message_unref(dbusMsg);
982       return ret;
983     });
984     return Message(reply);
985   }
986 
987   Message sendWithReplyBlocking(Message msg, Duration timeout) {
988     return sendWithReplyBlocking(msg, timeout.total!"msecs"().to!int);
989   }
990 }
991 
992 unittest {
993   import dunit.toolkit;
994   import ddbus.attributes : dbusMarshaling, MarshalingFlag;
995 
996   struct S1 {
997     private int a;
998     private @(Yes.DBusMarshal) double b;
999     string s;
1000   }
1001 
1002   @dbusMarshaling(MarshalingFlag.manualOnly)
1003   struct S2 {
1004     int h, i;
1005     @(Yes.DBusMarshal) int j, k;
1006     int* p;
1007   }
1008 
1009   @dbusMarshaling(MarshalingFlag.includePrivateFields)
1010   struct S3 {
1011     private Variant!int c;
1012     string d;
1013     S1 e;
1014     S2 f;
1015     @(No.DBusMarshal) uint g;
1016   }
1017 
1018   Message msg = Message(busName("org.example.wow"), ObjectPath("/wut"), interfaceName("org.test.iface"), "meth3");
1019 
1020   __gshared int dummy;
1021 
1022   enum testStruct = S3(variant(5), "blah", S1(-7, 63.5, "test"), S2(84, -123,
1023         78, 432, &dummy), 16);
1024 
1025   // Non-marshaled fields should appear as freshly initialized
1026   enum expectedResult = S3(variant(5), "blah", S1(int.init, 63.5, "test"),
1027         S2(int.init, int.init, 78, 432, null), uint.init);
1028 
1029   // Test struct conversion in building/reading messages
1030   msg.build(testStruct);
1031   msg.read!S3().assertEqual(expectedResult);
1032 
1033   // Test struct conversion in DBusAny
1034   DBusAny(testStruct).to!S3.assertEqual(expectedResult);
1035 }
1036 
1037 Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) {
1038   DBusConnection* conn = wrapErrors((err) { return dbus_bus_get(bus, err); });
1039   return Connection(conn);
1040 }
1041 
1042 unittest {
1043   import dunit.toolkit;
1044 
1045   // This test will only pass if DBus is installed.
1046   Connection conn = connectToBus();
1047   conn.conn.assertTruthy();
1048   // We can only count on no system bus on OSX
1049   version (OSX) {
1050     connectToBus(DBusBusType.DBUS_BUS_SYSTEM).assertThrow!DBusException();
1051   }
1052 }