Example #1
0
 def __init__(self, reactor):
     self.reactor = reactor
     self.sm = StateMachine(self.DEFAULT)
     self.device = None
     self.externalIP = None
     self.cleaningupCount = 0
     self.sm.appendCallback(self._onCleanup,
                            dest=self.CLEANINGUP,
                            single=True)
     self._initialize()
Example #2
0
 def __init__(self, seedNodes, reactor):
     self.seedNodes = seedNodes
     self.reactor = reactor
     self.sm = StateMachine(self.OFFLINE)
     self.services = {}
     self.nodeTable = None
     self.nodeTableRefresher = None
     self.rpcSocket = None
     self.dhtClient = None
     self.locationCache = None
     self.profile = None
     self.listener = None
     self.permissions = None
Example #3
0
 def __init__(self, reactor):
     self.reactor = reactor
     self.sm = StateMachine(self.DEFAULT)
     self.device = None
     self.externalIP = None
     self.cleaningupCount = 0
     self.sm.appendCallback(self._onCleanup, dest=self.CLEANINGUP, single=True)
     self._initialize()
Example #4
0
 def __init__( self, seedNodes, reactor ) :
     self.seedNodes = seedNodes
     self.reactor = reactor
     self.sm = StateMachine( self.OFFLINE )
     self.services = {}
     self.nodeTable = None
     self.nodeTableRefresher = None
     self.rpcSocket = None
     self.dhtClient = None
     self.locationCache = None
     self.profile = None
     self.listener = None
     self.permissions = None
Example #5
0
class UPnpMapper(object):
    DEFAULT = 0
    INITIALIZING = 1
    READY = 2
    CLEANINGUP = 3
    CLOSED = 4

    def __init__(self, reactor):
        self.reactor = reactor
        self.sm = StateMachine(self.DEFAULT)
        self.device = None
        self.externalIP = None
        self.cleaningupCount = 0
        self.sm.appendCallback(self._onCleanup,
                               dest=self.CLEANINGUP,
                               single=True)
        self._initialize()

    def shutdown(self, callback=None):
        if self.sm.current() in (self.CLEANINGUP, self.CLOSED):
            return None
        if self.sm.current() == self.READY:
            self.sm.change(self.CLEANINGUP)
        else:
            self.sm.change(self.CLOSED)
        if self.sm.current() == self.CLOSED:
            return None

        def onClosed():
            op.notify()

        def doCancel():
            self.sm.removeCallback(callbackId)

        callbackId = self.sm.appendCallback(onClosed,
                                            dest=self.CLOSED,
                                            single=True)
        op = AsyncOp(callback, doCancel)
        return op

    def _onCleanup(self):
        if self.cleaningupCount == 0:
            self.sm.change(self.CLOSED)

    def _initialize(self):
        class Dummy:
            pass

        obj = Dummy()

        def onError():
            self.sm.removeCallback(callbackId)
            self.sm.change(self.CLOSED)

        def onDiscover(device):
            if device is None:
                onError()
                return
            self.device = device
            obj.op = UPnpActions.getExternalIP(device, self.reactor,
                                               onExternalIP)

        def onExternalIP(externalIP):
            if externalIP is None:
                onError()
                return
            self.externalIP = externalIP
            self.sm.removeCallback(callbackId)
            self.sm.change(self.READY)

        self.sm.change(self.INITIALIZING)
        obj.op = UPnpActions.findDevice(self.reactor, onDiscover)
        callbackId = self.sm.insertCallback(lambda: obj.op.cancel(),
                                            src=self.INITIALIZING,
                                            single=True)

    def addMapping(self, localIP, localPort, callback=None):
        class Dummy:
            pass

        obj = Dummy()
        if self.sm.current() not in (self.INITIALIZING, self.READY):
            timerOp = self.reactor.callLater(0, lambda: obj.op.notify(None))
            obj.op = AsyncOp(callback, timerOp.cancel)
            return obj.op

        def doReady():
            obj.attempt = 0
            doAttempt()

        def doAttempt():
            if obj.attempt == 3:
                obj.op.notify(None)
                return
            obj.attempt += 1
            obj.externalPort = randint(10000, 20000)
            desc = 'CSpace_t%d' % int(time())
            obj.addOp = UPnpActions.addMapping(self.device, obj.externalPort,
                                               'TCP', localPort, localIP, desc,
                                               self.reactor, onAdd)
            obj.callbackId = self.sm.insertCallback(onAbort,
                                                    src=self.READY,
                                                    single=True)
            obj.op.setCanceler(onCancel)

        def onCancel():
            obj.addOp.cancel()
            self.sm.removeCallback(obj.callbackId)

        def onAbort():
            obj.addOp.cancel()
            obj.op.notify(None)

        def onAdd(result):
            self.sm.removeCallback(obj.callbackId)
            if not result:
                doAttempt()
                return
            mapping = (self.externalIP, obj.externalPort)
            obj.op.notify(mapping)
            self.sm.insertCallback(onCleanup,
                                   dest=self.CLEANINGUP,
                                   single=True)

        def onCleanup():
            self.cleaningupCount += 1
            UPnpActions.delMapping(self.device, obj.externalPort, 'TCP',
                                   self.reactor, onDelMapping)

        def onDelMapping(result):
            self.cleaningupCount -= 1
            self._onCleanup()

        if self.sm.current() == self.INITIALIZING:

            def checkReady():
                if self.sm.current() == self.READY:
                    obj.op.setCanceler(None)
                    doReady()
                    return
                obj.op.notify(None)

            obj.callbackId = self.sm.insertCallback(checkReady,
                                                    src=self.INITIALIZING,
                                                    single=True)
            obj.op = AsyncOp(callback,
                             lambda: self.sm.removeCallback(obj.callbackId))
            return obj.op
        obj.op = AsyncOp(callback, None)
        doReady()
        return obj.op
