def __init__(self, reactor, udpListener=None):
     super(NatConnectivity, self).__init__()
     self.reactor = reactor
     self.udpListener = udpListener # A listener for UDP communication
     self._puncher = Puncher(self.reactor, self, self.udpListener) # the puncher to establish the connection
    def connectTCP(self, \
                   host=None, \
                   port=None, \
                   remoteUri=None, \
                   factory=None, \
                   timeout=30, \
                   bindAddress=None, \
                   myUri=None):
        """
        Force a connection with another user through NATs
        helped by the ConnectionBroker.
        It needs at least one between (host, port) and 'remoteUri'
        
        @param host: a host ip address, default None
        @param port: a port number, the UDP remote port (it's mandatory if host is present)
        @param remoteUri : The remote endpoint's uri, default None
        @param factory: a twisted.internet.protocol.ClientFactory instance 
        @param timeout: number of seconds to wait before assuming the connection has failed.  (not implemented)
        @param bindAddress: a (host, port) tuple of local address to bind to, or None.
        @param myUri : The uri for future incoming connection request
        
        @return :  An object implementing IConnector.
        This connector will call various callbacks on the factory
        when a connection is made,failed, or lost
        - see ClientFactory docs for details.
        """
        d = defer.Deferred()
        d_conn = defer.Deferred()
        
        if host == None:
            remoteAddress = None
        else:
            remoteAddress = (host, port)
            
        localPort = 0 # any
        if bindAddress != None:
            localPort = bindAddress[1]
            
        if self._puncher == None:
            self._puncher = Puncher(self.reactor, self)

        if self._puncher.getClientFactory() == None and factory == None:
            # Error
            d.errback(failure.DefaultException('You have to specify a factory'))
        elif factory != None:
            self._puncher.setClientFactory(factory)
            self._puncher.c_factory = factory

        def fail(failure):
            """ Error in NAT Traversal TCP """
            print 'ERROR in NAT Traversal (registration):', failure#.getErrorMessage()

        def connection_succeed(result):
            print 'connection succeed:', result
            d_conn.callback(result)

        def connection_fail(failure):
            print 'connection fail', failure
            d = defer.Deferred()
            d.errback(failure)

        def discovery_fail(failure):
            d = defer.Deferred()
            d.errback(failure)

        def discovery_succeed(publicAddress):
            self.publicAddr = publicAddress
            #print 'Address discovered:', publicAddress
            if self.publicAddr != None:
                d = defer.Deferred()
                d = self._puncher.sndConnectionRequest(remoteUri, remoteAddress)
                d.addCallback(connection_succeed)
                d.addErrback(connection_fail)
            else:
                self.d.errback('The NAT doesn\'t allow inbound TCP connection')

        def registrationSucceed(result):
            
            print 'Registration to the SN Connection Broker has be done' 
            print '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'  
            
            # Discovery external address
            d = self.publicAddrDiscovery(localPort)
            d.addCallback(discovery_succeed)
            d.addErrback(discovery_fail)
            
        # Registration to Connection Broker for incoming connection
        if myUri != None and not self._puncher.registered:
            print '\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' 
            d = self._puncher.sndRegistrationRequest(myUri)
            d.addCallback(registrationSucceed)
            d.addErrback(fail)
        else:
            d = self.publicAddrDiscovery(localPort)
            d.addCallback(discovery_succeed)
            d.addErrback(discovery_fail)
            
        return d_conn
