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 retMsg.build(ret); 103 } else { 104 handler(args.expand); 105 } 106 if(!patt.signal) 107 conn.send(retMsg); 108 } 109 static string[] args = typeSigArr!Args; 110 static if(is(Ret==void)) { 111 static string[] ret = []; 112 } else { 113 static string[] ret = [typeSig!Ret]; 114 } 115 MessageHandler handleStruct = {func: &handlerWrapper, argSig: args, retSig: ret}; 116 callTable[patt] = handleStruct; 117 } 118 119 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"> 120 <node name="%s">`; 121 122 string introspectXML(string path) { 123 auto methods = callTable.byKey().filter!(a => (a.path == path) && !a.signal)().array() 124 // .schwartzSort!((a) => a.iface, "a<b")(); 125 .sort!((a,b) => a.iface < b.iface)(); 126 auto ifaces = methods.groupBy(); 127 auto app = appender!string; 128 formattedWrite(app,introspectHeader,path); 129 foreach(iface; ifaces) { 130 formattedWrite(app,`<interface name="%s">`,iface.front.iface); 131 foreach(methodPatt; iface.array()) { 132 formattedWrite(app,`<method name="%s">`,methodPatt.method); 133 auto handler = callTable[methodPatt]; 134 foreach(arg; handler.argSig) { 135 formattedWrite(app,`<arg type="%s" direction="in"/>`,arg); 136 } 137 foreach(arg; handler.retSig) { 138 formattedWrite(app,`<arg type="%s" direction="out"/>`,arg); 139 } 140 app.put("</method>"); 141 } 142 app.put("</interface>"); 143 } 144 145 string childPath = path; 146 if(!childPath.endsWith("/")) { 147 childPath ~= "/"; 148 } 149 auto children = callTable.byKey().filter!(a => (a.path.startsWith(childPath)) && !a.signal)() 150 .map!((s) => s.path.chompPrefix(childPath)) 151 .map!((s) => s.splitter('/').front) 152 .array().sort().uniq(); 153 foreach(child; children) { 154 formattedWrite(app,`<node name="%s"/>`,child); 155 } 156 157 app.put("</node>"); 158 return app.data; 159 } 160 161 void handleIntrospect(string path, Message call, Connection conn) { 162 auto retMsg = call.createReturn(); 163 retMsg.build(introspectXML(path)); 164 conn.sendBlocking(retMsg); 165 } 166 } 167 168 extern(C) private DBusHandlerResult filterFunc(DBusConnection *dConn, DBusMessage *dMsg, void *routerP) { 169 MessageRouter router = cast(MessageRouter)routerP; 170 dbus_message_ref(dMsg); 171 Message msg = Message(dMsg); 172 dbus_connection_ref(dConn); 173 Connection conn = Connection(dConn); 174 bool handled = router.handle(msg, conn); 175 if(handled) { 176 return DBusHandlerResult.DBUS_HANDLER_RESULT_HANDLED; 177 } else { 178 return DBusHandlerResult.DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 179 } 180 } 181 182 extern(C) private void unrootUserData(void *userdata) { 183 GC.removeRoot(userdata); 184 } 185 186 void registerRouter(Connection conn, MessageRouter router) { 187 void *routerP = cast(void*)router; 188 GC.addRoot(routerP); 189 dbus_connection_add_filter(conn.conn, &filterFunc, routerP, &unrootUserData); 190 } 191 192 unittest{ 193 import dunit.toolkit; 194 auto router = new MessageRouter(); 195 // set up test messages 196 MessagePattern patt = MessagePattern("/root","ca.thume.test","test"); 197 router.setHandler!(int,int)(patt,(int p) {return 6;}); 198 patt = MessagePattern("/root","ca.thume.tester","lolwut"); 199 router.setHandler!(void,int,string)(patt,(int p, string p2) {}); 200 patt = MessagePattern("/root/wat","ca.thume.tester","lolwut"); 201 router.setHandler!(int,int)(patt,(int p) {return 6;}); 202 patt = MessagePattern("/troll","ca.thume.tester","wow"); 203 router.setHandler!(void)(patt,{return;}); 204 205 // TODO: these tests rely on nondeterministic hash map ordering 206 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"> 207 <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="wat"/></node>`; 208 router.introspectXML("/root").assertEqual(introspectResult); 209 router.introspectXML("/").assertEndsWith(`<node name="/"><node name="root"/><node name="troll"/></node>`); 210 }