Example #6
0
class UserSession(object):
    (OFFLINE, CONNECTING, ONLINE, DISCONNECTING) = range(4)

    def __init__(self, seedNodes, reactor):
        self.seedNodes = seedNodes
        self.reactor = reactor
        self.sm = StateMachine(self.OFFLINE)
        self.services = {}
        self.nodeTable = None
        self.nodeTableRefresher = None
        self.rpcSocket = None
        self.dhtClient = None
        self.locationCache = None
        self.profile = None
        self.listener = None
        self.permissions = None
        #self.nodeRunner = None

    def getProfile(self):
        return self.profile

    def getPermissions(self):
        return self.permissions

    def isOnline(self):
        return self.sm.current() == self.ONLINE

    def registerService(self, serviceName, serviceCallback):
        assert isValidServiceName(serviceName)
        if serviceName in self.services:
            return False
        self.services[serviceName] = serviceCallback
        return True

    def unregisterService(self, serviceName):
        if self.services.pop(serviceName, None) is None:
            return False
        return True

    def goOnline(self, profile):
        assert self.sm.current() == self.OFFLINE
        obj = Dummy()

        def doCleanup():
            if obj.initNodeTableOp is not None:
                obj.initNodeTableOp.cancel()
            if obj.nodeTableRefresher is not None:
                obj.nodeTableRefresher.close()
            if obj.listener is not None:
                obj.listener.close()
            obj.rpcSocket.close()

        def onOffline():
            #self.nodeRunner.close()
            #self.nodeRunner = None
            self.listener.close()
            self.listener = None
            self.permissions = None
            self.locationCache.close()
            self.locationCache = None
            self.nodeTableRefresher.close()
            self.nodeTableRefresher = None
            _saveNodeCache(self.nodeTable)
            self.nodeTable = None
            self.dhtClient = None
            self.rpcSocket.close()
            self.rpcSocket = None
            self.profile = None

        def onListenerStart(result):
            if not result:
                self.sm.removeCallback(obj.callbackId)
                doCleanup()
                self.sm.change(self.OFFLINE)
                return
            obj.listener.setCloseCallback(self._onListenerClose)
            obj.listener.setIncomingCallback(self._onIncoming)
            self.profile = profile
            self.rpcSocket = obj.rpcSocket
            self.dhtClient = obj.dhtClient
            self.nodeTable = obj.nodeTable
            self.nodeTableRefresher = obj.nodeTableRefresher
            self.locationCache = LocationCache(self.dhtClient, self.nodeTable,
                                               self.reactor)
            self.listener = obj.listener
            self.permissions = Permissions(profile, self.services.keys())
            if self.permissions.isModified():
                self.permissions.savePermissions()
            #self.nodeRunner = NodeRunner( self.nodeTable, self.reactor )
            self.sm.removeCallback(obj.callbackId)
            self.sm.appendCallback(onOffline, dest=self.OFFLINE, single=True)
            self.sm.change(self.ONLINE)

        def onNodeTableInit():
            obj.initNodeTableOp = None
            obj.nodeTableRefresher = NodeTableRefresher(
                obj.nodeTable, obj.dhtClient, self.reactor)
            updateLevelStore = UpdateLevelStore(profile)
            obj.listener = UserListener(profile.name, profile.rsaKey,
                                        updateLevelStore, obj.dhtClient,
                                        obj.nodeTable, self.reactor)
            obj.listener.start(onListenerStart)

        self.sm.change(self.CONNECTING)
        obj.callbackId = self.sm.insertCallback(doCleanup,
                                                src=self.CONNECTING,
                                                single=True)
        obj.nodeTable = NodeTable(self.seedNodes)
        #_loadNodeCache( obj.nodeTable )
        udpSock = socket(AF_INET, SOCK_DGRAM)
        udpSock.bind(('', 0))
        obj.rpcSocket = RPCSocket(udpSock, self.reactor)
        obj.dhtClient = DHTClient(obj.rpcSocket, nodeTracker=obj.nodeTable)
        obj.initNodeTableOp = initNodeTable(obj.nodeTable, obj.dhtClient,
                                            self.reactor, onNodeTableInit)
        obj.nodeTableRefresher = None
        obj.listener = None

    def _onListenerClose(self):
        self.sm.change(self.OFFLINE)

    def _rejectIncoming(self, sslConn, callback=None):
        def onReject(sslConn):
            if sslConn is None:
                op.notify(False)
                return
            sslAbort(sslConn)
            op.notify(True)

        rejectOp = acceptIncoming(False, sslConn, self.reactor, onReject)
        op = AsyncOp(callback, rejectOp.cancel)
        return op

    def _acceptIncoming(self,
                        sslConn,
                        serviceName,
                        peerKey,
                        contactName,
                        incomingName,
                        callback=None):
        def onAccept(sslConn):
            if sslConn is None:
                op.notify(False)
                return
            serviceCallback = self.services.get(serviceName)
            if serviceCallback is None:
                sslAbort(sslConn)
                op.notify(False)
                return
            serviceCallback(sslConn, peerKey, contactName, incomingName)
            op.notify(True)

        acceptOp = acceptIncoming(True, sslConn, self.reactor, onAccept)
        op = AsyncOp(callback, acceptOp.cancel)
        return op

    def _promptIncoming(self,
                        sslConn,
                        serviceName,
                        peerKey,
                        contactName,
                        incomingName,
                        callback=None):
        def doCancel():
            promptOp.cancel()
            sslAbort(sslConn)

        def onPromptResult(promptResult):
            if not promptResult:
                rejectOp = self._rejectIncoming(sslConn, op.notify)
                op.setCanceler(rejectOp.cancel)
            else:
                acceptOp = self._acceptIncoming(sslConn, serviceName, peerKey,
                                                contactName, incomingName,
                                                op.notify)
                op.setCanceler(acceptOp.cancel)

        promptName = contactName
        if not promptName:
            promptName = '(Unknown %s)' % incomingName
        promptOp = IncomingPromptWindow(promptName, serviceName, self.reactor,
                                        onPromptResult).getOp()
        op = AsyncOp(callback, doCancel)
        return op

    def _onIncoming(self, sslConn, peerKey, incomingName, serviceName):
        assert self.sm.current() == self.ONLINE
        if not isValidUserName(incomingName):
            sslAbort(sslConn)
            return
        if not isValidServiceName(serviceName):
            sslAbort(sslConn)
            return
        contact = self.profile.getContactByPublicKey(peerKey)
        if contact is None:
            contactName = ''
        else:
            contactName = contact.name

        if serviceName not in self.services:
            action = ACCESS_DENY
        else:
            action = self.permissions.execute(contactName, serviceName)

        def onActionDone(result):
            self.sm.removeCallback(callbackId)

        if action == ACCESS_DENY:
            actionOp = self._rejectIncoming(sslConn, onActionDone)
        elif action == ACCESS_ALLOW:
            actionOp = self._acceptIncoming(sslConn, serviceName, peerKey,
                                            contactName, incomingName,
                                            onActionDone)
        else:
            assert action == ACCESS_PROMPT
            actionOp = self._promptIncoming(sslConn, serviceName, peerKey,
                                            contactName, incomingName,
                                            onActionDone)
        callbackId = self.sm.insertCallback(actionOp.cancel,
                                            src=self.ONLINE,
                                            single=True)

    def goOffline(self):
        assert self.sm.current() == self.ONLINE

        def onStop(result):
            self.sm.change(self.OFFLINE)

        self.sm.change(self.DISCONNECTING)
        self.listener.stop(onStop)

    def shutdown(self):
        self.sm.change(self.OFFLINE)

    def probeUserOnline(self, publicKey, callback=None):
        assert self.sm.current() == self.ONLINE

        def onStateChange():
            lookupOp.cancel()
            op.notify(False)

        def doCancel():
            lookupOp.cancel()
            self.sm.removeCallback(callbackId)

        def onLookup(location):
            self.sm.removeCallback(callbackId)
            if location is None:
                op.notify(False)
                return
            if (not location.directLocations) and (
                    not location.routedLocations):
                op.notify(False)
                return
            op.notify(True)

        lookupOp = self.locationCache.refreshUser(publicKey, onLookup)
        callbackId = self.sm.insertCallback(onStateChange,
                                            src=self.ONLINE,
                                            single=True)
        op = AsyncOp(callback, doCancel)
        return op

    def connectTo(self, publicKey, serviceName, callback=None):
        assert self.sm.current() == self.ONLINE

        def onStateChange():
            connectOp.cancel()
            op.notify(-1, None)

        def doCancel():
            connectOp.cancel()
            self.sm.removeCallback(callbackId)

        def onConnect(sslConn):
            self.sm.removeCallback(callbackId)
            if sslConn is None:
                op.notify(-1, None)
                return
            op.notify(0, sslConn)

        connectOp = userServiceConnect(self.profile.name, self.profile.rsaKey,
                                       self.listener.getPublicIP(), publicKey,
                                       serviceName, self.locationCache,
                                       self.reactor, onConnect)
        callbackId = self.sm.insertCallback(onStateChange,
                                            src=self.ONLINE,
                                            single=True)
        op = AsyncOp(callback, doCancel)
        return op
