From 6bce6cf6490ce819f048b7ea8f1458ec66fbf1c8 Mon Sep 17 00:00:00 2001 From: "Harry T. Vennik" Date: Thu, 20 Jul 2017 16:55:20 +0200 Subject: [PATCH 1/4] Added support for POD structs Note: structs are deliberately considered last, because other stuff may be implemented as structs as well. So we need to take care of special cases before trying struct (de)serialization. --- source/ddbus/conv.d | 35 +++++++++++++++++++++++++++++++++++ source/ddbus/util.d | 22 +++++++++++++++++----- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index a8d4c19..d4a74cd 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -113,6 +113,11 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { dbus_message_iter_open_container(iter, 'v', subSig, &sub); buildIter(&sub, arg.data); dbus_message_iter_close_container(iter, &sub); + } else static if(is(T == struct)) { + DBusMessageIter sub; + dbus_message_iter_open_container(iter, 'r', null, &sub); + buildIter(&sub, arg.tupleof); + dbus_message_iter_close_container(iter, &sub); } else static if(basicDBus!T) { dbus_message_iter_append_basic(iter,typeCode!T,&arg); } @@ -265,6 +270,10 @@ T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFla ret.entry.key = readIter!DBusAny(&sub); ret.entry.value = readIter!DBusAny(&sub); } + } else static if(is(T == struct)) { + DBusMessageIter sub; + dbus_message_iter_recurse(iter, &sub); + readIterStruct!T(&sub, ret); } else static if(basicDBus!T) { dbus_message_iter_get_basic(iter, &ret); } @@ -279,6 +288,13 @@ void readIterTuple(Tup)(DBusMessageIter *iter, ref Tup tuple) if(isTuple!Tup && } } +void readIterStruct(S)(DBusMessageIter *iter, ref S s) if(is(S == struct) && allCanDBus!(Fields!S)) { + alias FieldNameTuple!S names; + foreach(index, T; Fields!S) { + __traits(getMember, s, names[index]) = readIter!T(iter); + } +} + unittest { import dunit.toolkit; import ddbus.thin; @@ -355,3 +371,22 @@ unittest { readIter!F(&iter).assertThrow!InvalidValueException(); readIter!(BitFlags!F)(&iter2).assertThrow!InvalidValueException(); } + +unittest { + import dunit.toolkit; + import ddbus.thin; + + struct S1 { int a; double b; string s; } + struct S2 { Variant!int c; string d; S1 e; uint f; } + + Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth3"); + + enum testStruct = S2(variant(5), "blah", S1(-7, 63.5, "test"), 16); + msg.build(testStruct); + + DBusMessageIter iter; + dbus_message_iter_init(msg.msg, &iter); + + readIter!S2(&iter).assertEqual(testStruct); +} + diff --git a/source/ddbus/util.d b/source/ddbus/util.d index 968e89a..960624d 100644 --- a/source/ddbus/util.d +++ b/source/ddbus/util.d @@ -72,6 +72,8 @@ template canDBus(T) { } } else static if(isAssociativeArray!T) { enum canDBus = basicDBus!(KeyType!T) && canDBus!(ValueType!T); + } else static if(is(T == struct) && !isInstanceOf!(DictionaryEntry, T)) { + enum canDBus = allCanDBus!(Fields!T); } else { enum canDBus = false; } @@ -130,6 +132,13 @@ string typeSig(T)() if(canDBus!T) { return "a" ~ typeSig!(ElementType!T)(); } else static if(isAssociativeArray!T) { return "a{" ~ typeSig!(KeyType!T) ~ typeSig!(ValueType!T) ~ "}"; + } else static if(is(T == struct)) { + string sig = "("; + foreach(i, S; Fields!T) { + sig ~= typeSig!S(); + } + sig ~= ")"; + return sig; } } @@ -163,10 +172,8 @@ string[] typeSigArr(TS...)() if(allCanDBus!TS) { } int typeCode(T)() if(canDBus!T) { - static if (isTuple!T) - return 'r'; - else - return typeSig!T()[0]; + int code = typeSig!T()[0]; + return (code != '(') ? code : 'r'; } int typeCode(T)() if(isInstanceOf!(DictionaryEntry, T) && canDBus!(T[])) { @@ -188,9 +195,14 @@ unittest { // bit flags enum F : uint { a = 1, b = 2, c = 4 } typeSig!(BitFlags!F)().assertEqual(typeSig!uint()); - // structs + // tuples (represented as structs in DBus) typeSig!(Tuple!(int,string,string)).assertEqual("(iss)"); typeSig!(Tuple!(int,string,Variant!int,Tuple!(int,"k",double,"x"))).assertEqual("(isv(id))"); + // structs + struct S1 { int a; double b; string s; } + typeSig!S1.assertEqual("(ids)"); + struct S2 { Variant!int c; string d; S1 e; uint f; } + typeSig!S2.assertEqual("(vs(ids)u)"); // arrays typeSig!(int[]).assertEqual("ai"); typeSig!(Variant!int[]).assertEqual("av"); From 87a06cead1b2f503d71c52a23a096a38969c98ab Mon Sep 17 00:00:00 2001 From: "Harry T. Vennik" Date: Sat, 22 Jul 2017 15:06:23 +0200 Subject: [PATCH 2/4] Improved tests Moved unittest for struct types from `ddbus.conv` to `ddbus.thin` and made one struct field private. Test now fails compile-time because of error "member a is not accessible". --- source/ddbus/conv.d | 18 ------------------ source/ddbus/thin.d | 13 +++++++++++++ 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index d4a74cd..43e491a 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -372,21 +372,3 @@ unittest { readIter!(BitFlags!F)(&iter2).assertThrow!InvalidValueException(); } -unittest { - import dunit.toolkit; - import ddbus.thin; - - struct S1 { int a; double b; string s; } - struct S2 { Variant!int c; string d; S1 e; uint f; } - - Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth3"); - - enum testStruct = S2(variant(5), "blah", S1(-7, 63.5, "test"), 16); - msg.build(testStruct); - - DBusMessageIter iter; - dbus_message_iter_init(msg.msg, &iter); - - readIter!S2(&iter).assertEqual(testStruct); -} - diff --git a/source/ddbus/thin.d b/source/ddbus/thin.d index de60eba..7710502 100644 --- a/source/ddbus/thin.d +++ b/source/ddbus/thin.d @@ -570,6 +570,19 @@ struct Connection { } } +unittest { + import dunit.toolkit; + + struct S1 { private int a; double b; string s; } + struct S2 { Variant!int c; string d; S1 e; uint f; } + + Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth3"); + + enum testStruct = S2(variant(5), "blah", S1(-7, 63.5, "test"), 16); + msg.build(testStruct); + msg.read!S2().assertEqual(testStruct); +} + Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) { DBusConnection *conn = wrapErrors((err) { return dbus_bus_get(bus,err); }); return Connection(conn); From fc9a604e8536cfdcb5e4b322aa2dbbd234b9145a Mon Sep 17 00:00:00 2001 From: "Harry T. Vennik" Date: Sat, 22 Jul 2017 15:14:27 +0200 Subject: [PATCH 3/4] Fix demarshaling of private struct fields Tests now pass again. --- source/ddbus/conv.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index 43e491a..fa5abcb 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -291,7 +291,7 @@ void readIterTuple(Tup)(DBusMessageIter *iter, ref Tup tuple) if(isTuple!Tup && void readIterStruct(S)(DBusMessageIter *iter, ref S s) if(is(S == struct) && allCanDBus!(Fields!S)) { alias FieldNameTuple!S names; foreach(index, T; Fields!S) { - __traits(getMember, s, names[index]) = readIter!T(iter); + s.tupleof[index] = readIter!T(iter); } } From da536be3d1fad0815640c4a82fd72264c5b940e4 Mon Sep 17 00:00:00 2001 From: "Harry T. Vennik" Date: Wed, 26 Jul 2017 09:45:39 +0200 Subject: [PATCH 4/4] Implemented selective marshaling of struct fields - Skips private fields by default - Allows overriding of that default by an UDA on the struct type - Allows per-field overriding using UDA on field --- source/ddbus/conv.d | 82 +++++++++++++++++++++++++++++++++++++++++++-- source/ddbus/thin.d | 43 +++++++++++++++++++++--- 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index fa5abcb..11683d7 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -5,6 +5,7 @@ import ddbus.exception : InvalidValueException, TypeMismatchException; import ddbus.util; import ddbus.thin; import std.exception : enforce; +import std.meta: allSatisfy; import std.string; import std.typecons; import std.range; @@ -116,7 +117,22 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { } else static if(is(T == struct)) { DBusMessageIter sub; dbus_message_iter_open_container(iter, 'r', null, &sub); - buildIter(&sub, arg.tupleof); + + // Following failed because of missing 'this' for members of arg. + // That sucks. It worked without Filter. + // Reported: https://issues.dlang.org/show_bug.cgi?id=17692 +// buildIter(&sub, Filter!(isAllowedField, arg.tupleof)); + + // Using foreach to work around the issue + foreach(i, member; arg.tupleof) { + // Ugly, but we need to use tupleof again in the condition, because when + // we use `member`, isAllowedField will fail because it'll find this + // nice `buildIter` function instead of T when it looks up the parent + // scope of its argument. + static if (isAllowedField!(arg.tupleof[i])) + buildIter(&sub, member); + } + dbus_message_iter_close_container(iter, &sub); } else static if(basicDBus!T) { dbus_message_iter_append_basic(iter,typeCode!T,&arg); @@ -289,12 +305,72 @@ void readIterTuple(Tup)(DBusMessageIter *iter, ref Tup tuple) if(isTuple!Tup && } void readIterStruct(S)(DBusMessageIter *iter, ref S s) if(is(S == struct) && allCanDBus!(Fields!S)) { - alias FieldNameTuple!S names; foreach(index, T; Fields!S) { - s.tupleof[index] = readIter!T(iter); + static if (isAllowedField!(s.tupleof[index])) { + s.tupleof[index] = readIter!T(iter); + } } } +/++ + Flags for use with dbusMarshaling UDA + + Default is to include public fields only ++/ +enum MarshalingFlag : ubyte { + includePrivateFields = 1 << 0, /// Automatically include private fields + manualOnly = 1 << 7 /// Only include fields with explicit + /// `@Yes.DBusMarshal`. This overrides any + /// `include` flags. +} + +private template isMarshalingFlag(T) { + enum isMarshalingFlag = is(T == MarshalingFlag); +} + +/++ + UDA for specifying DBus marshaling options on structs ++/ +auto dbusMarshaling(Args)(Args args ...) + if (allSatisfy!(isMarshalingFlag, Args)) { + return BitFlags!MarshalingFlag(args); +} + +private template marshalingFlags(S) if (is(S == struct)) { + private alias getUDAs!(S, BitFlags!MarshalingFlag) UDAs; + + static if (UDAs.length == 0) + enum marshalingFlags = BitFlags!MarshalingFlag.init; + else { + static assert (UDAs.length == 1, + "Only one @dbusMarshaling UDA allowed on type."); + static assert (is(typeof(UDAs[0]) == BitFlags!MarshalingFlag), + "Huh? Did you intend to use @dbusMarshaling UDA?"); + enum marshalingFlags = UDAs[0]; + } +} + +private template isAllowedField(alias field) { + private enum flags = marshalingFlags!(__traits(parent, field)); + private alias getUDAs!(field, Flag!"DBusMarshal") UDAs; + + static if (UDAs.length != 0) { + static assert (UDAs.length == 1, + "Only one UDA of type Flag!\"DBusMarshal\" allowed on struct field."); + static assert (is(typeof(UDAs[0]) == Flag!"DBusMarshal"), + "Did you intend to add UDA Yes.DBusMarshal or No.DBusMarshal?"); + enum isAllowedField = cast(bool) UDAs[0]; + } else static if (!(flags & MarshalingFlag.manualOnly)) { + static if (__traits(getProtection, field) == "public") + enum isAllowedField = true; + else static if (cast(bool) (flags & MarshalingFlag.includePrivateFields)) + enum isAllowedField = true; + else + enum isAllowedField = false; + } else + enum isAllowedField = false; +} + unittest { import dunit.toolkit; import ddbus.thin; diff --git a/source/ddbus/thin.d b/source/ddbus/thin.d index 7710502..c98527e 100644 --- a/source/ddbus/thin.d +++ b/source/ddbus/thin.d @@ -573,14 +573,49 @@ struct Connection { unittest { import dunit.toolkit; - struct S1 { private int a; double b; string s; } - struct S2 { Variant!int c; string d; S1 e; uint f; } + struct S1 { + private int a; + private @(Yes.DBusMarshal) double b; + string s; + } + + @dbusMarshaling(MarshalingFlag.manualOnly) + struct S2 { + int h, i; + @(Yes.DBusMarshal) int j, k; + } + + @dbusMarshaling(MarshalingFlag.includePrivateFields) + struct S3 { + private Variant!int c; + string d; + S1 e; + S2 f; + @(No.DBusMarshal) uint g; + } Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth3"); - enum testStruct = S2(variant(5), "blah", S1(-7, 63.5, "test"), 16); + enum testStruct = S3( + variant(5), "blah", + S1(-7, 63.5, "test"), + S2(84, -123, 78, 432), + 16 + ); + msg.build(testStruct); - msg.read!S2().assertEqual(testStruct); + + // Non-marshaled fields should appear as freshly initialized + enum expectedResult = S3( + variant(5), "blah", + S1(int.init, 63.5, "test"), + S2(int.init, int.init, 78, 432), + uint.init + ); + + msg.read!S3().assertEqual(expectedResult); + + } Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) {