1 module ddbus.conv;
2 
3 import ddbus.attributes : isAllowedField;
4 import ddbus.c_lib;
5 import ddbus.exception : InvalidValueException, TypeMismatchException;
6 import ddbus.util;
7 import ddbus.thin;
8 
9 import std.exception : enforce;
10 import std.meta : allSatisfy;
11 import std..string;
12 import std.typecons;
13 import std.range;
14 import std.traits;
15 import std.variant : VariantN;
16 
17 void buildIter(TS...)(DBusMessageIter* iter, TS args)
18     if (allCanDBus!TS) {
19   foreach (index, arg; args) {
20     alias T = Unqual!(TS[index]);
21     static if (is(T == string) || is(T == InterfaceName) || is(T == BusName)) {
22       immutable(char)* cStr = arg.toStringz();
23       dbus_message_iter_append_basic(iter, typeCode!T, &cStr);
24     } else static if (is(T == ObjectPath)) {
25       immutable(char)* cStr = arg.toString().toStringz();
26       dbus_message_iter_append_basic(iter, typeCode!T, &cStr);
27     } else static if (is(T == bool)) {
28       dbus_bool_t longerBool = arg; // dbus bools are ints
29       dbus_message_iter_append_basic(iter, typeCode!T, &longerBool);
30     } else static if (isTuple!T) {
31       DBusMessageIter sub;
32       dbus_message_iter_open_container(iter, 'r', null, &sub);
33       buildIter(&sub, arg.expand);
34       dbus_message_iter_close_container(iter, &sub);
35     } else static if (isInputRange!T) {
36       DBusMessageIter sub;
37       const(char)* subSig = (typeSig!(ElementType!T)()).toStringz();
38       dbus_message_iter_open_container(iter, 'a', subSig, &sub);
39       foreach (x; arg) {
40         static if (isInstanceOf!(DictionaryEntry, typeof(x))) {
41           DBusMessageIter entry;
42           dbus_message_iter_open_container(&sub, 'e', null, &entry);
43           buildIter(&entry, x.key);
44           buildIter(&entry, x.value);
45           dbus_message_iter_close_container(&sub, &entry);
46         } else {
47           buildIter(&sub, x);
48         }
49       }
50       dbus_message_iter_close_container(iter, &sub);
51     } else static if (isAssociativeArray!T) {
52       DBusMessageIter sub;
53       const(char)* subSig = typeSig!T[1 .. $].toStringz();
54       dbus_message_iter_open_container(iter, 'a', subSig, &sub);
55       foreach (k, v; arg) {
56         DBusMessageIter entry;
57         dbus_message_iter_open_container(&sub, 'e', null, &entry);
58         buildIter(&entry, k);
59         buildIter(&entry, v);
60         dbus_message_iter_close_container(&sub, &entry);
61       }
62       dbus_message_iter_close_container(iter, &sub);
63     } else static if (isInstanceOf!(VariantN, T)) {
64       enforce(arg.hasValue, new InvalidValueException(arg, "dbus:" ~ cast(char) typeCode!T));
65 
66       DBusMessageIter sub;
67       foreach (AT; T.AllowedTypes) {
68         if (arg.peek!AT) {
69           dbus_message_iter_open_container(iter, 'v', typeSig!AT.ptr, &sub);
70           buildIter(&sub, arg.get!AT);
71           dbus_message_iter_close_container(iter, &sub);
72           break;
73         }
74       }
75     } else static if (is(T == DBusAny) || is(T == Variant!DBusAny)) {
76       static if (is(T == Variant!DBusAny)) {
77         auto val = arg.data;
78         val.explicitVariant = true;
79       } else {
80         auto val = arg;
81       }
82       DBusMessageIter subStore;
83       DBusMessageIter* sub = &subStore;
84       const(char)[] sig = [cast(char) val.type];
85       if (val.type == 'a') {
86         sig ~= val.signature;
87       } else if (val.type == 'r') {
88         sig = val.signature;
89       }
90 
91       sig ~= '\0';
92 
93       if (!val.explicitVariant) {
94         sub = iter;
95       } else {
96         dbus_message_iter_open_container(iter, 'v', sig.ptr, sub);
97       }
98 
99       if (val.type == 's') {
100         buildIter(sub, val.str);
101       } else if (val.type == 'o') {
102         buildIter(sub, val.obj);
103       } else if (val.type == 'b') {
104         buildIter(sub, val.boolean);
105       } else if (dbus_type_is_basic(val.type)) {
106         dbus_message_iter_append_basic(sub, val.type, &val.int64);
107       } else if (val.type == 'a') {
108         DBusMessageIter arr;
109         dbus_message_iter_open_container(sub, 'a', sig[1 .. $].ptr, &arr);
110 
111         if (val.signature == ['y']) {
112           foreach (item; val.binaryData) {
113             dbus_message_iter_append_basic(&arr, 'y', &item);
114           }
115         } else {
116           foreach (item; val.array) {
117             buildIter(&arr, item);
118           }
119         }
120 
121         dbus_message_iter_close_container(sub, &arr);
122       } else if (val.type == 'r') {
123         DBusMessageIter arr;
124         dbus_message_iter_open_container(sub, 'r', null, &arr);
125 
126         foreach (item; val.tuple) {
127           buildIter(&arr, item);
128         }
129 
130         dbus_message_iter_close_container(sub, &arr);
131       } else if (val.type == 'e') {
132         DBusMessageIter entry;
133         dbus_message_iter_open_container(sub, 'e', null, &entry);
134         buildIter(&entry, val.entry.key);
135         buildIter(&entry, val.entry.value);
136         dbus_message_iter_close_container(sub, &entry);
137       }
138 
139       if (val.explicitVariant) {
140         dbus_message_iter_close_container(iter, sub);
141       }
142     } else static if (isInstanceOf!(Variant, T)) {
143       DBusMessageIter sub;
144       const(char)* subSig = typeSig!(VariantType!T).toStringz();
145       dbus_message_iter_open_container(iter, 'v', subSig, &sub);
146       buildIter(&sub, arg.data);
147       dbus_message_iter_close_container(iter, &sub);
148     } else static if (is(T == struct)) {
149       DBusMessageIter sub;
150       dbus_message_iter_open_container(iter, 'r', null, &sub);
151 
152       // Following failed because of missing 'this' for members of arg.
153       // That sucks. It worked without Filter.
154       // Reported: https://issues.dlang.org/show_bug.cgi?id=17692
155       //    buildIter(&sub, Filter!(isAllowedField, arg.tupleof));
156 
157       // Using foreach to work around the issue
158       foreach (i, member; arg.tupleof) {
159         // Ugly, but we need to use tupleof again in the condition, because when
160         // we use `member`, isAllowedField will fail because it'll find this
161         // nice `buildIter` function instead of T when it looks up the parent
162         // scope of its argument.
163         static if (isAllowedField!(arg.tupleof[i]))
164           buildIter(&sub, member);
165       }
166 
167       dbus_message_iter_close_container(iter, &sub);
168     } else static if (basicDBus!T) {
169       dbus_message_iter_append_basic(iter, typeCode!T, &arg);
170     }
171   }
172 }
173 
174 T readIter(T)(DBusMessageIter* iter)
175     if (is(T == enum) && !is(Unqual!T == InterfaceName) && !is(Unqual!T == BusName) && !is(Unqual!T == FileDescriptor)) {
176   import std.algorithm.searching : canFind;
177 
178   alias B = Unqual!(OriginalType!T);
179 
180   B value = readIter!B(iter);
181   enforce(only(EnumMembers!T).canFind(value), new InvalidValueException(value, T.stringof));
182   return cast(T) value;
183 }
184 
185 T readIter(T)(DBusMessageIter* iter)
186     if (isInstanceOf!(BitFlags, T)) {
187   import std.algorithm.iteration : fold;
188 
189   alias TemplateArgsOf!T[0] E;
190   alias OriginalType!E B;
191 
192   B mask = only(EnumMembers!E).fold!((a, b) => cast(B)(a | b));
193 
194   B value = readIter!B(iter);
195   enforce(!(value & ~mask), new InvalidValueException(value, T.stringof));
196 
197   return T(cast(E) value);
198 }
199 
200 U readIter(U)(DBusMessageIter* iter)
201     if (!(is(U == enum) && !is(Unqual!U == InterfaceName) && !is(Unqual!U == BusName) && !is(U == FileDescriptor)) 
202     && !isInstanceOf!(BitFlags, U) && canDBus!U) {
203   alias T = Unqual!U;
204 
205   auto argType = dbus_message_iter_get_arg_type(iter);
206   T ret;
207 
208   static if (!isInstanceOf!(Variant, T) || is(T == Variant!DBusAny)) {
209     if (argType == 'v') {
210       DBusMessageIter sub;
211       dbus_message_iter_recurse(iter, &sub);
212       static if (is(T == Variant!DBusAny)) {
213         ret = variant(readIter!DBusAny(&sub));
214       } else {
215         ret = readIter!T(&sub);
216         static if (is(T == DBusAny))
217           ret.explicitVariant = true;
218       }
219       dbus_message_iter_next(iter);
220       return cast(U) ret;
221     }
222   }
223 
224   static if (!is(T == DBusAny) && !is(T == Variant!DBusAny) && !isInstanceOf!(VariantN, T)) {
225     enforce(argType == typeCode!T(), new TypeMismatchException(typeCode!T(), argType));
226   }
227 
228   static if (is(T == string) || is(T == InterfaceName) || is(T == BusName) || is(T == ObjectPath)) {
229     const(char)* cStr;
230     dbus_message_iter_get_basic(iter, &cStr);
231     string str = cStr.fromStringz().idup; // copy string
232     static if (is(T == string) || is(T : InterfaceName) || is(T : BusName)) {
233       ret = cast(T)str;
234     } else {
235       ret = ObjectPath(str);
236     }
237   } else static if (is(T == bool)) {
238     dbus_bool_t longerBool;
239     dbus_message_iter_get_basic(iter, &longerBool);
240     ret = cast(bool) longerBool;
241   } else static if (isTuple!T) {
242     DBusMessageIter sub;
243     dbus_message_iter_recurse(iter, &sub);
244     readIterTuple!T(&sub, ret);
245   } else static if (is(T t : S[], S)) {
246     assert(dbus_message_iter_get_element_type(iter) == typeCode!S);
247 
248     DBusMessageIter sub;
249     dbus_message_iter_recurse(iter, &sub);
250 
251     while (dbus_message_iter_get_arg_type(&sub) != 0) {
252       static if (is(S == DictionaryEntry!(K, V), K, V)) {
253         DBusMessageIter entry;
254         dbus_message_iter_recurse(&sub, &entry);
255         ret ~= S(readIter!K(&entry), readIter!V(&entry));
256         dbus_message_iter_next(&sub);
257       } else {
258         ret ~= readIter!S(&sub);
259       }
260     }
261   } else static if (isInstanceOf!(Variant, T)) {
262     DBusMessageIter sub;
263     dbus_message_iter_recurse(iter, &sub);
264     ret.data = readIter!(VariantType!T)(&sub);
265   } else static if (isInstanceOf!(VariantN, T)) {
266     scope const(char)[] argSig = dbus_message_iter_get_signature(iter).fromStringz();
267     scope (exit)
268       dbus_free(cast(void*) argSig.ptr);
269 
270     foreach (AT; T.AllowedTypes) {
271       // We have to compare the full signature here, not just the typecode.
272       // Otherwise, in case of container types, we might select the wrong one.
273       // We would then be calling an incorrect instance of readIter, which would
274       // probably throw a TypeMismatchException.
275       if (typeSig!AT == argSig) {
276         ret = readIter!AT(iter);
277         break;
278       }
279     }
280 
281     // If no value is in ret, apparently none of the types matched.
282     enforce(ret.hasValue, new TypeMismatchException(typeCode!T, argType));
283   } else static if (isAssociativeArray!T) {
284     DBusMessageIter sub;
285     dbus_message_iter_recurse(iter, &sub);
286 
287     while (dbus_message_iter_get_arg_type(&sub) != 0) {
288       DBusMessageIter entry;
289       dbus_message_iter_recurse(&sub, &entry);
290       auto k = readIter!(KeyType!T)(&entry);
291       auto v = readIter!(ValueType!T)(&entry);
292       ret[k] = v;
293       dbus_message_iter_next(&sub);
294     }
295   } else static if (is(T == DBusAny)) {
296     ret.type = argType;
297     ret.explicitVariant = false;
298 
299     if (ret.type == 's') {
300       ret.str = readIter!string(iter);
301       return cast(U) ret;
302     } else if (ret.type == 'o') {
303       ret.obj = readIter!ObjectPath(iter);
304       return cast(U) ret;
305     } else if (ret.type == 'b') {
306       ret.boolean = readIter!bool(iter);
307       return cast(U) ret;
308     } else if (dbus_type_is_basic(ret.type)) {
309       dbus_message_iter_get_basic(iter, &ret.int64);
310     } else if (ret.type == 'a') {
311       DBusMessageIter sub;
312       dbus_message_iter_recurse(iter, &sub);
313       auto sig = dbus_message_iter_get_signature(&sub);
314       ret.signature = sig.fromStringz.dup;
315       dbus_free(sig);
316       if (ret.signature == ['y']) {
317         while (dbus_message_iter_get_arg_type(&sub) != 0) {
318           ubyte b;
319           assert(dbus_message_iter_get_arg_type(&sub) == 'y');
320           dbus_message_iter_get_basic(&sub, &b);
321           dbus_message_iter_next(&sub);
322           ret.binaryData ~= b;
323         }
324       } else {
325         while (dbus_message_iter_get_arg_type(&sub) != 0) {
326           ret.array ~= readIter!DBusAny(&sub);
327         }
328       }
329     } else if (ret.type == 'r') {
330       auto sig = dbus_message_iter_get_signature(iter);
331       ret.signature = sig.fromStringz.dup;
332       dbus_free(sig);
333 
334       DBusMessageIter sub;
335       dbus_message_iter_recurse(iter, &sub);
336 
337       while (dbus_message_iter_get_arg_type(&sub) != 0) {
338         ret.tuple ~= readIter!DBusAny(&sub);
339       }
340     } else if (ret.type == 'e') {
341       DBusMessageIter sub;
342       dbus_message_iter_recurse(iter, &sub);
343 
344       ret.entry = new DictionaryEntry!(DBusAny, DBusAny);
345       ret.entry.key = readIter!DBusAny(&sub);
346       ret.entry.value = readIter!DBusAny(&sub);
347     }
348   } else static if (is(T == struct)) {
349     DBusMessageIter sub;
350     dbus_message_iter_recurse(iter, &sub);
351     readIterStruct!T(&sub, ret);
352   } else static if (basicDBus!T) {
353     dbus_message_iter_get_basic(iter, &ret);
354   }
355 
356   dbus_message_iter_next(iter);
357   return cast(U) ret;
358 }
359 
360 void readIterTuple(Tup)(DBusMessageIter* iter, ref Tup tuple)
361     if (isTuple!Tup && allCanDBus!(Tup.Types)) {
362   foreach (index, T; Tup.Types) {
363     tuple[index] = readIter!T(iter);
364   }
365 }
366 
367 void readIterStruct(S)(DBusMessageIter* iter, ref S s)
368     if (is(S == struct) && canDBus!S) {
369   foreach (index, T; Fields!S) {
370     static if (isAllowedField!(s.tupleof[index])) {
371       s.tupleof[index] = readIter!T(iter);
372     }
373   }
374 }
375 
376 unittest {
377   import dunit.toolkit;
378   import ddbus.thin;
379 
380   Variant!T var(T)(T data) {
381     return Variant!T(data);
382   }
383 
384   Message msg = Message(busName("org.example.wow"), ObjectPath("/wut"), interfaceName("org.test.iface"), "meth");
385   bool[] emptyB;
386   string[string] map;
387   map["hello"] = "world";
388   DBusAny anyVar = DBusAny(cast(ulong) 1561);
389   anyVar.type.assertEqual('t');
390   anyVar.uint64.assertEqual(1561);
391   anyVar.explicitVariant.assertEqual(false);
392   auto tupleMember = DBusAny(tuple(Variant!int(45), Variant!ushort(5), 32,
393       [1, 2], tuple(variant(4), 5), map));
394   Variant!DBusAny complexVar = variant(DBusAny([
395         "hello world": variant(DBusAny(1337)),
396         "array value": variant(DBusAny([42, 64])),
397         "tuple value": variant(tupleMember),
398         "optimized binary data": variant(DBusAny(cast(ubyte[])[1, 2, 3, 4, 5, 6]))
399       ]));
400   complexVar.data.type.assertEqual('a');
401   complexVar.data.signature.assertEqual("{sv}".dup);
402   tupleMember.signature.assertEqual("(vviai(vi)a{ss})");
403 
404   auto args = tuple(5, true, "wow", interfaceName("methodName"), var(5.9), [6, 5], tuple(6.2, 4, [["lol"]],
405       emptyB, var([4, 2])), map, anyVar, complexVar);
406   msg.build(args.expand);
407   msg.signature().assertEqual("ibssvai(diaasabv)a{ss}tv");
408 
409   msg.read!string().assertThrow!TypeMismatchException();
410   msg.readTuple!(Tuple!(int, bool, double)).assertThrow!TypeMismatchException();
411   msg.readTuple!(Tuple!(int, bool, string, InterfaceName, double))
412     .assertEqual(tuple(5, true, "wow", interfaceName("methodName"), 5.9));
413 
414   msg.readTuple!(typeof(args))().assertEqual(args);
415   DBusMessageIter iter;
416   dbus_message_iter_init(msg.msg, &iter);
417   readIter!int(&iter).assertEqual(5);
418   readIter!bool(&iter).assertEqual(true);
419   readIter!string(&iter).assertEqual("wow");
420   readIter!InterfaceName(&iter).assertEqual(interfaceName("methodName"));
421   readIter!double(&iter).assertEqual(5.9);
422   readIter!(int[])(&iter).assertEqual([6, 5]);
423   readIter!(Tuple!(double, int, string[][], bool[], Variant!(int[])))(&iter).assertEqual(
424       tuple(6.2, 4, [["lol"]], emptyB, var([4, 2])));
425 
426   // There are two ways to read a dictionary, so duplicate the iterator to test both.
427   auto iter2 = iter;
428   readIter!(string[string])(&iter).assertEqual(["hello": "world"]);
429   auto dict = readIter!(DictionaryEntry!(string, string)[])(&iter2);
430   dict.length.assertEqual(1);
431   dict[0].key.assertEqual("hello");
432   dict[0].value.assertEqual("world");
433 
434   readIter!DBusAny(&iter).assertEqual(anyVar);
435   readIter!(Variant!DBusAny)(&iter).assertEqual(complexVar);
436 }
437 
438 unittest {
439   import dunit.toolkit;
440   import ddbus.thin;
441 
442   import std.variant : Algebraic;
443 
444   enum E : int {
445     a,
446     b,
447     c
448   }
449 
450   enum F : uint {
451     x = 1,
452     y = 2,
453     z = 4
454   }
455 
456   alias V = Algebraic!(ubyte, short, int, long, string);
457 
458   Message msg = Message(busName("org.example.wow"), ObjectPath("/wut"), interfaceName("org.test.iface"), "meth2");
459   V v1 = "hello from variant";
460   V v2 = cast(short) 345;
461   msg.build(E.c, 4, 5u, 8u, v1, v2);
462 
463   DBusMessageIter iter, iter2;
464   dbus_message_iter_init(msg.msg, &iter);
465 
466   readIter!E(&iter).assertEqual(E.c);
467   readIter!E(&iter).assertThrow!InvalidValueException();
468 
469   iter2 = iter;
470   readIter!F(&iter).assertThrow!InvalidValueException();
471   readIter!(BitFlags!F)(&iter2).assertEqual(BitFlags!F(F.x, F.z));
472 
473   readIter!F(&iter).assertThrow!InvalidValueException();
474   readIter!(BitFlags!F)(&iter2).assertThrow!InvalidValueException();
475 
476   readIter!V(&iter).assertEqual(v1);
477   readIter!short(&iter).assertEqual(v2.get!short);
478 }