Example #7
0
class UserSession( object ) :
    (OFFLINE,CONNECTING,ONLINE,DISCONNECTING) = range(4)

    def __init__( self, seedNodes, reactor ) :
        self.seedNodes = seedNodes
        self.reactor = reactor
        self.sm = StateMachine( self.OFFLINE )
        self.services = {}
        self.nodeTable = None
        self.nodeTableRefresher = None
        self.rpcSocket = None
        self.dhtClient = None
        self.locationCache = None
        self.profile = None
        self.listener = None
        self.permissions = None
        #self.nodeRunner = None

    def getProfile( self ) : return self.profile
    def getPermissions( self ) : return self.permissions

    def isOnline( self ) : return self.sm.current() == self.ONLINE

    def registerService( self, serviceName, serviceCallback ) :
        assert isValidServiceName(serviceName)
        if serviceName in self.services :
            return False
        self.services[serviceName] = serviceCallback
        return True

    def unregisterService( self, serviceName ) :
        if self.services.pop(serviceName,None) is None :
            return False
        return True

    def goOnline( self, profile ) :
        assert self.sm.current() == self.OFFLINE
        obj = Dummy()
        def doCleanup() :
            if obj.initNodeTableOp is not None :
                obj.initNodeTableOp.cancel()
            if obj.nodeTableRefresher is not None :
                obj.nodeTableRefresher.close()
            if obj.listener is not None :
                obj.listener.close()
            obj.rpcSocket.close()
        def onOffline() :
            #self.nodeRunner.close()
            #self.nodeRunner = None
            self.listener.close()
            self.listener = None
            self.permissions = None
            self.locationCache.close()
            self.locationCache = None
            self.nodeTableRefresher.close()
            self.nodeTableRefresher = None
            _saveNodeCache( self.nodeTable )
            self.nodeTable = None
            self.dhtClient = None
            self.rpcSocket.close()
            self.rpcSocket = None
            self.profile = None
        def onListenerStart( result ) :
            if not result :
                self.sm.removeCallback( obj.callbackId )
                doCleanup()
                self.sm.change( self.OFFLINE )
                return
            obj.listener.setCloseCallback( self._onListenerClose )
            obj.listener.setIncomingCallback( self._onIncoming )
            self.profile = profile
            self.rpcSocket = obj.rpcSocket
            self.dhtClient = obj.dhtClient
            self.nodeTable = obj.nodeTable
            self.nodeTableRefresher = obj.nodeTableRefresher
            self.locationCache = LocationCache( self.dhtClient,
                    self.nodeTable, self.reactor )
            self.listener = obj.listener
            self.permissions = Permissions( profile, self.services.keys() )
            if self.permissions.isModified() :
                self.permissions.savePermissions()
            #self.nodeRunner = NodeRunner( self.nodeTable, self.reactor )
            self.sm.removeCallback( obj.callbackId )
            self.sm.appendCallback( onOffline, dest=self.OFFLINE, single=True )
            self.sm.change( self.ONLINE )
        def onNodeTableInit() :
            obj.initNodeTableOp = None
            obj.nodeTableRefresher = NodeTableRefresher(
                    obj.nodeTable, obj.dhtClient, self.reactor )
            updateLevelStore = UpdateLevelStore( profile )
            obj.listener = UserListener( profile.name, profile.keyId, 
                    profile.rsaKey, updateLevelStore,
                    obj.dhtClient, obj.nodeTable, self.reactor )
            obj.listener.start( onListenerStart )
        self.sm.change( self.CONNECTING )
        obj.callbackId = self.sm.insertCallback( doCleanup,
                src=self.CONNECTING, single=True )
        obj.nodeTable = NodeTable( self.seedNodes )
        #_loadNodeCache( obj.nodeTable )
        udpSock = socket( AF_INET, SOCK_DGRAM )
        udpSock.bind( ('',0) )
        obj.rpcSocket = RPCSocket( udpSock, self.reactor )
        obj.dhtClient = DHTClient( obj.rpcSocket,
                nodeTracker=obj.nodeTable )
        obj.initNodeTableOp = initNodeTable( obj.nodeTable,
                obj.dhtClient, self.reactor, onNodeTableInit )
        obj.nodeTableRefresher = None
        obj.listener = None

    def _onListenerClose( self ) :
        self.sm.change( self.OFFLINE )

    def _rejectIncoming( self, sslConn, callback=None ) :
        def onReject( sslConn ) :
            if sslConn is None :
                op.notify( False )
                return
            sslAbort( sslConn )
            op.notify( True )
        rejectOp = acceptIncoming( False, sslConn, self.reactor,
                onReject )
        op = AsyncOp( callback, rejectOp.cancel )
        return op

    def _acceptIncoming( self, sslConn, serviceName, peerKey,
            contactName, peerKeyID, incomingName, callback=None ) :
        def onAccept( sslConn ) :
            if sslConn is None :
                op.notify( False )
                return
            serviceCallback = self.services.get( serviceName )
            if serviceCallback is None :
                sslAbort( sslConn )
                op.notify( False )
                return
            serviceCallback( sslConn, peerKey, contactName, peerKeyID,
                    incomingName )
            op.notify( True )
        acceptOp = acceptIncoming( True, sslConn, self.reactor,
                onAccept )
        op = AsyncOp( callback, acceptOp.cancel )
        return op

    # def _promptIncoming( self, sslConn, serviceName, peerKey,
    #         contactName, incomingName, callback=None ) :
    #     def doCancel() :
    #         promptOp.cancel()
    #         sslAbort( sslConn )
    #     def onPromptResult( promptResult ) :
    #         if not promptResult :
    #             rejectOp = self._rejectIncoming( sslConn, op.notify )
    #             op.setCanceler( rejectOp.cancel )
    #         else :
    #             acceptOp = self._acceptIncoming( sslConn, serviceName,
    #                     peerKey, contactName, incomingName, op.notify )
    #             op.setCanceler( acceptOp.cancel )
    #     promptName = contactName
    #     if not promptName :
    #         promptName = '(Unknown %s)' % incomingName
    #     promptOp = IncomingPromptWindow( promptName, serviceName,
    #             self.reactor, onPromptResult ).getOp()
    #     op = AsyncOp( callback, doCancel )
    #     return op

    def _onIncoming( self, sslConn, peerKey, incomingName, peerKeyID,
            serviceName ) :
        assert self.sm.current() == self.ONLINE
        if not isValidUserName(incomingName) :
            sslAbort( sslConn )
            return
        if not isValidServiceName(serviceName) :
            sslAbort( sslConn )
            return
        contact = self.profile.getContactByPublicKey( peerKey )
        if contact is None :
            contactName = peerKeyID
        else :
            contactName = contact.name
        if serviceName not in self.services :
            action = ACCESS_DENY
        else :
            action = self.permissions.execute( contactName,
                    serviceName )
        def onActionDone( result ) :
            self.sm.removeCallback( callbackId )
        if action == ACCESS_DENY :
            actionOp = self._rejectIncoming( sslConn, onActionDone )
        else:
        # elif action == ACCESS_ALLOW  :
            actionOp = self._acceptIncoming( sslConn, serviceName,
                    peerKey, contactName, peerKeyID, incomingName, onActionDone )
        # else :
        #     assert action == ACCESS_PROMPT
        #     actionOp = self._promptIncoming( sslConn, serviceName,
        #             peerKey, contactName, incomingName, onActionDone )
        callbackId = self.sm.insertCallback( actionOp.cancel,
                src=self.ONLINE, single=True )

    def goOffline( self ) :
        assert self.sm.current() == self.ONLINE
        def onStop( result ) :
            self.sm.change( self.OFFLINE )
        self.sm.change( self.DISCONNECTING )
        self.listener.stop( onStop )

    def shutdown( self ) :
        self.sm.change( self.OFFLINE )

    def probeUserOnline( self, publicKey, callback=None ) :
        assert self.sm.current() == self.ONLINE
        def onStateChange() :
            lookupOp.cancel()
            op.notify( False )
        def doCancel() :
            lookupOp.cancel()
            self.sm.removeCallback( callbackId )
        def onLookup( location ) :
            self.sm.removeCallback( callbackId )
            if location is None :
                op.notify( False )
                return
            if (not location.directLocations) and (not location.routedLocations) :
                op.notify( False )
                return
            op.notify( True )
        lookupOp = self.locationCache.refreshUser( publicKey,
                onLookup )
        callbackId = self.sm.insertCallback( onStateChange,
                src=self.ONLINE, single=True )
        op = AsyncOp( callback, doCancel )
        return op

    def connectTo( self, publicKey, serviceName, callback=None ) :
        assert self.sm.current() == self.ONLINE
        def onStateChange() :
            connectOp.cancel()
            op.notify( -1, None )
        def doCancel() :
            connectOp.cancel()
            self.sm.removeCallback( callbackId )
        def onConnect( sslConn ) :
            self.sm.removeCallback( callbackId )
            if sslConn is None :
                op.notify( -1, None )
                return
            op.notify( 0, sslConn )
        connectOp = userServiceConnect( self.profile.name, self.profile.keyId,
                self.profile.rsaKey, self.listener.getPublicIP(),
                publicKey, serviceName, self.locationCache,
                self.reactor, onConnect )
        callbackId = self.sm.insertCallback( onStateChange,
                src=self.ONLINE, single=True )
        op = AsyncOp( callback, doCancel )
        return op