class NatConnectivity(NatManager, object):

    """
    Interface with the application.
    Discover NAT information (type and mapping) and force a connection
    through NATs with the Super Node Connection Broker's help
    or by a directly UDP connection with the remote endpoint.
    """
  
    logging.basicConfig()
    log = logging.getLogger("ntcp")
    log.setLevel(logging.DEBUG)

    def __init__(self, reactor, udpListener=None):
        super(NatConnectivity, self).__init__()
        self.reactor = reactor
        self.udpListener = udpListener # A listener for UDP communication
        self._puncher = Puncher(self.reactor, self, self.udpListener) # the puncher to establish the connection

    def datagramReceived(self, message, fromAddr):
        """A link to the internal datagramReceived function"""
        self._puncher.datagramReceived(message, fromAddr)
    
    def holePunching(self, uri, myUri):
        """
        Starts the hole punching procedure to punch a hole
        for UDP communication with peer identified by URI

        @param uri: the remote peer to contact
        @param myUri: personal uri 
        """
        return self._puncher.sndLookupRequest(remoteUri=uri, localUri=myUri)

    def setServerFactory(self, factory):
        """Sets a factory for TCP connection
        @param factory - a twisted.internet.protocol.*Factory instance 
        """
        self._puncher.setServerFactory(factory)
        self._puncher.s_factory = factory
        
    def getFactory(self):
        """Gets the TCP factory
        @return: factory - a twisted.internet.protocol.ServerFactory instance
        """
        return self._puncher.getFactory()

    def natDiscovery(self, bloking = 1):
        """
        Discover NAT presence and information about.
        
        @param bloking: if 0 makes NAT discovery in non bloking mode (default 1)
        @return void :
        """
        if bloking:
            return self._natDiscoveryDefer()
        else:
            return self._natDiscoveryThread()
 
    def listenTCP(self, port=0, factory=None, backlog=5, interface='',  myUri=None):
        """Make a registration to CB and listen for incoming connection request
        
        @param port: a port number on which to listen, default to 0 (any) (only default implemented)
        @param factory: a twisted.internet.protocol.ServerFactory instance 
        @param backlog: size of the listen queue (not implemented)
        @param interface: the hostname to bind to, defaults to '' (all) (not implemented)
        @param myUri: the user's URI for registration to Connection Broker
        return: void
        """
        d = defer.Deferred()
        # self._puncher = Puncher(self.reactor, self, self.udpListener)
    
        if factory != None:
            # Sets the factory for TCP connection
            self.setServerFactory(factory)

        d = self._puncher.sndRegistrationRequest(myUri)
        return NtcpFactory(d)
    
    def connectTCP(self, \
                   host=None, \
                   port=None, \
                   remoteUri=None, \
                   factory=None, \
                   timeout=30, \
                   bindAddress=None, \
                   myUri=None):
        """
        Force a connection with another user through NATs
        helped by the ConnectionBroker.
        It needs at least one between (host, port) and 'remoteUri'
        
        @param host: a host ip address, default None
        @param port: a port number, the UDP remote port (it's mandatory if host is present)
        @param remoteUri : The remote endpoint's uri, default None
        @param factory: a twisted.internet.protocol.ClientFactory instance 
        @param timeout: number of seconds to wait before assuming the connection has failed.  (not implemented)
        @param bindAddress: a (host, port) tuple of local address to bind to, or None.
        @param myUri : The uri for future incoming connection request
        
        @return :  An object implementing IConnector.
        This connector will call various callbacks on the factory
        when a connection is made,failed, or lost
        - see ClientFactory docs for details.
        """
        d = defer.Deferred()
        d_conn = defer.Deferred()
        
        if host == None:
            remoteAddress = None
        else:
            remoteAddress = (host, port)
            
        localPort = 0 # any
        if bindAddress != None:
            localPort = bindAddress[1]
            
        if self._puncher == None:
            self._puncher = Puncher(self.reactor, self)

        if self._puncher.getClientFactory() == None and factory == None:
            # Error
            d.errback(failure.DefaultException('You have to specify a factory'))
        elif factory != None:
            self._puncher.setClientFactory(factory)
            self._puncher.c_factory = factory

        def fail(failure):
            """ Error in NAT Traversal TCP """
            print 'ERROR in NAT Traversal (registration):', failure#.getErrorMessage()

        def connection_succeed(result):
            print 'connection succeed:', result
            d_conn.callback(result)

        def connection_fail(failure):
            print 'connection fail', failure
            d = defer.Deferred()
            d.errback(failure)

        def discovery_fail(failure):
            d = defer.Deferred()
            d.errback(failure)

        def discovery_succeed(publicAddress):
            self.publicAddr = publicAddress
            #print 'Address discovered:', publicAddress
            if self.publicAddr != None:
                d = defer.Deferred()
                d = self._puncher.sndConnectionRequest(remoteUri, remoteAddress)
                d.addCallback(connection_succeed)
                d.addErrback(connection_fail)
            else:
                self.d.errback('The NAT doesn\'t allow inbound TCP connection')

        def registrationSucceed(result):
            
            print 'Registration to the SN Connection Broker has be done' 
            print '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'  
            
            # Discovery external address
            d = self.publicAddrDiscovery(localPort)
            d.addCallback(discovery_succeed)
            d.addErrback(discovery_fail)
            
        # Registration to Connection Broker for incoming connection
        if myUri != None and not self._puncher.registered:
            print '\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' 
            d = self._puncher.sndRegistrationRequest(myUri)
            d.addCallback(registrationSucceed)
            d.addErrback(fail)
        else:
            d = self.publicAddrDiscovery(localPort)
            d.addCallback(discovery_succeed)
            d.addErrback(discovery_fail)
            
        return d_conn

    def getP2PConfiguration(self, \
                            host=None, \
                            port=None, \
                            remoteUri=None):
        """
        Gets the local and remote NAT type.
        It requires at least one between parameters (host, port) and remoteUri

        @param host: a host name, default None
        @param port: a port number, the UDP remote port (it's mandatory if host is present)
        @param remoteUri : The remote endpoint's uri, default None
        @return: defer the callback result is (local NAT type, remote NAT type)
        """
        
        d = defer.Deferred()
        if host == None:
            remoteAddress = None
        else:
            remoteAddress = (host, port)

        def fail(failure):
            """ Error in NTCP"""
            print 'ERROR in NTCP (get configuration):', failure.getErrorMessage()
            return None
        
        def discoveryConfigurationSucceed(remote):
            # Returns the result
            if remote == 'Unknown':
                d.callback((self.type, None))
            else:
                d.callback((self.type, self.remoteNat.type))

        def discoverySucceed(result):
            # Asks for remote NAT configuration
            d = defer.Deferred()
            d = self._puncher.sndConfigurationRequest(\
                remoteUri, remoteAddress)
            d.addCallback(discoveryConfigurationSucceed)
            d.addErrback(fail)
            
        
        if self.discover == 0:
            # Discover local NAT configuration
            d = defer.Deferred()
            d = self.natDiscovery()
            d.addCallback(discoverySucceed)
            d.addErrback(fail)
        else:
            # We know already the NAT configuration
            discoverySucceed(None)

        return d

    def getlocalNatConf(self):
        """
        Gets the NAT objet with the NAT's configuration

        @return localNat: an ntcp.NatConnectivity.Nat object
        """
        return self.localNat