4024da939015cbcbcc120b12203729ec35dbcee7
[contrib/cloudeebus.git] / cloudeebus / cloudeebus.py
1 #!/usr/bin/env python
2
3 # Cloudeebus
4 #
5 # Copyright 2012 Intel Corporation.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 #
19 # Luc Yriarte <luc.yriarte@intel.com>
20 # Christophe Guiraud <christophe.guiraud@intel.com>
21 #
22
23
24 import argparse, dbus, json, sys
25
26 from twisted.internet import glib2reactor
27 # Configure the twisted mainloop to be run inside the glib mainloop.
28 # This must be done before importing the other twisted modules
29 glib2reactor.install()
30 from twisted.internet import reactor, defer
31
32 from autobahn.websocket import listenWS
33 from autobahn.wamp import exportRpc, WampServerFactory, WampCraServerProtocol
34
35 from dbus.mainloop.glib import DBusGMainLoop
36
37 import gobject
38 import re
39 import dbus.service
40 gobject.threads_init()
41
42 from dbus import glib
43 glib.init_threads()
44
45 # enable debug log
46 from twisted.python import log
47
48 # XML parser module
49 from xml.etree.ElementTree import XMLParser
50
51 # For debug only
52 import os
53
54 ###############################################################################
55
56 VERSION = "0.3.0"
57 OPENDOOR = False
58 CREDENTIALS = {}
59 WHITELIST = []
60
61 ###############################################################################
62 class DbusCache:
63     '''
64     Global cache of DBus connexions and signal handlers
65     '''
66     def __init__(self):
67         self.dbusConnexions = {}
68         self.signalHandlers = {}
69
70
71     def reset(self):
72         '''
73         Disconnect signal handlers before resetting cache.
74         '''
75         self.dbusConnexions = {}
76         # disconnect signal handlers
77         for key in self.signalHandlers:
78             self.signalHandlers[key].disconnect()
79         self.signalHandlers = {}
80
81
82     def dbusConnexion(self, busName):
83         if not self.dbusConnexions.has_key(busName):
84             if busName == "session":
85                 self.dbusConnexions[busName] = dbus.SessionBus()
86             elif busName == "system":
87                 self.dbusConnexions[busName] = dbus.SystemBus()
88             else:
89                 raise Exception("Error: invalid bus: %s" % busName)
90         return self.dbusConnexions[busName]
91
92
93
94 ###############################################################################
95 class DbusSignalHandler:
96     '''
97     signal hash id as busName#senderName#objectName#interfaceName#signalName
98     '''
99     def __init__(self, busName, senderName, objectName, interfaceName, signalName):
100         self.id = "#".join([busName, senderName, objectName, interfaceName, signalName])
101         # connect handler to signal
102         self.bus = cache.dbusConnexion(busName)
103         self.bus.add_signal_receiver(self.handleSignal, signalName, interfaceName, senderName, objectName)
104         
105     
106     def disconnect(self):
107         names = self.id.split("#")
108         self.bus.remove_signal_receiver(self.handleSignal, names[4], names[3], names[1], names[2])
109
110
111     def handleSignal(self, *args):
112         '''
113         publish dbus args under topic hash id
114         '''
115         factory.dispatch(self.id, json.dumps(args))
116
117
118
119 ###############################################################################
120 class DbusCallHandler:
121     '''
122     deferred reply to return dbus results
123     '''
124     def __init__(self, method, args):
125         self.pending = False
126         self.request = defer.Deferred()
127         self.method = method
128         self.args = args
129
130
131     def callMethod(self):
132         '''
133         dbus method async call
134         '''
135         self.pending = True
136         self.method(*self.args, reply_handler=self.dbusSuccess, error_handler=self.dbusError)
137         return self.request
138
139
140     def dbusSuccess(self, *result):
141         '''
142         return JSON string result array
143         '''
144         self.request.callback(json.dumps(result))
145         self.pending = False
146
147
148     def dbusError(self, error):
149         '''
150         return dbus error message
151         '''
152         self.request.errback(Exception(error.get_dbus_message()))
153         self.pending = False
154
155
156
157 ################################################################################       
158 class ExecCode:
159     '''
160     Execute DynDBusClass generated code
161     '''
162     def __init__(self, globalCtx, localCtx) :
163         self.exec_string = ""
164         self.exec_code = None
165         self.exec_code_valid = 1
166         self.indent_level = 0
167         self.indent_increment = 1
168         self.line = 0
169         self.localCtx = localCtx
170         self.globalCtx = globalCtx
171         
172
173     def append_stmt(self, stmt) :
174         self.exec_code_valid = 0
175         self.line += 1
176         for x in range(0,self.indent_level):
177             self.exec_string = self.exec_string + ' '            
178         self.exec_string = self.exec_string + stmt + '\n'
179
180     def indent(self) :
181         self.indent_level = self.indent_level + self.indent_increment
182
183     def dedent(self) :
184         self.indent_level = self.indent_level - self.indent_increment
185     
186     # compile : Compile exec_string into exec_code using the builtin
187     # compile function. Skip if already in sync.
188     def compile(self) :
189         if not self.exec_code_valid :
190             self.exec_code = compile(self.exec_string, "<string>", "exec")
191         self.exec_code_valid = True
192
193     def execute(self) :
194         if not self.exec_code_valid :
195             self.compile()
196         exec(self.exec_code, self.globalCtx, self.localCtx)
197
198
199
200 ################################################################################       
201 class XmlCbParser: # The target object of the parser
202     maxDepth = 0
203     depth = 0
204     def __init__(self, dynDBusClass):
205         self.dynDBusClass = dynDBusClass
206         
207     def start(self, tag, attrib):   # Called for each opening tag.
208         if (tag == 'node'):
209             return
210         # Set interface name
211         if (tag == 'interface'):
212             self.dynDBusClass.set_interface(attrib['name'])
213             return
214         # Set method name
215         if (tag == 'method'):
216             self.current = tag
217             self.dynDBusClass.def_method(attrib['name'])
218             return
219         if (tag == 'signal'):
220             self.current = tag
221             self.dynDBusClass.def_signal(attrib['name'])
222             return
223
224         # Set signature (in/out & name) for method
225         if (tag == 'arg'):
226             if (self.current == 'method'):
227                 if (attrib.has_key('direction') == False):
228                     attrib['direction'] = "in"
229                 self.dynDBusClass.add_signature(attrib['name'],
230                                                 attrib['direction'],
231                                                 attrib['type'])
232                 return
233             if (self.current == 'signal'):
234                 if (attrib.has_key('name') == False):
235                     attrib['name'] = 'value'
236                 self.dynDBusClass.add_signature(attrib['name'], 'in',
237                                                 attrib['type'])
238                 return
239     def end(self, tag):             # Called for each closing tag.
240         if (tag == 'method'):
241             self.dynDBusClass.add_dbus_method()
242             self.dynDBusClass.add_body_method()
243             self.dynDBusClass.end_method()
244         if (tag == 'signal'):
245             self.dynDBusClass.add_dbus_signal()
246             self.dynDBusClass.add_body_signal()
247             self.dynDBusClass.end_method()
248            
249     def data(self, data):
250         pass            # We do not need to do anything with data.
251     def close(self):    # Called when all data has been parsed.
252         return self.maxDepth
253
254
255        
256 ################################################################################       
257 class DynDBusClass():
258     def __init__(self, className, globalCtx, localCtx):
259         self.className = className
260         self.xmlCB = XmlCbParser(self)
261         self.signature = {}
262         self.class_code = ExecCode(globalCtx, localCtx)  
263         self.class_code.indent_increment = 4
264         self.class_code.append_stmt("import dbus")
265         self.class_code.append_stmt("\n")
266         self.class_code.append_stmt("class " + self.className + "(dbus.service.Object):")
267         self.class_code.indent()
268         
269         ## Overload of __init__ method 
270         self.def_method("__init__")
271         self.add_method("bus, callback=None, objPath='/sample', busName='org.cloudeebus'")
272         self.add_stmt("self.bus = bus")
273         self.add_stmt("self.objPath = objPath")
274         self.add_stmt("self.callback = callback")        
275         self.add_stmt("dbus.service.Object.__init__(self, conn=bus, bus_name=busName)")
276         self.end_method()
277                
278         ## Create 'add_to_connection' method 
279         self.def_method("add_to_connection")
280         self.add_method("connection=None, path=None")
281         self.add_stmt("dbus.service.Object.add_to_connection(self, connection=self.bus, path=self.objPath)")
282         self.end_method()
283                
284         ## Create 'remove_from_connection' method 
285         self.def_method("remove_from_connection")
286         self.add_method("connection=None, path=None")
287         self.add_stmt("dbus.service.Object.remove_from_connection(self, connection=None, path=self.objPath)")
288         self.end_method()
289                
290     def createDBusServiceFromXML(self, xml):
291         self.parser = XMLParser(target=self.xmlCB)
292         self.parser.feed(xml)
293         self.parser.close()
294     
295     def set_interface(self, ifName):
296         self.ifName = ifName
297         
298     def def_method(self, methodName):
299         self.methodToAdd = methodName
300         self.signalToAdd = None
301         self.args_str = str()
302         self.signature = {}
303         self.signature['name'] = str()
304         self.signature['in'] = str()                
305         self.signature['out'] = str()                        
306
307     def def_signal(self, signalName):
308         self.methodToAdd = None
309         self.signalToAdd = signalName
310         self.args_str = str()
311         self.signature = {}
312         self.signature['name'] = str()
313         self.signature['in'] = str()                
314         self.signature['out'] = str()                        
315
316     def add_signature(self, name, direction, signature):
317         if (direction == 'in'):
318             self.signature['in'] += signature
319             if (self.signature['name'] != str()):
320                 self.signature['name'] += ", "
321             self.signature['name'] += name
322         if (direction == 'out'):
323             self.signature['out'] = signature                        
324         
325     def add_method(self, args = None, async_success_cb = None, async_err_cb = None):
326         async_cb_str = str()
327         if (self.methodToAdd != None):
328             name = self.methodToAdd
329         else:
330             name = self.signalToAdd
331         if (args != None):
332             self.args_str = args
333         if (async_success_cb != None):
334             async_cb_str = async_success_cb
335         if (async_err_cb != None):
336             if (async_cb_str != str()):
337                 async_cb_str += ", "
338             async_cb_str += async_err_cb
339                         
340         parameters = self.args_str
341         if (async_cb_str != str()):
342             if (parameters != str()):
343                 parameters += ", "
344             parameters +=async_cb_str       
345         
346         if (parameters != str()):
347             self.class_code.append_stmt("def " + name + "(self, %s):" % parameters)               
348         else:
349             self.class_code.append_stmt("def " + name + "(self):")
350         self.class_code.indent()
351         
352     def end_method(self):
353         self.class_code.append_stmt("\n")
354         self.class_code.dedent()
355         
356     def add_dbus_method(self):
357         decorator = '@dbus.service.method("' + self.ifName + '"'
358         if (self.signature.has_key('in') and self.signature['in'] != str()):
359                 decorator += ", in_signature='" + self.signature['in'] + "'"
360         if (self.signature.has_key('out') and self.signature['out'] != str()):
361                 decorator += ", out_signature='" + self.signature['out'] + "'"
362         decorator += ", async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')"            
363         decorator += ")"
364         self.class_code.append_stmt(decorator)
365         if (self.signature.has_key('name') and self.signature['name'] != str()):
366             self.add_method(self.signature['name'], async_success_cb='dbus_async_cb', async_err_cb='dbus_async_err_cb')
367         else:
368             self.add_method(async_success_cb='dbus_async_cb', async_err_cb='dbus_async_err_cb')
369
370     def add_dbus_signal(self):
371         decorator = '@dbus.service.signal("' + self.ifName + '"'
372         if (self.signature.has_key('in') and self.signature['in'] != str()):
373                 decorator += ", signature='" + self.signature['in'] + "'"
374         decorator += ")"            
375         self.class_code.append_stmt(decorator)
376         if (self.signature.has_key('name') and self.signature['name'] != str()):
377             self.add_method(self.signature['name'])
378         else:
379             self.add_method()
380
381     def add_body_method(self):
382         if (self.methodToAdd != None):
383             if (self.args_str != str()):
384                 self.class_code.append_stmt("self.callback('" + self.methodToAdd + "', self.objPath, '"  + self.ifName + "', " + "dbus_async_cb, dbus_async_err_cb, %s)" % self.args_str)
385             else:        
386                 self.class_code.append_stmt("self.callback('" + self.methodToAdd + "', self.objPath, '"  + self.ifName + "', " + "dbus_async_cb, dbus_async_err_cb)")
387
388     def add_body_signal(self):
389         self.class_code.append_stmt("return") ## TODO: Remove and fix with code ad hoc
390         self.class_code.append_stmt("\n")
391
392     def add_stmt(self, stmt) :
393         self.class_code.append_stmt(stmt)
394         
395     def declare(self) :
396         self.class_code.execute()
397
398
399
400 ###############################################################################
401 class CloudeebusService:
402     '''
403     support for sending DBus messages and registering for DBus signals
404     '''
405     def __init__(self, permissions):
406         self.permissions = permissions;
407         self.proxyObjects = {}
408         self.proxyMethods = {}
409         self.pendingCalls = []
410         self.dynDBusClasses = {} # DBus class source code generated dynamically (a list because one by classname)
411         self.services = {}  # DBus service created
412         self.serviceAgents = {} # Instantiated DBus class previously generated dynamically, for now, one by classname
413         self.servicePendingCalls = {} # JS methods called (and waiting for a Success/error response), containing 'methodId', (successCB, errorCB)
414         self.localCtx = locals()
415         self.globalCtx = globals()
416
417
418     def proxyObject(self, busName, serviceName, objectName):
419         '''
420         object hash id as busName#serviceName#objectName
421         '''
422         id = "#".join([busName, serviceName, objectName])
423         if not self.proxyObjects.has_key(id):
424             if not OPENDOOR:
425                 # check permissions, array.index throws exception
426                 self.permissions.index(serviceName)
427             bus = cache.dbusConnexion(busName)
428             self.proxyObjects[id] = bus.get_object(serviceName, objectName)
429         return self.proxyObjects[id]
430
431
432     def proxyMethod(self, busName, serviceName, objectName, interfaceName, methodName):
433         '''
434         method hash id as busName#serviceName#objectName#interfaceName#methodName
435         '''
436         id = "#".join([busName, serviceName, objectName, interfaceName, methodName])
437         if not self.proxyMethods.has_key(id):
438             obj = self.proxyObject(busName, serviceName, objectName)
439             self.proxyMethods[id] = obj.get_dbus_method(methodName, interfaceName)
440         return self.proxyMethods[id]
441
442
443     @exportRpc
444     def dbusRegister(self, list):
445         '''
446         arguments: bus, sender, object, interface, signal
447         '''
448         if len(list) < 5:
449             raise Exception("Error: expected arguments: bus, sender, object, interface, signal)")
450         
451         if not OPENDOOR:
452             # check permissions, array.index throws exception
453             self.permissions.index(list[1])
454         
455         # check if a handler exists
456         sigId = "#".join(list)
457         if cache.signalHandlers.has_key(sigId):
458             return sigId
459         
460         # create a handler that will publish the signal
461         dbusSignalHandler = DbusSignalHandler(*list)
462         cache.signalHandlers[sigId] = dbusSignalHandler
463         
464         return dbusSignalHandler.id
465
466
467     @exportRpc
468     def dbusSend(self, list):
469         '''
470         arguments: bus, destination, object, interface, message, [args]
471         '''
472         # clear pending calls
473         for call in self.pendingCalls:
474             if not call.pending:
475                 self.pendingCalls.remove(call)
476         
477         if len(list) < 5:
478             raise Exception("Error: expected arguments: bus, destination, object, interface, message, [args])")
479         
480         # parse JSON arg list
481         args = []
482         if len(list) == 6:
483             args = json.loads(list[5])
484         
485         # get dbus proxy method
486         method = self.proxyMethod(*list[0:5])
487         
488         # use a deferred call handler to manage dbus results
489         dbusCallHandler = DbusCallHandler(method, args)
490         self.pendingCalls.append(dbusCallHandler)
491         return dbusCallHandler.callMethod()
492
493
494     @exportRpc
495     def emitSignal(self, list):
496         '''
497         arguments: agentObjectPath, signalName, result (to emit)
498         '''
499         objectPath = list[0]
500         className = re.sub('/', '_', objectPath[1:])
501         signalName = list[1]
502         result = list[2]
503         if (self.serviceAgents.has_key(className) == True):
504             exe_str = "self.serviceAgents['"+ className +"']."+ signalName + "(" + str(result) + ")"
505             eval(exe_str, self.globalCtx, self.localCtx)
506         else:
507             raise Exception("No object path " + objectPath)
508
509     @exportRpc
510     def returnMethod(self, list):
511         '''
512         arguments: methodId, callIndex, success (=true, error otherwise), result (to return)
513         '''
514         methodId = list[0]
515         callIndex = list[1]
516         success = list[2]
517         result = list[3]
518         if (self.servicePendingCalls.has_key(methodId)):
519             cb = self.servicePendingCalls[methodId]['calls'][callIndex]
520             if cb is None:
521                 raise Exception("No pending call " + str(callIndex) + " for methodID " + methodId)
522             if (success):                
523                 successCB = cb["successCB"]
524                 if (result != None):
525                     successCB(result)
526                 else:
527                     successCB()                    
528             else:     
529                 errorCB = cb["errorCB"]        
530                 if (result != None):
531                     errorCB(result)
532                 else:
533                     errorCB()
534             self.servicePendingCalls[methodId]['calls'][callIndex] = None
535             self.servicePendingCalls[methodId]['count'] = self.servicePendingCalls[methodId]['count'] - 1
536             if self.servicePendingCalls[methodId]['count'] == 0:
537                 del self.servicePendingCalls[methodId]
538         else:
539             raise Exception("No methodID " + methodId)
540
541     def srvCB(self, name, objPath, ifName, async_succes_cb, async_error_cb, *args):
542         methodId = self.srvName + "#" + objPath + "#" + ifName + "#" + name
543         cb = { 'successCB': async_succes_cb, 
544                'errorCB': async_error_cb}
545         if methodId not in self.servicePendingCalls:
546             self.servicePendingCalls[methodId] = {'count': 0, 'calls': []}
547         pendingCallStr = json.dumps({'callIndex': len(self.servicePendingCalls[methodId]['calls']), 'args': args})
548         self.servicePendingCalls[methodId]['calls'].append(cb)
549         self.servicePendingCalls[methodId]['count'] = self.servicePendingCalls[methodId]['count'] + 1
550         factory.dispatch(methodId, pendingCallStr)
551                     
552     @exportRpc
553     def serviceAdd(self, list):
554         '''
555         arguments: busName, srvName
556         '''
557         busName = list[0]
558         self.bus =  cache.dbusConnexion( busName['name'] )
559         self.srvName = list[1]
560         if (self.services.has_key(self.srvName) == False):            
561             self.services[self.srvName] = dbus.service.BusName(name = self.srvName, bus = self.bus)
562         return self.srvName
563
564     @exportRpc
565     def serviceRelease(self, list):
566         '''
567         arguments: busName, srvName
568         '''
569         self.srvName = list[0]
570         if (self.services.has_key(self.srvName) == True):
571             self.services.pop(self.srvName)
572             return self.srvName
573         else:
574             raise Exception(self.srvName + " do not exist")
575                    
576     @exportRpc
577     def serviceAddAgent(self, list):
578         '''
579         arguments: objectPath, xmlTemplate
580         '''
581         self.agentObjectPath = list[0]
582         xmlTemplate = list[1]
583         self.className = re.sub('/', '_', self.agentObjectPath[1:])
584         if (self.dynDBusClasses.has_key(self.className) == False):
585             self.dynDBusClasses[self.className] = DynDBusClass(self.className, self.globalCtx, self.localCtx)
586             self.dynDBusClasses[self.className].createDBusServiceFromXML(xmlTemplate)
587
588             # For Debug only
589             if (1):
590                 if (1): ## Force deletion
591                     if os.access('./MyDbusClass.py', os.R_OK) == True:
592                         os.remove('./MyDbusClass.py')
593                     
594                     if os.access('./MyDbusClass.py', os.R_OK) == False:
595                         f = open('./MyDbusClass.py', 'w')
596                         f.write(self.dynDBusClasses[self.className].class_code.exec_string)
597                         f.close()
598                         
599             self.dynDBusClasses[self.className].declare()
600
601         ## Class already exist, instanciate it if not already instanciated
602         if (self.serviceAgents.has_key(self.className) == False):
603             self.serviceAgents[self.className] = eval(self.className + "(self.bus, callback=self.srvCB, objPath=self.agentObjectPath, busName=self.srvName)", self.globalCtx, self.localCtx)
604             
605         self.serviceAgents[self.className].add_to_connection()
606         return (self.agentObjectPath)
607                     
608     @exportRpc
609     def serviceDelAgent(self, list):
610         '''
611         arguments: objectPath, xmlTemplate
612         '''
613         agentObjectPath = list[0]
614         className = re.sub('/', '_', agentObjectPath[1:])
615
616         if (self.serviceAgents.has_key(className)):
617             self.serviceAgents[self.className].remove_from_connection()
618             self.serviceAgents.pop(self.className)
619         else:
620             raise Exception(agentObjectPath + " doesn't exist!")
621         
622         return (agentObjectPath)
623                     
624     @exportRpc
625     def getVersion(self):
626         '''
627         return current version string
628         '''
629         return VERSION
630
631
632
633 ###############################################################################
634 class CloudeebusServerProtocol(WampCraServerProtocol):
635     '''
636     connexion and session authentication management
637     '''
638     
639     def onSessionOpen(self):
640         # CRA authentication options
641         self.clientAuthTimeout = 0
642         self.clientAuthAllowAnonymous = OPENDOOR
643         # CRA authentication init
644         WampCraServerProtocol.onSessionOpen(self)
645     
646     
647     def getAuthPermissions(self, key, extra):
648         return json.loads(extra.get("permissions", "[]"))
649     
650     
651     def getAuthSecret(self, key):
652         secret = CREDENTIALS.get(key, None)
653         if secret is None:
654             return None
655         # secret must be of str type to be hashed
656         return secret.encode('utf-8')
657     
658
659     def onAuthenticated(self, key, permissions):
660         if not OPENDOOR:
661             # check authentication key
662             if key is None:
663                 raise Exception("Authentication failed")
664             # check permissions, array.index throws exception
665             for req in permissions:
666                 WHITELIST.index(req)
667         # create cloudeebus service instance
668         self.cloudeebusService = CloudeebusService(permissions)
669         # register it for RPC
670         self.registerForRpc(self.cloudeebusService)
671         # register for Publish / Subscribe
672         self.registerForPubSub("", True)
673     
674     
675     def connectionLost(self, reason):
676         WampCraServerProtocol.connectionLost(self, reason)
677         if factory.getConnectionCount() == 0:
678             cache.reset()
679
680
681
682 ###############################################################################
683
684 if __name__ == '__main__':
685     
686     cache = DbusCache()
687
688     parser = argparse.ArgumentParser(description='Javascript DBus bridge.')
689     parser.add_argument('-v', '--version', action='store_true', 
690         help='print version and exit')
691     parser.add_argument('-d', '--debug', action='store_true', 
692         help='log debug info on standard output')
693     parser.add_argument('-o', '--opendoor', action='store_true',
694         help='allow anonymous access to all services')
695     parser.add_argument('-p', '--port', default='9000',
696         help='port number')
697     parser.add_argument('-c', '--credentials',
698         help='path to credentials file')
699     parser.add_argument('-w', '--whitelist',
700         help='path to whitelist file')
701     
702     args = parser.parse_args(sys.argv[1:])
703
704     if args.version:
705         print("Cloudeebus version " + VERSION)
706         exit(0)
707     
708     if args.debug:
709         log.startLogging(sys.stdout)
710     
711     OPENDOOR = args.opendoor
712     
713     if args.credentials:
714         jfile = open(args.credentials)
715         CREDENTIALS = json.load(jfile)
716         jfile.close()
717     
718     if args.whitelist:
719         jfile = open(args.whitelist)
720         WHITELIST = json.load(jfile)
721         jfile.close()
722     
723     uri = "ws://localhost:" + args.port
724     
725     factory = WampServerFactory(uri, debugWamp = args.debug)
726     factory.protocol = CloudeebusServerProtocol
727     factory.setProtocolOptions(allowHixie76 = True)
728     
729     listenWS(factory)
730     
731     DBusGMainLoop(set_as_default=True)
732     
733     reactor.run()