1 module ddbus.router; 2 3 import ddbus.thin; 4 import ddbus.c_lib; 5 import ddbus.util; 6 import std.string; 7 import std.typecons; 8 import core.memory; 9 import std.array; 10 import std.algorithm; 11 import std.format; 12 13 struct MessagePattern { 14 string path; 15 string iface; 16 string method; 17 bool signal; 18 19 this(Message msg) { 20 path = msg.path(); 21 iface = msg.iface(); 22 method = msg.member(); 23 signal = (msg.type() == MessageType.Signal); 24 } 25 26 this(string path, string iface, string method, bool signal = false) { 27 this.path = path; 28 this.iface = iface; 29 this.method = method; 30 this.signal = signal; 31 } 32 33 size_t toHash() const @safe nothrow { 34 size_t hash = 0; 35 auto stringHash = &(typeid(path).getHash); 36 hash += stringHash(&path); 37 hash += stringHash(&iface); 38 hash += stringHash(&method); 39 hash += (signal?1:0); 40 return hash; 41 } 42 43 bool opEquals(ref const this s) const @safe pure nothrow { 44 return (path == s.path) && (iface == s.iface) && (method == s.method) && (signal == s.signal); 45 } 46 } 47 48 unittest { 49 import dunit.toolkit; 50 auto msg = Message("org.example.test", "/test","org.example.testing","testMethod"); 51 auto patt= new MessagePattern(msg); 52 patt.assertEqual(patt); 53 patt.signal.assertFalse(); 54 patt.path.assertEqual("/test"); 55 } 56 57 struct MessageHandler { 58 alias HandlerFunc = void delegate(Message call, Connection conn); 59 HandlerFunc func; 60 string[] argSig; 61 string[] retSig; 62 } 63 64 class MessageRouter { 65 MessageHandler[MessagePattern] callTable; 66 67 bool handle(Message msg, Connection conn) { 68 MessageType type = msg.type(); 69 if(type != MessageType.Call && type != MessageType.Signal) 70 return false; 71 auto pattern = MessagePattern(msg); 72 // import std.stdio; debug writeln("Handling ", pattern); 73 74 if(pattern.iface == "org.freedesktop.DBus.Introspectable" && 75 pattern.method == "Introspect" && !pattern.signal) { 76 handleIntrospect(pattern.path, msg, conn); 77 return true; 78 } 79 80 MessageHandler* handler = (pattern in callTable); 81 if(handler is null) return false; 82 83 // Check for matching argument types 84 version(DDBusNoChecking) { 85 86 } else { 87 if(!equal(join(handler.argSig), msg.signature())) { 88 return false; 89 } 90 } 91 92 handler.func(msg,conn); 93 return true; 94 } 95 96 void setHandler(Ret, Args...)(MessagePattern patt, Ret delegate(Args) handler) { 97 void handlerWrapper(Message call, Connection conn) { 98 Tuple!Args args = call.readTuple!(Tuple!Args)(); 99 auto retMsg = call.createReturn(); 100 static if(!is(Ret == void)) { 101 Ret ret = handler(args.expand); 102 static if (is(Ret == Tuple!T, T...)) 103 retMsg.build!T(ret.expand); 104 else 105 retMsg.build(ret); 106 } else { 107 handler(args.expand); 108 } 109 if(!patt.signal) 110 conn.send(retMsg); 111 } 112 static string[] args = typeSigArr!Args; 113 static if(is(Ret==void)) { 114 static string[] ret = []; 115 } else { 116 static string[] ret = typeSigReturn!Ret; 117 } 118 MessageHandler handleStruct = {func: &handlerWrapper, argSig: args, retSig: ret}; 119 callTable[patt] = handleStruct; 120 } 121 122 static string introspectHeader = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> 123 <node name="%s">`; 124 125 string introspectXML(string path) { 126 auto methods = callTable.byKey().filter!(a => (a.path == path) && !a.signal)().array() 127 // .schwartzSort!((a) => a.iface, "a<b")(); 128 .sort!((a,b) => a.iface < b.iface)(); 129 auto ifaces = methods.groupBy(); 130 auto app = appender!string; 131 formattedWrite(app,introspectHeader,path); 132 foreach(iface; ifaces) { 133 formattedWrite(app,`<interface name="%s">`,iface.front.iface); 134 foreach(methodPatt; iface.array()) { 135 formattedWrite(app,`<method name="%s">`,methodPatt.method); 136 auto handler = callTable[methodPatt]; 137 foreach(arg; handler.argSig) { 138 formattedWrite(app,`<arg type="%s" direction="in"/>`,arg); 139 } 140 foreach(arg; handler.retSig) { 141 formattedWrite(app,`<arg type="%s" direction="out"/>`,arg); 142 } 143 app.put("</method>"); 144 } 145 app.put("</interface>"); 146 } 147 148 string childPath = path; 149 if(!childPath.endsWith("/")) { 150 childPath ~= "/"; 151 } 152 auto children = callTable.byKey().filter!(a => (a.path.startsWith(childPath)) && !a.signal)() 153 .map!((s) => s.path.chompPrefix(childPath)) 154 .map!((s) => s.splitter('/').front) 155 .array().sort().uniq(); 156 foreach(child; children) { 157 formattedWrite(app,`<node name="%s"/>`,child); 158 } 159 160 app.put("</node>"); 161 return app.data; 162 } 163 164 void handleIntrospect(string path, Message call, Connection conn) { 165 auto retMsg = call.createReturn(); 166 retMsg.build(introspectXML(path)); 167 conn.sendBlocking(retMsg); 168 } 169 } 170 171 extern(C) private DBusHandlerResult filterFunc(DBusConnection *dConn, DBusMessage *dMsg, void *routerP) { 172 MessageRouter router = cast(MessageRouter)routerP; 173 dbus_message_ref(dMsg); 174 Message msg = Message(dMsg); 175 dbus_connection_ref(dConn); 176 Connection conn = Connection(dConn); 177 bool handled = router.handle(msg, conn); 178 if(handled) { 179 return DBusHandlerResult.DBUS_HANDLER_RESULT_HANDLED; 180 } else { 181 return DBusHandlerResult.DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 182 } 183 } 184 185 extern(C) private void unrootUserData(void *userdata) { 186 GC.removeRoot(userdata); 187 } 188 189 void registerRouter(Connection conn, MessageRouter router) { 190 void *routerP = cast(void*)router; 191 GC.addRoot(routerP); 192 dbus_connection_add_filter(conn.conn, &filterFunc, routerP, &unrootUserData); 193 } 194 195 unittest{ 196 import dunit.toolkit; 197 auto router = new MessageRouter(); 198 // set up test messages 199 MessagePattern patt = MessagePattern("/root","ca.thume.test","test"); 200 router.setHandler!(int,int)(patt,(int p) {return 6;}); 201 patt = MessagePattern("/root","ca.thume.tester","lolwut"); 202 router.setHandler!(void,int,string)(patt,(int p, string p2) {}); 203 patt = MessagePattern("/root/wat","ca.thume.tester","lolwut"); 204 router.setHandler!(int,int)(patt,(int p) {return 6;}); 205 patt = MessagePattern("/root/bar","ca.thume.tester","lolwut"); 206 router.setHandler!(Variant!DBusAny,int)(patt,(int p) {return variant(DBusAny(p));}); 207 patt = MessagePattern("/root/foo","ca.thume.tester","lolwut"); 208 router.setHandler!(Tuple!(string,string,int),int,Variant!DBusAny)(patt,(int p, Variant!DBusAny any) {Tuple!(string,string,int) ret; ret[0] = "a"; ret[1] = "b"; ret[2] = p; return ret;}); 209 patt = MessagePattern("/troll","ca.thume.tester","wow"); 210 router.setHandler!(void)(patt,{return;}); 211 212 static assert(!__traits(compiles, { 213 patt = MessagePattern("/root/bar","ca.thume.tester","lolwut"); 214 router.setHandler!(void, DBusAny)(patt,(DBusAny wrongUsage){return;}); 215 })); 216 217 // TODO: these tests rely on nondeterministic hash map ordering 218 static string introspectResult = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> 219 <node name="/root"><interface name="ca.thume.test"><method name="test"><arg type="i" direction="in"/><arg type="i" direction="out"/></method></interface><interface name="ca.thume.tester"><method name="lolwut"><arg type="i" direction="in"/><arg type="s" direction="in"/></method></interface><node name="bar"/><node name="foo"/><node name="wat"/></node>`; 220 router.introspectXML("/root").assertEqual(introspectResult); 221 static string introspectResult2 = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> 222 <node name="/root/foo"><interface name="ca.thume.tester"><method name="lolwut"><arg type="i" direction="in"/><arg type="v" direction="in"/><arg type="s" direction="out"/><arg type="s" direction="out"/><arg type="i" direction="out"/></method></interface></node>`; 223 router.introspectXML("/root/foo").assertEqual(introspectResult2); 224 router.introspectXML("/").assertEndsWith(`<node name="/"><node name="root"/><node name="troll"/></node>`); 225 }