Example #8
0
class UPnpMapper(object):
    DEFAULT = 0
    INITIALIZING = 1
    READY = 2
    CLEANINGUP = 3
    CLOSED = 4

    def __init__(self, reactor):
        self.reactor = reactor
        self.sm = StateMachine(self.DEFAULT)
        self.device = None
        self.externalIP = None
        self.cleaningupCount = 0
        self.sm.appendCallback(self._onCleanup, dest=self.CLEANINGUP, single=True)
        self._initialize()

    def shutdown(self, callback=None):
        if self.sm.current() in (self.CLEANINGUP, self.CLOSED):
            return None
        if self.sm.current() == self.READY:
            self.sm.change(self.CLEANINGUP)
        else:
            self.sm.change(self.CLOSED)
        if self.sm.current() == self.CLOSED:
            return None

        def onClosed():
            op.notify()

        def doCancel():
            self.sm.removeCallback(callbackId)

        callbackId = self.sm.appendCallback(onClosed, dest=self.CLOSED, single=True)
        op = AsyncOp(callback, doCancel)
        return op

    def _onCleanup(self):
        if self.cleaningupCount == 0:
            self.sm.change(self.CLOSED)

    def _initialize(self):
        class Dummy:
            pass

        obj = Dummy()

        def onError():
            self.sm.removeCallback(callbackId)
            self.sm.change(self.CLOSED)

        def onDiscover(device):
            if device is None:
                onError()
                return
            self.device = device
            obj.op = UPnpActions.getExternalIP(device, self.reactor, onExternalIP)

        def onExternalIP(externalIP):
            if externalIP is None:
                onError()
                return
            self.externalIP = externalIP
            self.sm.removeCallback(callbackId)
            self.sm.change(self.READY)

        self.sm.change(self.INITIALIZING)
        obj.op = UPnpActions.findDevice(self.reactor, onDiscover)
        callbackId = self.sm.insertCallback(lambda: obj.op.cancel(), src=self.INITIALIZING, single=True)

    def addMapping(self, localIP, localPort, callback=None):
        class Dummy:
            pass

        obj = Dummy()
        if self.sm.current() not in (self.INITIALIZING, self.READY):
            timerOp = self.reactor.callLater(0, lambda: obj.op.notify(None))
            obj.op = AsyncOp(callback, timerOp.cancel)
            return obj.op

        def doReady():
            obj.attempt = 0
            doAttempt()

        def doAttempt():
            if obj.attempt == 3:
                obj.op.notify(None)
                return
            obj.attempt += 1
            obj.externalPort = randint(10000, 20000)
            desc = "CSpace_t%d" % int(time())
            obj.addOp = UPnpActions.addMapping(
                self.device, obj.externalPort, "TCP", localPort, localIP, desc, self.reactor, onAdd
            )
            obj.callbackId = self.sm.insertCallback(onAbort, src=self.READY, single=True)
            obj.op.setCanceler(onCancel)

        def onCancel():
            obj.addOp.cancel()
            self.sm.removeCallback(obj.callbackId)

        def onAbort():
            obj.addOp.cancel()
            obj.op.notify(None)

        def onAdd(result):
            self.sm.removeCallback(obj.callbackId)
            if not result:
                doAttempt()
                return
            mapping = (self.externalIP, obj.externalPort)
            obj.op.notify(mapping)
            self.sm.insertCallback(onCleanup, dest=self.CLEANINGUP, single=True)

        def onCleanup():
            self.cleaningupCount += 1
            UPnpActions.delMapping(self.device, obj.externalPort, "TCP", self.reactor, onDelMapping)

        def onDelMapping(result):
            self.cleaningupCount -= 1
            self._onCleanup()

        if self.sm.current() == self.INITIALIZING:

            def checkReady():
                if self.sm.current() == self.READY:
                    obj.op.setCanceler(None)
                    doReady()
                    return
                obj.op.notify(None)

            obj.callbackId = self.sm.insertCallback(checkReady, src=self.INITIALIZING, single=True)
            obj.op = AsyncOp(callback, lambda: self.sm.removeCallback(obj.callbackId))
            return obj.op
        obj.op = AsyncOp(callback, None)
        doReady()
        return obj.op