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 }