class Node(object): """ Create a new Node for communication with an NDN hub with the given Transport object and connectionInfo. :param Transport transport: An object of a subclass of Transport used for communication. :param Transport.ConnectionInfo connectionInfo: An object of a subclass of Transport.ConnectionInfo to be used to connect to the transport. """ def __init__(self, transport, connectionInfo): self._transport = transport self._connectionInfo = connectionInfo self._pendingInterestTable = PendingInterestTable() self._interestFilterTable = InterestFilterTable() self._registeredPrefixTable = RegisteredPrefixTable( self._interestFilterTable) self._delayedCallTable = DelayedCallTable() # An array of function objects self._onConnectedCallbacks = [] self._commandInterestGenerator = CommandInterestGenerator() self._timeoutPrefix = Name("/local/timeout") self._lastEntryId = 0 self._lastEntryIdLock = threading.Lock() self._connectStatus = Node._ConnectStatus.UNCONNECTED def expressInterest(self, pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face): """ Send the Interest through the transport, read the entire response and call onData, onTimeout or onNetworkNack as described below. :param int pendingInterestId: The getNextEntryId() for the pending interest ID which Face got so it could return it to the caller. :param Interest interestCopy: The Interest which is NOT copied for this internal Node method. The Face expressInterest is responsible for making a copy for Node to use. :param onData: When a matching data packet is received, this calls onData(interest, data) where interest is the Interest given to expressInterest and data is the received Data object. :type onData: function object :param onTimeout: If the interest times out according to the interest lifetime, this calls onTimeout(interest) where interest is the Interest given to expressInterest. If onTimeout is None, this does not use it. :type onTimeout: function object :param onNetworkNack: When a network Nack packet for the interest is received and onNetworkNack is not None, this calls onNetworkNack(interest, networkNack) and does not call onTimeout. interest is the sent Interest and networkNack is the received NetworkNack. However, if a network Nack is received and onNetworkNack is None, do nothing and wait for the interest to time out. :type onNetworkNack: function object :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param Face face: The face which has the callLater method, used for interest timeouts. The callLater method may be overridden in a subclass of Face. :throws: RuntimeError If the encoded interest size exceeds getMaxNdnPacketSize(). """ # Set the nonce in our copy of the Interest so it is saved in the PIT. interestCopy.setNonce(Node._nonceTemplate) interestCopy.refreshNonce() if self._connectStatus == self._ConnectStatus.CONNECT_COMPLETE: # We are connected. Simply send the interest. self._expressInterestHelper(pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face) return # TODO: Properly check if we are already connected to the expected host. if not self._transport.isAsync(): # The simple case: Just do a blocking connect and express. self._transport.connect(self._connectionInfo, self, None) self._expressInterestHelper(pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face) # Make future calls to expressInterest send directly to the Transport. self._connectStatus = self._ConnectStatus.CONNECT_COMPLETE return # Handle the async case. if self._connectStatus == Node._ConnectStatus.UNCONNECTED: self._connectStatus = Node._ConnectStatus.CONNECT_REQUESTED # expressInterestHelper will be called by onConnected. self._onConnectedCallbacks.append( lambda: self._expressInterestHelper( pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face)) def onConnected(): # Assume that further calls to expressInterest dispatched to the # event loop are queued and won't enter expressInterest until # this method completes and sets CONNECT_COMPLETE. # Call each callback added while the connection was opening. for onConnectedCallback in self._onConnectedCallbacks: onConnectedCallback() self._onConnectedCallbacks = [] # Make future calls to expressInterest send directly to the # Transport. self._connectStatus = Node._ConnectStatus.CONNECT_COMPLETE self._transport.connect(self._connectionInfo, self, onConnected) elif self._connectStatus == self._ConnectStatus.CONNECT_REQUESTED: # Still connecting. add to the interests to express by onConnected. self._onConnectedCallbacks.append( lambda: self._expressInterestHelper( pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face)) else: # Don't expect this to happen. raise RuntimeError("Node: Unrecognized _connectStatus " + str(self._connectStatus)) def removePendingInterest(self, pendingInterestId): """ Remove the pending interest entry with the pendingInterestId from the pending interest table. This does not affect another pending interest with a different pendingInterestId, even if it has the same interest name. If there is no entry with the pendingInterestId, do nothing. :param int pendingInterestId: The ID returned from expressInterest. """ self._pendingInterestTable.removePendingInterest(pendingInterestId) def makeCommandInterest(self, interest, keyChain, certificateName, wireFormat): """ Append a timestamp component and a random value component to interest's name. Then use the keyChain and certificateName to sign the interest. If the interest lifetime is not set, this sets it. :param Interest interest: The interest whose name is append with components. :param KeyChain keyChain: The KeyChain for calling sign. :param Name certificateName: The certificate name of the key to use for signing. :param wireFormat: A WireFormat object used to encode the SignatureInfo and to encode the interest name for signing. :type wireFormat: A subclass of WireFormat """ self._commandInterestGenerator.generate(interest, keyChain, certificateName, wireFormat) def registerPrefix(self, registeredPrefixId, prefixCopy, onInterest, onRegisterFailed, onRegisterSuccess, flags, wireFormat, commandKeyChain, commandCertificateName, face): """ Register prefix with the connected NDN hub and call onInterest when a matching interest is received. To register a prefix with NFD, you must first call setCommandSigningInfo. :param int registeredPrefixId: The getNextEntryId() for the registered prefix ID which Face got so it could return it to the caller. :param Name prefixCopy: The Name for the prefix to register which is NOT copied for this internal Node method. The Face registerPrefix is responsible for making a copy for Node to use. :param onInterest: (optional) If not None, this creates an interest filter from prefixCopy so that when an Interest is received which matches the filter, this calls onInterest(prefix, interest, face, interestFilterId, filter). NOTE: You must not change the prefix or filter objects - if you need to change them then make a copy. If onInterest is None, it is ignored and you must call setInterestFilter. :type onInterest: function object :param onRegisterFailed: A function object to call if failed to retrieve the connected hub's ID or failed to register the prefix. :type onRegisterFailed: function object :param onRegisterSuccess: This calls onRegisterSuccess(prefix, registeredPrefixId) when this receives a success message from the forwarder. If onRegisterSuccess is None, this does not use it. :type onRegisterSuccess: function object :param ForwardingFlags flags: The flags for finer control of which interests are forwardedto the application. :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param KeyChain commandKeyChain: The KeyChain object for signing interests. :param Name commandCertificateName: The certificate name for signing interests. :param Face face: The face which is passed to the onInterest callback. If onInterest is None, this is ignored. """ self._nfdRegisterPrefix(registeredPrefixId, prefixCopy, onInterest, onRegisterFailed, onRegisterSuccess, flags, commandKeyChain, commandCertificateName, face) def removeRegisteredPrefix(self, registeredPrefixId): """ Remove the registered prefix entry with the registeredPrefixId from the registered prefix table. This does not affect another registered prefix with a different registeredPrefixId, even if it has the same prefix name. If an interest filter was automatically created by registerPrefix, also remove it. If there is no entry with the registeredPrefixId, do nothing. :param int registeredPrefixId: The ID returned from registerPrefix. """ self._registeredPrefixTable.removeRegisteredPrefix(registeredPrefixId) def setInterestFilter(self, interestFilterId, filterCopy, onInterest, face): """ Add an entry to the local interest filter table to call the onInterest callback for a matching incoming Interest. This method only modifies the library's local callback table and does not register the prefix with the forwarder. It will always succeed. To register a prefix with the forwarder, use registerPrefix. :param int interestFilterId: The getNextEntryId() for the interest filter ID which Face got so it could return it to the caller. :param InterestFilter filterCopy: The InterestFilter with a prefix and optional regex filter used to match the name of an incoming Interest, which is NOT copied for this internal Node method. The Face setInterestFilter is responsible for making a copy for Node to use. :param onInterest: When an Interest is received which matches the filter, this calls onInterest(prefix, interest, face, interestFilterId, filter). :type onInterest: function object :param Face face: The face which is passed to the onInterest callback. """ self._interestFilterTable.setInterestFilter(interestFilterId, InterestFilter(filterCopy), onInterest, face) def unsetInterestFilter(self, interestFilterId): """ Remove the interest filter entry which has the interestFilterId from the interest filter table. This does not affect another interest filter with a different interestFilterId, even if it has the same prefix name. If there is no entry with the interestFilterId, do nothing. :param int interestFilterId: The ID returned from setInterestFilter. """ self._interestFilterTable.unsetInterestFilter(interestFilterId) def send(self, encoding): """ Send the encoded packet out through the transport. :param encoding: The array of bytes for the encoded packet to send. :type encoding: An array type with int elements :throws: RuntimeError If the packet size exceeds getMaxNdnPacketSize(). """ if len(encoding) > self.getMaxNdnPacketSize(): raise RuntimeError( "The encoded packet size exceeds the maximum limit getMaxNdnPacketSize()" ) self._transport.send(encoding) def processEvents(self): """ Process any packets to receive and call callbacks such as onData, onInterest or onTimeout. This returns immediately if there is no data to receive. This blocks while calling the callbacks. You should repeatedly call this from an event loop, with calls to sleep as needed so that the loop doesn't use 100% of the CPU. Since processEvents modifies the pending interest table, your application should make sure that it calls processEvents in the same thread as expressInterest (which also modifies the pending interest table). :raises: This may raise an exception for reading data or in the callback for processing the data. If you call this from an main event loop, you may want to catch and log/disregard all exceptions. """ self._transport.processEvents() # If Face.callLater is overridden to use a different mechanism, then # processEvents is not needed to check for delayed calls. self._delayedCallTable.callTimedOut() def getTransport(self): """ Get the transport object given to the constructor. :return: The transport object. :rtype: Transport """ return self._transport def getConnectionInfo(self): """ Get the connectionInfo object given to the constructor. :return: The connectionInfo object. :rtype: Transport.ConnectionInfo """ return self._connectionInfo def onReceivedElement(self, element): """ This is called by the transport's ElementReader to process an entire received Data or Interest element. :param element: The bytes of the incoming element. :type element: An array type with int elements """ lpPacket = None if element[0] == Tlv.LpPacket_LpPacket: # Decode the LpPacket and replace element with the fragment. lpPacket = LpPacket() # Set copy False so that the fragment is a slice which will be # copied below. The header fields are all integers and don't need to # be copied. TlvWireFormat.get().decodeLpPacket(lpPacket, element, False) element = lpPacket.getFragmentWireEncoding().buf() # First, decode as Interest or Data. interest = None data = None decoder = TlvDecoder(element) if decoder.peekType(Tlv.Interest, len(element)): interest = Interest() interest.wireDecode(element, TlvWireFormat.get()) if lpPacket != None: interest.setLpPacket(lpPacket) elif decoder.peekType(Tlv.Data, len(element)): data = Data() data.wireDecode(element, TlvWireFormat.get()) if lpPacket != None: data.setLpPacket(lpPacket) if lpPacket != None: # We have decoded the fragment, so remove the wire encoding to save # memory. lpPacket.setFragmentWireEncoding(Blob()) networkNack = NetworkNack.getFirstHeader(lpPacket) if networkNack != None: if interest == None: # We got a Nack but not for an Interest, so drop the packet. return pendingInterests = [] self._pendingInterestTable.extractEntriesForNackInterest( interest, pendingInterests) for pendingInterest in pendingInterests: try: pendingInterest.getOnNetworkNack()( pendingInterest.getInterest(), networkNack) except: logging.exception("Error in onNetworkNack") # We have processed the network Nack packet. return # Now process as Interest or Data. if interest != None: # Call all interest filter callbacks which match. matchedFilters = [] self._interestFilterTable.getMatchedFilters( interest, matchedFilters) for i in range(len(matchedFilters)): entry = matchedFilters[i] includeFilter = True onInterestCall = entry.getOnInterest() # If onInterest is not a function nor a method assumes it is a # calleable object if (not inspect.isfunction(onInterestCall) and not inspect.ismethod(onInterestCall)): onInterestCall = onInterestCall.__call__ # Use getcallargs to test if onInterest accepts 5 args. try: inspect.getcallargs(onInterestCall, None, None, None, None, None) except TypeError: # Assume onInterest is old-style with 4 arguments. includeFilter = False if includeFilter: try: entry.getOnInterest()(entry.getFilter().getPrefix(), interest, entry.getFace(), entry.getInterestFilterId(), entry.getFilter()) except: logging.exception("Error in onInterest") else: # Old-style onInterest without the filter argument. We # still pass a Face instead of Transport since Face also # has a send method. try: entry.getOnInterest()(entry.getFilter().getPrefix(), interest, entry.getFace(), entry.getInterestFilterId()) except: logging.exception("Error in onInterest") elif data != None: pendingInterests = [] self._pendingInterestTable.extractEntriesForExpressedInterest( data, pendingInterests) for pendingInterest in pendingInterests: try: pendingInterest.getOnData()(pendingInterest.getInterest(), data) except: logging.exception("Error in onData") def isLocal(self): """ Check if the face is local based on the current connection through the Transport; some Transport may cause network I/O (e.g. an IP host name lookup). :return: True if the face is local, False if not. :rtype bool: """ return self._transport.isLocal(self._connectionInfo) def shutdown(self): """ Call getTransport().close(). """ self._transport.close() @staticmethod def getMaxNdnPacketSize(): """ Get the practical limit of the size of a network-layer packet. If a packet is larger than this, the library or application MAY drop it. :return: The maximum NDN packet size. :rtype: int """ return Common.MAX_NDN_PACKET_SIZE def _expressInterestHelper(self, pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face): """ Do the work of expressInterest once we know we are connected. Add the entry to the PIT, encode and send the interest. :param int pendingInterestId: The getNextEntryId() for the pending interest ID which Face got so it could return it to the caller. :param Interest interestCopy: The Interest to send, which has already been copied. :param onData: A function object to call when a matching data packet is received. :type onData: function object :param onTimeout: A function object to call if the interest times out. If onTimeout is None, this does not use it. :type onTimeout: function object :param onNetworkNack: A function object to call when a network Nack packet is received. :type onNetworkNack: function object :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param Face face: The face which has the callLater method, used for interest timeouts. The callLater method may be overridden in a subclass of Face. :throws: RuntimeError If the encoded interest size exceeds getMaxNdnPacketSize(). """ pendingInterest = self._pendingInterestTable.add( pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack) if pendingInterest == None: # removePendingInterest was already called with the pendingInterestId. return if (onTimeout or interestCopy.getInterestLifetimeMilliseconds() != None and interestCopy.getInterestLifetimeMilliseconds() >= 0.0): # Set up the timeout. delayMilliseconds = interestCopy.getInterestLifetimeMilliseconds() if delayMilliseconds == None or delayMilliseconds < 0.0: # Use a default timeout delay. delayMilliseconds = 4000.0 face.callLater( delayMilliseconds, lambda: self._processInterestTimeout(pendingInterest)) # Special case: For _timeoutPrefix we don't actually send the interest. if not self._timeoutPrefix.match(interestCopy.getName()): encoding = interestCopy.wireEncode(wireFormat) if encoding.size() > self.getMaxNdnPacketSize(): raise RuntimeError( "The encoded interest size exceeds the maximum limit getMaxNdnPacketSize()" ) self._transport.send(encoding.toBuffer()) def _nfdRegisterPrefix(self, registeredPrefixId, prefix, onInterest, onRegisterFailed, onRegisterSuccess, flags, commandKeyChain, commandCertificateName, face): """ Do the work of registerPrefix to register with NFD. :param int registeredPrefixId: The getNextEntryId() which registerPrefix got so it could return it to the caller. If this is 0, then don't add to _registeredPrefixTable (assuming it has already been done). """ if commandKeyChain == None: raise RuntimeError( "registerPrefix: The command KeyChain has not been set. You must call setCommandSigningInfo." ) if commandCertificateName.size() == 0: raise RuntimeError( "registerPrefix: The command certificate name has not been set. You must call setCommandSigningInfo." ) controlParameters = ControlParameters() controlParameters.setName(prefix) controlParameters.setForwardingFlags(flags) commandInterest = Interest() if self.isLocal(): commandInterest.setName(Name("/localhost/nfd/rib/register")) # The interest is answered by the local host, so set a short timeout. commandInterest.setInterestLifetimeMilliseconds(2000.0) else: commandInterest.setName(Name("/localhop/nfd/rib/register")) # The host is remote, so set a longer timeout. commandInterest.setInterestLifetimeMilliseconds(4000.0) # NFD only accepts TlvWireFormat packets. commandInterest.getName().append( controlParameters.wireEncode(TlvWireFormat.get())) self.makeCommandInterest(commandInterest, commandKeyChain, commandCertificateName, TlvWireFormat.get()) # Send the registration interest. response = Node._RegisterResponse(prefix, onRegisterFailed, onRegisterSuccess, registeredPrefixId, self, onInterest, face) self.expressInterest(self.getNextEntryId(), commandInterest, response.onData, response.onTimeout, None, TlvWireFormat.get(), face) def callLater(self, delayMilliseconds, callback): """ Call callback() after the given delay. This adds to self._delayedCallTable which is used by processEvents(). :param float delayMilliseconds: The delay in milliseconds. :param callback: This calls callback() after the delay. :type callback: function object """ self._delayedCallTable.callLater(delayMilliseconds, callback) def _processInterestTimeout(self, pendingInterest): """ This is used in callLater for when the pending interest expires. If the pendingInterest is still in the _pendingInterestTable, remove it and call its onTimeout callback. """ if self._pendingInterestTable.removeEntry(pendingInterest): pendingInterest.callTimeout() def getNextEntryId(self): """ Get the next unique entry ID for the pending interest table, interest filter table, etc. This uses a threading.Lock() to be thread safe. Most entry IDs are for the pending interest table (there usually are not many interest filter table entries) so we use a common pool to only have to do the thread safe lock in one method which is called by Face. :return: The next entry ID. :rtype: int """ with self._lastEntryIdLock: self._lastEntryId += 1 return self._lastEntryId class _ConnectStatus(object): UNCONNECTED = 1 CONNECT_REQUESTED = 2 CONNECT_COMPLETE = 3 class _RegisterResponse(object): """ A _RegisterResponse receives the response Data packet from the register prefix interest sent to the connected NDN hub. If this gets a bad response or a timeout, call onRegisterFailed. """ def __init__(self, prefix, onRegisterFailed, onRegisterSuccess, registeredPrefixId, parent, onInterest, face): self._prefix = prefix self._onRegisterFailed = onRegisterFailed self._onRegisterSuccess = onRegisterSuccess self._registeredPrefixId = registeredPrefixId self._parent = parent self._onInterest = onInterest self._face = face def onData(self, interest, responseData): """ We received the response. """ # Decode responseData.getContent() and check for a success code. controlResponse = ControlResponse() try: controlResponse.wireDecode(responseData.getContent(), TlvWireFormat.get()) except ValueError as ex: logging.getLogger(__name__).info( "Register prefix failed: Error decoding the NFD response: %s", str(ex)) try: self._onRegisterFailed(self._prefix) except: logging.exception("Error in onRegisterFailed") return # Status code 200 is "OK". if controlResponse.getStatusCode() != 200: logging.getLogger(__name__).info( "Register prefix failed: Expected NFD status code 200, got: %d", controlResponse.getStatusCode()) try: self._onRegisterFailed(self._prefix) except: logging.exception("Error in onRegisterFailed") return # Success, so we can add to the registered prefix table. if self._registeredPrefixId != 0: interestFilterId = 0 if self._onInterest != None: # registerPrefix was called with the "combined" form that includes # the callback, so add an InterestFilterEntry. interestFilterId = self._parent.getNextEntryId() self._parent.setInterestFilter( interestFilterId, InterestFilter(self._prefix), self._onInterest, self._face) if not self._parent._registeredPrefixTable.add( self._registeredPrefixId, self._prefix, interestFilterId): # removeRegisteredPrefix was already called with the registeredPrefixId. if interestFilterId > 0: # Remove the related interest filter we just added. self._parent.unsetInterestFilter(interestFilterId) return logging.getLogger(__name__).info( "Register prefix succeeded with the NFD forwarder for prefix %s", self._prefix.toUri()) if self._onRegisterSuccess != None: try: self._onRegisterSuccess(self._prefix, self._registeredPrefixId) except: logging.exception("Error in onRegisterSuccess") def onTimeout(self, interest): """ We timed out waiting for the response. """ logging.getLogger(__name__).info( "Timeout for NFD register prefix command.") try: self._onRegisterFailed(self._prefix) except: logging.exception("Error in onRegisterFailed") _nonceTemplate = Blob(bytearray(4), False)
class InterestFilter(object): """ Create an InterestFilter to match any Interest whose name starts with the given prefix. If the optional regexFilter is provided then the remaining components match the regexFilter regular expression as described in doesMatch. :param prefix: The prefix. If a Name then this makes a copy of the Name. Otherwise it create a Name from the URI string. :type prefix: Name or str :param str regexFilter: (optional) The regular expression for matching the remaining name components. """ def __init__(self, prefix, regexFilter = None): if type(prefix) is InterestFilter: interestFilter = prefix # The copy constructor. self._prefix = Name(interestFilter._prefix) self._regexFilter = interestFilter._regexFilter self._regexFilterPattern = interestFilter._regexFilterPattern else: self._prefix = Name(prefix) self._regexFilter = regexFilter if regexFilter != None: self._regexFilterPattern = self.makePattern(regexFilter) else: self._regexFilterPattern = None def doesMatch(self, name): """ Check if the given name matches this filter. Match if name starts with this filter's prefix. If this filter has the optional regexFilter then the remaining components match the regexFilter regular expression. For example, the following InterestFilter: InterestFilter("/hello", "<world><>+") will match all Interests, whose name has the prefix `/hello` which is followed by a component `world` and has at least one more component after it. Examples: /hello/world/! /hello/world/x/y/z Note that the regular expression will need to match all remaining components (e.g., there are implicit heading `^` and trailing `$` symbols in the regular expression). :param Name name: The name to check against this filter. :return: True if name matches this filter, otherwise False. :rtype: bool """ if len(name) < len(self._prefix): return False if self.hasRegexFilter(): # Perform a prefix match and regular expression match for the # remaining components. if not self._prefix.match(name): return False return None != NdnRegexMatcher.match( self._regexFilterPattern, name.getSubName(len(self._prefix))) else: # Just perform a prefix match. return self._prefix.match(name) def getPrefix(self): """ Get the prefix given to the constructor. :return: The prefix Name which you should not modify. :rtype: Name """ return self._prefix def hasRegexFilter(self): """ Check if a regexFilter was supplied to the constructor. :return: True if a regexFilter was supplied to the constructor. :rtype: bool """ return self._regexFilter != None def getRegexFilter(self): """ Get the regex filter. This is only valid if hasRegexFilter() is True. :return: The regular expression for matching the remaining name components. :rtype: str """ return self._regexFilter @staticmethod def makePattern(regexFilter): """ If regexFilter doesn't already have them, add ^ to the beginning and $ to the end since these are required by NdnRegexMatcher.match. :param str regexFilter: The regex filter. :return: The regex pattern with ^ and $. :rtype str: """ if len(regexFilter) == 0: # We don't expect this. return "^$" pattern = regexFilter if pattern[0] != '^': pattern = "^" + pattern if pattern[-1] != '$': pattern = pattern + "$" return pattern
class Node(object): """ Create a new Node for communication with an NDN hub with the given Transport object and connectionInfo. :param Transport transport: An object of a subclass of Transport used for communication. :param Transport.ConnectionInfo connectionInfo: An object of a subclass of Transport.ConnectionInfo to be used to connect to the transport. """ def __init__(self, transport, connectionInfo): self._transport = transport self._connectionInfo = connectionInfo self._pendingInterestTable = PendingInterestTable() self._interestFilterTable = InterestFilterTable() self._registeredPrefixTable = RegisteredPrefixTable(self._interestFilterTable) self._delayedCallTable = DelayedCallTable() # An array of function objects self._onConnectedCallbacks = [] self._commandInterestGenerator = CommandInterestGenerator() self._timeoutPrefix = Name("/local/timeout") self._lastEntryId = 0 self._lastEntryIdLock = threading.Lock() self._connectStatus = Node._ConnectStatus.UNCONNECTED def expressInterest( self, pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face): """ Send the Interest through the transport, read the entire response and call onData, onTimeout or onNetworkNack as described below. :param int pendingInterestId: The getNextEntryId() for the pending interest ID which Face got so it could return it to the caller. :param Interest interestCopy: The Interest which is NOT copied for this internal Node method. The Face expressInterest is responsible for making a copy for Node to use. :param onData: When a matching data packet is received, this calls onData(interest, data) where interest is the Interest given to expressInterest and data is the received Data object. :type onData: function object :param onTimeout: If the interest times out according to the interest lifetime, this calls onTimeout(interest) where interest is the Interest given to expressInterest. If onTimeout is None, this does not use it. :type onTimeout: function object :param onNetworkNack: When a network Nack packet for the interest is received and onNetworkNack is not None, this calls onNetworkNack(interest, networkNack) and does not call onTimeout. interest is the sent Interest and networkNack is the received NetworkNack. However, if a network Nack is received and onNetworkNack is None, do nothing and wait for the interest to time out. :type onNetworkNack: function object :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param Face face: The face which has the callLater method, used for interest timeouts. The callLater method may be overridden in a subclass of Face. :throws: RuntimeError If the encoded interest size exceeds getMaxNdnPacketSize(). """ # Set the nonce in our copy of the Interest so it is saved in the PIT. interestCopy.setNonce(Node._nonceTemplate) interestCopy.refreshNonce() if self._connectStatus == self._ConnectStatus.CONNECT_COMPLETE: # We are connected. Simply send the interest. self._expressInterestHelper( pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face) return # TODO: Properly check if we are already connected to the expected host. if not self._transport.isAsync(): # The simple case: Just do a blocking connect and express. self._transport.connect(self._connectionInfo, self, None); self._expressInterestHelper(pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face) # Make future calls to expressInterest send directly to the Transport. self._connectStatus = self._ConnectStatus.CONNECT_COMPLETE return # Handle the async case. if self._connectStatus == Node._ConnectStatus.UNCONNECTED: self._connectStatus = Node._ConnectStatus.CONNECT_REQUESTED # expressInterestHelper will be called by onConnected. self._onConnectedCallbacks.append( lambda: self._expressInterestHelper (pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face)) def onConnected(): # Assume that further calls to expressInterest dispatched to the # event loop are queued and won't enter expressInterest until # this method completes and sets CONNECT_COMPLETE. # Call each callback added while the connection was opening. for onConnectedCallback in self._onConnectedCallbacks: onConnectedCallback() self._onConnectedCallbacks = [] # Make future calls to expressInterest send directly to the # Transport. self._connectStatus = Node._ConnectStatus.CONNECT_COMPLETE self._transport.connect(self._connectionInfo, self, onConnected) elif self._connectStatus == self._ConnectStatus.CONNECT_REQUESTED: # Still connecting. add to the interests to express by onConnected. self._onConnectedCallbacks.append( lambda: self._expressInterestHelper (pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face)) else: # Don't expect this to happen. raise RuntimeError( "Node: Unrecognized _connectStatus " + str(self._connectStatus)) def removePendingInterest(self, pendingInterestId): """ Remove the pending interest entry with the pendingInterestId from the pending interest table. This does not affect another pending interest with a different pendingInterestId, even if it has the same interest name. If there is no entry with the pendingInterestId, do nothing. :param int pendingInterestId: The ID returned from expressInterest. """ self._pendingInterestTable.removePendingInterest(pendingInterestId) def makeCommandInterest(self, interest, keyChain, certificateName, wireFormat): """ Append a timestamp component and a random value component to interest's name. Then use the keyChain and certificateName to sign the interest. If the interest lifetime is not set, this sets it. :param Interest interest: The interest whose name is append with components. :param KeyChain keyChain: The KeyChain for calling sign. :param Name certificateName: The certificate name of the key to use for signing. :param wireFormat: A WireFormat object used to encode the SignatureInfo and to encode the interest name for signing. :type wireFormat: A subclass of WireFormat """ self._commandInterestGenerator.generate( interest, keyChain, certificateName, wireFormat) def registerPrefix( self, registeredPrefixId, prefixCopy, onInterest, onRegisterFailed, onRegisterSuccess, flags, wireFormat, commandKeyChain, commandCertificateName, face): """ Register prefix with the connected NDN hub and call onInterest when a matching interest is received. To register a prefix with NFD, you must first call setCommandSigningInfo. :param int registeredPrefixId: The getNextEntryId() for the registered prefix ID which Face got so it could return it to the caller. :param Name prefixCopy: The Name for the prefix to register which is NOT copied for this internal Node method. The Face registerPrefix is responsible for making a copy for Node to use. :param onInterest: (optional) If not None, this creates an interest filter from prefixCopy so that when an Interest is received which matches the filter, this calls onInterest(prefix, interest, face, interestFilterId, filter). NOTE: You must not change the prefix or filter objects - if you need to change them then make a copy. If onInterest is None, it is ignored and you must call setInterestFilter. :type onInterest: function object :param onRegisterFailed: A function object to call if failed to retrieve the connected hub's ID or failed to register the prefix. :type onRegisterFailed: function object :param onRegisterSuccess: This calls onRegisterSuccess(prefix, registeredPrefixId) when this receives a success message from the forwarder. If onRegisterSuccess is None, this does not use it. :type onRegisterSuccess: function object :param ForwardingFlags flags: The flags for finer control of which interests are forwardedto the application. :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param KeyChain commandKeyChain: The KeyChain object for signing interests. :param Name commandCertificateName: The certificate name for signing interests. :param Face face: The face which is passed to the onInterest callback. If onInterest is None, this is ignored. """ self._nfdRegisterPrefix( registeredPrefixId, prefixCopy, onInterest, onRegisterFailed, onRegisterSuccess, flags, commandKeyChain, commandCertificateName, face) def removeRegisteredPrefix(self, registeredPrefixId): """ Remove the registered prefix entry with the registeredPrefixId from the registered prefix table. This does not affect another registered prefix with a different registeredPrefixId, even if it has the same prefix name. If an interest filter was automatically created by registerPrefix, also remove it. If there is no entry with the registeredPrefixId, do nothing. :param int registeredPrefixId: The ID returned from registerPrefix. """ self._registeredPrefixTable.removeRegisteredPrefix(registeredPrefixId) def setInterestFilter(self, interestFilterId, filterCopy, onInterest, face): """ Add an entry to the local interest filter table to call the onInterest callback for a matching incoming Interest. This method only modifies the library's local callback table and does not register the prefix with the forwarder. It will always succeed. To register a prefix with the forwarder, use registerPrefix. :param int interestFilterId: The getNextEntryId() for the interest filter ID which Face got so it could return it to the caller. :param InterestFilter filterCopy: The InterestFilter with a prefix and optional regex filter used to match the name of an incoming Interest, which is NOT copied for this internal Node method. The Face setInterestFilter is responsible for making a copy for Node to use. :param onInterest: When an Interest is received which matches the filter, this calls onInterest(prefix, interest, face, interestFilterId, filter). :type onInterest: function object :param Face face: The face which is passed to the onInterest callback. """ self._interestFilterTable.setInterestFilter( interestFilterId, InterestFilter(filterCopy), onInterest, face) def unsetInterestFilter(self, interestFilterId): """ Remove the interest filter entry which has the interestFilterId from the interest filter table. This does not affect another interest filter with a different interestFilterId, even if it has the same prefix name. If there is no entry with the interestFilterId, do nothing. :param int interestFilterId: The ID returned from setInterestFilter. """ self._interestFilterTable.unsetInterestFilter(interestFilterId) def send(self, encoding): """ Send the encoded packet out through the transport. :param encoding: The array of bytes for the encoded packet to send. :type encoding: An array type with int elements :throws: RuntimeError If the packet size exceeds getMaxNdnPacketSize(). """ if len(encoding) > self.getMaxNdnPacketSize(): raise RuntimeError( "The encoded packet size exceeds the maximum limit getMaxNdnPacketSize()") self._transport.send(encoding) def processEvents(self): """ Process any packets to receive and call callbacks such as onData, onInterest or onTimeout. This returns immediately if there is no data to receive. This blocks while calling the callbacks. You should repeatedly call this from an event loop, with calls to sleep as needed so that the loop doesn't use 100% of the CPU. Since processEvents modifies the pending interest table, your application should make sure that it calls processEvents in the same thread as expressInterest (which also modifies the pending interest table). :raises: This may raise an exception for reading data or in the callback for processing the data. If you call this from an main event loop, you may want to catch and log/disregard all exceptions. """ self._transport.processEvents() # If Face.callLater is overridden to use a different mechanism, then # processEvents is not needed to check for delayed calls. self._delayedCallTable.callTimedOut(); def getTransport(self): """ Get the transport object given to the constructor. :return: The transport object. :rtype: Transport """ return self._transport def getConnectionInfo(self): """ Get the connectionInfo object given to the constructor. :return: The connectionInfo object. :rtype: Transport.ConnectionInfo """ return self._connectionInfo def onReceivedElement(self, element): """ This is called by the transport's ElementReader to process an entire received Data or Interest element. :param element: The bytes of the incoming element. :type element: An array type with int elements """ lpPacket = None if element[0] == Tlv.LpPacket_LpPacket: # Decode the LpPacket and replace element with the fragment. lpPacket = LpPacket() # Set copy False so that the fragment is a slice which will be # copied below. The header fields are all integers and don't need to # be copied. TlvWireFormat.get().decodeLpPacket(lpPacket, element, False) element = lpPacket.getFragmentWireEncoding().buf() # First, decode as Interest or Data. interest = None data = None decoder = TlvDecoder(element) if decoder.peekType(Tlv.Interest, len(element)): interest = Interest() interest.wireDecode(element, TlvWireFormat.get()) if lpPacket != None: interest.setLpPacket(lpPacket) elif decoder.peekType(Tlv.Data, len(element)): data = Data() data.wireDecode(element, TlvWireFormat.get()) if lpPacket != None: data.setLpPacket(lpPacket) if lpPacket != None: # We have decoded the fragment, so remove the wire encoding to save # memory. lpPacket.setFragmentWireEncoding(Blob()) networkNack = NetworkNack.getFirstHeader(lpPacket) if networkNack != None: if interest == None: # We got a Nack but not for an Interest, so drop the packet. return pendingInterests = [] self._pendingInterestTable.extractEntriesForNackInterest( interest, pendingInterests) for pendingInterest in pendingInterests: try: pendingInterest.getOnNetworkNack()( pendingInterest.getInterest(), networkNack) except: logging.exception("Error in onNetworkNack") # We have processed the network Nack packet. return # Now process as Interest or Data. if interest != None: # Call all interest filter callbacks which match. matchedFilters = [] self._interestFilterTable.getMatchedFilters(interest, matchedFilters) for i in range(len(matchedFilters)): entry = matchedFilters[i] includeFilter = True onInterestCall = entry.getOnInterest() # If onInterest is not a function nor a method assumes it is a # calleable object if (not inspect.isfunction(onInterestCall) and not inspect.ismethod(onInterestCall)): onInterestCall = onInterestCall.__call__ # Use getcallargs to test if onInterest accepts 5 args. try: inspect.getcallargs(onInterestCall, None, None, None, None, None) except TypeError: # Assume onInterest is old-style with 4 arguments. includeFilter = False if includeFilter: try: entry.getOnInterest()( entry.getFilter().getPrefix(), interest, entry.getFace(), entry.getInterestFilterId(), entry.getFilter()) except: logging.exception("Error in onInterest") else: # Old-style onInterest without the filter argument. We # still pass a Face instead of Transport since Face also # has a send method. try: entry.getOnInterest()( entry.getFilter().getPrefix(), interest, entry.getFace(), entry.getInterestFilterId()) except: logging.exception("Error in onInterest") elif data != None: pendingInterests = [] self._pendingInterestTable.extractEntriesForExpressedInterest( data, pendingInterests) for pendingInterest in pendingInterests: try: pendingInterest.getOnData()(pendingInterest.getInterest(), data) except: logging.exception("Error in onData") def isLocal(self): """ Check if the face is local based on the current connection through the Transport; some Transport may cause network I/O (e.g. an IP host name lookup). :return: True if the face is local, False if not. :rtype bool: """ return self._transport.isLocal(self._connectionInfo) def shutdown(self): """ Call getTransport().close(). """ self._transport.close() @staticmethod def getMaxNdnPacketSize(): """ Get the practical limit of the size of a network-layer packet. If a packet is larger than this, the library or application MAY drop it. :return: The maximum NDN packet size. :rtype: int """ return Common.MAX_NDN_PACKET_SIZE def _expressInterestHelper( self, pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack, wireFormat, face): """ Do the work of expressInterest once we know we are connected. Add the entry to the PIT, encode and send the interest. :param int pendingInterestId: The getNextEntryId() for the pending interest ID which Face got so it could return it to the caller. :param Interest interestCopy: The Interest to send, which has already been copied. :param onData: A function object to call when a matching data packet is received. :type onData: function object :param onTimeout: A function object to call if the interest times out. If onTimeout is None, this does not use it. :type onTimeout: function object :param onNetworkNack: A function object to call when a network Nack packet is received. :type onNetworkNack: function object :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param Face face: The face which has the callLater method, used for interest timeouts. The callLater method may be overridden in a subclass of Face. :throws: RuntimeError If the encoded interest size exceeds getMaxNdnPacketSize(). """ pendingInterest = self._pendingInterestTable.add( pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack) if pendingInterest == None: # removePendingInterest was already called with the pendingInterestId. return if (onTimeout or interestCopy.getInterestLifetimeMilliseconds() != None and interestCopy.getInterestLifetimeMilliseconds() >= 0.0): # Set up the timeout. delayMilliseconds = interestCopy.getInterestLifetimeMilliseconds() if delayMilliseconds == None or delayMilliseconds < 0.0: # Use a default timeout delay. delayMilliseconds = 4000.0 face.callLater(delayMilliseconds, lambda: self._processInterestTimeout(pendingInterest)) # Special case: For _timeoutPrefix we don't actually send the interest. if not self._timeoutPrefix.match(interestCopy.getName()): encoding = interestCopy.wireEncode(wireFormat) if encoding.size() > self.getMaxNdnPacketSize(): raise RuntimeError( "The encoded interest size exceeds the maximum limit getMaxNdnPacketSize()") self._transport.send(encoding.toBuffer()) def _nfdRegisterPrefix( self, registeredPrefixId, prefix, onInterest, onRegisterFailed, onRegisterSuccess, flags, commandKeyChain, commandCertificateName, face): """ Do the work of registerPrefix to register with NFD. :param int registeredPrefixId: The getNextEntryId() which registerPrefix got so it could return it to the caller. If this is 0, then don't add to _registeredPrefixTable (assuming it has already been done). """ if commandKeyChain == None: raise RuntimeError( "registerPrefix: The command KeyChain has not been set. You must call setCommandSigningInfo.") if commandCertificateName.size() == 0: raise RuntimeError( "registerPrefix: The command certificate name has not been set. You must call setCommandSigningInfo.") controlParameters = ControlParameters() controlParameters.setName(prefix) controlParameters.setForwardingFlags(flags) commandInterest = Interest() if self.isLocal(): commandInterest.setName(Name("/localhost/nfd/rib/register")) # The interest is answered by the local host, so set a short timeout. commandInterest.setInterestLifetimeMilliseconds(2000.0) else: commandInterest.setName(Name("/localhop/nfd/rib/register")) # The host is remote, so set a longer timeout. commandInterest.setInterestLifetimeMilliseconds(4000.0) # NFD only accepts TlvWireFormat packets. commandInterest.getName().append(controlParameters.wireEncode(TlvWireFormat.get())) self.makeCommandInterest( commandInterest, commandKeyChain, commandCertificateName, TlvWireFormat.get()) # Send the registration interest. response = Node._RegisterResponse( prefix, onRegisterFailed, onRegisterSuccess, registeredPrefixId, self, onInterest, face) self.expressInterest( self.getNextEntryId(), commandInterest, response.onData, response.onTimeout, None, TlvWireFormat.get(), face) def callLater(self, delayMilliseconds, callback): """ Call callback() after the given delay. This adds to self._delayedCallTable which is used by processEvents(). :param float delayMilliseconds: The delay in milliseconds. :param callback: This calls callback() after the delay. :type callback: function object """ self._delayedCallTable.callLater(delayMilliseconds, callback) def _processInterestTimeout(self, pendingInterest): """ This is used in callLater for when the pending interest expires. If the pendingInterest is still in the _pendingInterestTable, remove it and call its onTimeout callback. """ if self._pendingInterestTable.removeEntry(pendingInterest): pendingInterest.callTimeout() def getNextEntryId(self): """ Get the next unique entry ID for the pending interest table, interest filter table, etc. This uses a threading.Lock() to be thread safe. Most entry IDs are for the pending interest table (there usually are not many interest filter table entries) so we use a common pool to only have to do the thread safe lock in one method which is called by Face. :return: The next entry ID. :rtype: int """ with self._lastEntryIdLock: self._lastEntryId += 1 return self._lastEntryId class _ConnectStatus(object): UNCONNECTED = 1 CONNECT_REQUESTED = 2 CONNECT_COMPLETE = 3 class _RegisterResponse(object): """ A _RegisterResponse receives the response Data packet from the register prefix interest sent to the connected NDN hub. If this gets a bad response or a timeout, call onRegisterFailed. """ def __init__(self, prefix, onRegisterFailed, onRegisterSuccess, registeredPrefixId, parent, onInterest, face): self._prefix = prefix self._onRegisterFailed = onRegisterFailed self._onRegisterSuccess = onRegisterSuccess self._registeredPrefixId = registeredPrefixId self._parent = parent self._onInterest = onInterest self._face = face def onData(self, interest, responseData): """ We received the response. """ # Decode responseData.getContent() and check for a success code. controlResponse = ControlResponse() try: controlResponse.wireDecode(responseData.getContent(), TlvWireFormat.get()) except ValueError as ex: logging.getLogger(__name__).info( "Register prefix failed: Error decoding the NFD response: %s", str(ex)) try: self._onRegisterFailed(self._prefix) except: logging.exception("Error in onRegisterFailed") return # Status code 200 is "OK". if controlResponse.getStatusCode() != 200: logging.getLogger(__name__).info( "Register prefix failed: Expected NFD status code 200, got: %d", controlResponse.getStatusCode()) try: self._onRegisterFailed(self._prefix) except: logging.exception("Error in onRegisterFailed") return # Success, so we can add to the registered prefix table. if self._registeredPrefixId != 0: interestFilterId = 0 if self._onInterest != None: # registerPrefix was called with the "combined" form that includes # the callback, so add an InterestFilterEntry. interestFilterId = self._parent.getNextEntryId() self._parent.setInterestFilter( interestFilterId, InterestFilter(self._prefix), self._onInterest, self._face) if not self._parent._registeredPrefixTable.add( self._registeredPrefixId, self._prefix, interestFilterId): # removeRegisteredPrefix was already called with the registeredPrefixId. if interestFilterId > 0: # Remove the related interest filter we just added. self._parent.unsetInterestFilter(interestFilterId) return logging.getLogger(__name__).info( "Register prefix succeeded with the NFD forwarder for prefix %s", self._prefix.toUri()) if self._onRegisterSuccess != None: try: self._onRegisterSuccess(self._prefix, self._registeredPrefixId) except: logging.exception("Error in onRegisterSuccess") def onTimeout(self, interest): """ We timed out waiting for the response. """ logging.getLogger(__name__).info( "Timeout for NFD register prefix command.") try: self._onRegisterFailed(self._prefix) except: logging.exception("Error in onRegisterFailed") _nonceTemplate = Blob(bytearray(4), False)
class Node(object): """ Create a new Node for communication with an NDN hub with the given Transport object and connectionInfo. :param Transport transport: An object of a subclass of Transport used for communication. :param Transport.ConnectionInfo connectionInfo: An object of a subclass of Transport.ConnectionInfo to be used to connect to the transport. """ def __init__(self, transport, connectionInfo): self._transport = transport self._connectionInfo = connectionInfo # An array of _PendingInterest self._pendingInterestTable = [] # An array of _RegisteredPrefix self._registeredPrefixTable = [] # An array of _InterestFilterEntry self._interestFilterTable = [] # An array of _DelayedCall self._delayedCallTable = [] # An array of function objects self._onConnectedCallbacks = [] self._ndndIdFetcherInterest = Interest( Name("/%C1.M.S.localhost/%C1.M.SRV/ndnd/KEY")) self._ndndIdFetcherInterest.setInterestLifetimeMilliseconds(4000.0) self._ndndId = None self._commandInterestGenerator = CommandInterestGenerator() self._timeoutPrefix = Name("/local/timeout") self._lastEntryId = 0 self._lastEntryIdLock = threading.Lock() self._connectStatus = Node._ConnectStatus.UNCONNECTED def expressInterest( self, pendingInterestId, interestCopy, onData, onTimeout, wireFormat, face): """ Send the Interest through the transport, read the entire response and call onData(interest, data). :param int pendingInterestId: The getNextEntryId() for the pending interest ID which Face got so it could return it to the caller. :param Interest interestCopy: The Interest which is NOT copied for this internal Node method. The Face expressInterest is responsible for making a copy for Node to use. :param onData: A function object to call when a matching data packet is received. :type onData: function object :param onTimeout: A function object to call if the interest times out. If onTimeout is None, this does not use it. :type onTimeout: function object :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param Face face: The face which has the callLater method, used for interest timeouts. The callLater method may be overridden in a subclass of Face. :throws: RuntimeError If the encoded interest size exceeds getMaxNdnPacketSize(). """ # TODO: Properly check if we are already connected to the expected host. if self._connectStatus == self._ConnectStatus.CONNECT_COMPLETE: # We are connected. Simply send the interest. self._expressInterestHelper( pendingInterestId, interestCopy, onData, onTimeout, wireFormat, face) return if self._connectStatus == Node._ConnectStatus.UNCONNECTED: self._connectStatus = Node._ConnectStatus.CONNECT_REQUESTED # expressInterestHelper will be called by onConnected. self._onConnectedCallbacks.append( lambda: self._expressInterestHelper (pendingInterestId, interestCopy, onData, onTimeout, wireFormat, face)) def onConnected(): # Assume that further calls to expressInterest dispatched to the # event loop are queued and won't enter expressInterest until # this method completes and sets CONNECT_COMPLETE. # Call each callback added while the connection was opening. for onConnectedCallback in self._onConnectedCallbacks: onConnectedCallback() self._onConnectedCallbacks = [] # Make future calls to expressInterest send directly to the # Transport. self._connectStatus = Node._ConnectStatus.CONNECT_COMPLETE self._transport.connect(self._connectionInfo, self, onConnected) elif self._connectStatus == self._ConnectStatus.CONNECT_REQUESTED: # Still connecting. add to the interests to express by onConnected. self._onConnectedCallbacks.append( lambda: self._expressInterestHelper (pendingInterestId, interestCopy, onData, onTimeout, wireFormat, face)) else: # Don't expect this to happen. raise RuntimeError( "Node: Unrecognized _connectStatus " + str(self._connectStatus)) def removePendingInterest(self, pendingInterestId): """ Remove the pending interest entry with the pendingInterestId from the pending interest table. This does not affect another pending interest with a different pendingInterestId, even if it has the same interest name. If there is no entry with the pendingInterestId, do nothing. :param int pendingInterestId: The ID returned from expressInterest. """ count = 0 # Go backwards through the list so we can erase entries. # Remove all entries even though pendingInterestId should be unique. i = len(self._pendingInterestTable) - 1 while i >= 0: if (self._pendingInterestTable[i].getPendingInterestId() == pendingInterestId): count += 1 # For efficiency, mark this as removed so that # _processInterestTimeout doesn't look for it. self._pendingInterestTable[i].setIsRemoved() self._pendingInterestTable.pop(i) i -= 1 if count == 0: logging.getLogger(__name__).debug( "removePendingInterest: Didn't find pendingInterestId " + pendingInterestId) def makeCommandInterest(self, interest, keyChain, certificateName, wireFormat): """ Append a timestamp component and a random value component to interest's name. Then use the keyChain and certificateName to sign the interest. If the interest lifetime is not set, this sets it. :param Interest interest: The interest whose name is append with components. :param KeyChain keyChain: The KeyChain for calling sign. :param Name certificateName: The certificate name of the key to use for signing. :param wireFormat: A WireFormat object used to encode the SignatureInfo and to encode the interest name for signing. :type wireFormat: A subclass of WireFormat """ self._commandInterestGenerator.generate( interest, keyChain, certificateName, wireFormat) def registerPrefix( self, registeredPrefixId, prefixCopy, onInterest, onRegisterFailed, flags, wireFormat, commandKeyChain, commandCertificateName, face): """ Register prefix with the connected NDN hub and call onInterest when a matching interest is received. :param int registeredPrefixId: The getNextEntryId() for the registered prefix ID which Face got so it could return it to the caller. :param Name prefixCopy: The Name for the prefix to register which is NOT copied for this internal Node method. The Face registerPrefix is responsible for making a copy for Node to use. :param onInterest: (optional) If not None, this creates an interest filter from prefixCopy so that when an Interest is received which matches the filter, this calls onInterest(prefix, interest, face, interestFilterId, filter). NOTE: You must not change the prefix or filter objects - if you need to change them then make a copy. If onInterest is None, it is ignored and you must call setInterestFilter. :type onInterest: function object :param onRegisterFailed: A function object to call if failed to retrieve the connected hub's ID or failed to register the prefix. :type onRegisterFailed: function object :param ForwardingFlags flags: The flags for finer control of which interests are forwardedto the application. :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param KeyChain commandKeyChain: The KeyChain object for signing interests. If null, assume we are connected to a legacy NDNx forwarder. :param Name commandCertificateName: The certificate name for signing interests. :param Face face: The face which is passed to the onInterest callback. If onInterest is None, this is ignored. """ # If we have an _ndndId, we know we already connected to NDNx. if self._ndndId != None or commandKeyChain == None: # Assume we are connected to a legacy NDNx server. if not WireFormat.ENABLE_NDNX: raise RuntimeError( "registerPrefix with NDNx is deprecated. To enable while you upgrade your code to use NFD, set WireFormat.ENABLE_NDNX = True") if self._ndndId == None: # First fetch the ndndId of the connected hub. fetcher = Node._NdndIdFetcher( self, registeredPrefixId, prefixCopy, onInterest, onRegisterFailed, flags, wireFormat, face) # We send the interest using the given wire format so that the hub # receives (and sends) in the application's desired wire format. self.expressInterest( self.getNextEntryId(), self._ndndIdFetcherInterest, fetcher.onData, fetcher.onTimeout, wireFormat, face) else: self._registerPrefixHelper( registeredPrefixId, prefixCopy, onInterest, onRegisterFailed, flags, wireFormat, face) else: # The application set the KeyChain for signing NFD interests. self._nfdRegisterPrefix( registeredPrefixId, prefixCopy, onInterest, onRegisterFailed, flags, commandKeyChain, commandCertificateName, face) def removeRegisteredPrefix(self, registeredPrefixId): """ Remove the registered prefix entry with the registeredPrefixId from the registered prefix table. This does not affect another registered prefix with a different registeredPrefixId, even if it has the same prefix name. If an interest filter was automatically created by registerPrefix, also remove it. If there is no entry with the registeredPrefixId, do nothing. :param int registeredPrefixId: The ID returned from registerPrefix. """ count = 0 # Go backwards through the list so we can erase entries. # Remove all entries even though registeredPrefixId should be unique. i = len(self._registeredPrefixTable) - 1 while i >= 0: entry = self._registeredPrefixTable[i] if (entry.getRegisteredPrefixId() == registeredPrefixId): count += 1 if entry.getRelatedInterestFilterId() > 0: # Remove the related interest filter. self.unsetInterestFilter(entry.getRelatedInterestFilterId()) self._registeredPrefixTable.pop(i) i -= 1 if count == 0: logging.getLogger(__name__).debug( "removeRegisteredPrefix: Didn't find registeredPrefixId " + registeredPrefixId) def setInterestFilter(self, interestFilterId, filterCopy, onInterest, face): """ Add an entry to the local interest filter table to call the onInterest callback for a matching incoming Interest. This method only modifies the library's local callback table and does not register the prefix with the forwarder. It will always succeed. To register a prefix with the forwarder, use registerPrefix. :param int interestFilterId: The getNextEntryId() for the interest filter ID which Face got so it could return it to the caller. :param InterestFilter filterCopy: The InterestFilter with a prefix and optional regex filter used to match the name of an incoming Interest, which is NOT copied for this internal Node method. The Face setInterestFilter is responsible for making a copy for Node to use. :param onInterest: When an Interest is received which matches the filter, this calls onInterest(prefix, interest, face, interestFilterId, filter). :type onInterest: function object :param Face face: The face which is passed to the onInterest callback. """ self._interestFilterTable.append(Node._InterestFilterEntry (interestFilterId, filterCopy, onInterest, face)) def unsetInterestFilter(self, interestFilterId): """ Remove the interest filter entry which has the interestFilterId from the interest filter table. This does not affect another interest filter with a different interestFilterId, even if it has the same prefix name. If there is no entry with the interestFilterId, do nothing. :param int interestFilterId: The ID returned from setInterestFilter. """ count = 0 # Go backwards through the list so we can erase entries. # Remove all entries even though interestFilterId should be unique. i = len(self._interestFilterTable) - 1 while i >= 0: if (self._interestFilterTable[i].getInterestFilterId() == interestFilterId): count += 1 self._interestFilterTable.pop(i) i -= 1 if count == 0: logging.getLogger(__name__).debug( "unsetInterestFilter: Didn't find interestFilterId " + interestFilterId) def send(self, encoding): """ Send the encoded packet out through the transport. :param encoding: The array of bytes for the encoded packet to send. :type encoding: An array type with int elements :throws: RuntimeError If the packet size exceeds getMaxNdnPacketSize(). """ if len(encoding) > self.getMaxNdnPacketSize(): raise RuntimeError( "The encoded packet size exceeds the maximum limit getMaxNdnPacketSize()") self._transport.send(encoding) def processEvents(self): """ Process any packets to receive and call callbacks such as onData, onInterest or onTimeout. This returns immediately if there is no data to receive. This blocks while calling the callbacks. You should repeatedly call this from an event loop, with calls to sleep as needed so that the loop doesn't use 100% of the CPU. Since processEvents modifies the pending interest table, your application should make sure that it calls processEvents in the same thread as expressInterest (which also modifies the pending interest table). :raises: This may raise an exception for reading data or in the callback for processing the data. If you call this from an main event loop, you may want to catch and log/disregard all exceptions. """ self._transport.processEvents() # Check for delayed calls. Since callLater does a sorted insert into # _delayedCallTable, the check for timeouts is quick and does not # require searching the entire table. If callLater is overridden to use # a different mechanism, then processEvents is not needed to check for # delayed calls. now = Common.getNowMilliseconds() # _delayedCallTable is sorted on _callTime, so we only need to process # the timed-out entries at the front, then quit. while (len(self._delayedCallTable) > 0 and self._delayedCallTable[0].getCallTime() <= now): delayedCall = self._delayedCallTable[0] del self._delayedCallTable[0] delayedCall.callCallback() def getTransport(self): """ Get the transport object given to the constructor. :return: The transport object. :rtype: Transport """ return self._transport def getConnectionInfo(self): """ Get the connectionInfo object given to the constructor. :return: The connectionInfo object. :rtype: Transport.ConnectionInfo """ return self._connectionInfo def onReceivedElement(self, element): """ This is called by the transport's ElementReader to process an entire received Data or Interest element. :param element: The bytes of the incoming element. :type element: An array type with int elements """ # The type codes for TLV Interest and Data packets are chosen to not # conflict with the first byte of a binary XML packet, so we canjust # look at the first byte. if not (element[0] == Tlv.Interest or element[0] == Tlv.Data): # Ignore non-TLV elements. # Assume it is Binary XML. if not WireFormat.ENABLE_NDNX: raise RuntimeError( "BinaryXmlWireFormat (NDNx) is deprecated. To enable while you upgrade your network to use NDN-TLV, set WireFormat.ENABLE_NDNX = True") return # First, decode as Interest or Data. interest = None data = None decoder = TlvDecoder(element) if decoder.peekType(Tlv.Interest, len(element)): interest = Interest() interest.wireDecode(element, TlvWireFormat.get()) elif decoder.peekType(Tlv.Data, len(element)): data = Data() data.wireDecode(element, TlvWireFormat.get()) # Now process as Interest or Data. if interest != None: # Call all interest filter callbacks which match. for i in range(len(self._interestFilterTable)): entry = self._interestFilterTable[i] if entry.getFilter().doesMatch(interest.getName()): includeFilter = True # Use getcallargs to test if onInterest accepts 5 args. try: inspect.getcallargs(entry.getOnInterest(), None, None, None, None, None) except TypeError: # Assume onInterest is old-style with 4 arguments. includeFilter = False if includeFilter: entry.getOnInterest()( entry.getFilter().getPrefix(), interest, entry.getFace(), entry.getInterestFilterId(), entry.getFilter()) else: # Old-style onInterest without the filter argument. We # still pass a Face instead of Transport since Face also # has a send method. entry.getOnInterest()( entry.getFilter().getPrefix(), interest, entry.getFace(), entry.getInterestFilterId()) elif data != None: pendingInterests = self._extractEntriesForExpressedInterest( data.getName()) for pendingInterest in pendingInterests: pendingInterest.getOnData()(pendingInterest.getInterest(), data) def isLocal(self): """ Check if the face is local based on the current connection through the Transport; some Transport may cause network I/O (e.g. an IP host name lookup). :return: True if the face is local, False if not. :rtype bool: """ return self._transport.isLocal(self._connectionInfo) def shutdown(self): """ Call getTransport().close(). """ self._transport.close() @staticmethod def getMaxNdnPacketSize(): """ Get the practical limit of the size of a network-layer packet. If a packet is larger than this, the library or application MAY drop it. :return: The maximum NDN packet size. :rtype: int """ return Common.MAX_NDN_PACKET_SIZE def _expressInterestHelper( self, pendingInterestId, interestCopy, onData, onTimeout, wireFormat, face): """ Do the work of expressInterest once we know we are connected. Add the entry to the PIT, encode and send the interest. :param int pendingInterestId: The getNextEntryId() for the pending interest ID which Face got so it could return it to the caller. :param Interest interestCopy: The Interest to send, which has already been copied. :param onData: A function object to call when a matching data packet is received. :type onData: function object :param onTimeout: A function object to call if the interest times out. If onTimeout is None, this does not use it. :type onTimeout: function object :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param Face face: The face which has the callLater method, used for interest timeouts. The callLater method may be overridden in a subclass of Face. :throws: RuntimeError If the encoded interest size exceeds getMaxNdnPacketSize(). """ pendingInterest = Node._PendingInterest( pendingInterestId, interestCopy, onData, onTimeout) self._pendingInterestTable.append(pendingInterest) if (interestCopy.getInterestLifetimeMilliseconds() != None and interestCopy.getInterestLifetimeMilliseconds() >= 0.0): # Set up the timeout. face.callLater(interestCopy.getInterestLifetimeMilliseconds(), lambda: self._processInterestTimeout(pendingInterest)) # Special case: For _timeoutPrefix we don't actually send the interest. if not self._timeoutPrefix.match(interestCopy.getName()): encoding = interestCopy.wireEncode(wireFormat) if encoding.size() > self.getMaxNdnPacketSize(): raise RuntimeError( "The encoded interest size exceeds the maximum limit getMaxNdnPacketSize()") self._transport.send(encoding.toBuffer()) def _extractEntriesForExpressedInterest(self, name): """ Find all entries from the _pendingInterestTable where the name conforms to the entry's interest selectors, remove the entries from the table and return them. :param Name name: The name to find the interest for (from the incoming data packet). :return: The matching entries from the _pendingInterestTable, or [] if none are found. :rtype: array of _PendingInterest """ result = [] # Go backwards through the list so we can erase entries. i = len(self._pendingInterestTable) - 1 while i >= 0: pendingInterest = self._pendingInterestTable[i] if pendingInterest.getInterest().matchesName(name): result.append(pendingInterest) # We let the callback from callLater call _processInterestTimeout, # but for efficiency, mark this as removed so that it returns # right away. self._pendingInterestTable.pop(i) pendingInterest.setIsRemoved() i -= 1 return result def _registerPrefixHelper( self, registeredPrefixId, prefix, onInterest, onRegisterFailed, flags, wireFormat, face): """ Do the work of registerPrefix to register with NDNx once we have an _ndndId. :param int registeredPrefixId: The getNextEntryId() which registerPrefix got so it could return it to the caller. If this is 0, then don't add to _registeredPrefixTable (assuming it has already been done). """ if not WireFormat.ENABLE_NDNX: # We can get here if the command signing info is set, but running NDNx. raise RuntimeError( "registerPrefix with NDNx is deprecated. To enable while you upgrade your code to use NFD, set WireFormat.ENABLE_NDNX = True") # Create a ForwardingEntry. # Note: ndnd ignores any freshness that is larger than 3600 seconds and # sets 300 seconds instead. To register "forever", (=2000000000 sec), # the freshness period must be omitted. forwardingEntry = ForwardingEntry() forwardingEntry.setAction("selfreg") forwardingEntry.setPrefix(prefix) forwardingEntry.setForwardingFlags(flags) content = forwardingEntry.wireEncode(wireFormat) # Set the ForwardingEntry as the content of a Data packet and sign. data = Data() data.setContent(content) # Set the name to a random value so that each request is unique. nonce = bytearray(4) for i in range(len(nonce)): nonce[i] = _systemRandom.randint(0, 0xff) data.getName().append(nonce) # The ndnd ignores the signature, so set to blank values. data.getSignature().getKeyLocator().setType( KeyLocatorType.KEY_LOCATOR_DIGEST) data.getSignature().getKeyLocator().setKeyData( Blob(bytearray(32), False)) data.getSignature().setSignature(Blob(bytearray(128), False)) encodedData = data.wireEncode(wireFormat) # Create an interest where the name has the encoded Data packet. interestName = Name().append("ndnx").append(self._ndndId).append( "selfreg").append(encodedData) interest = Interest(interestName) interest.setInterestLifetimeMilliseconds(4000.0) interest.setScope(1) if registeredPrefixId != 0: interestFilterId = 0 if onInterest != None: # registerPrefix was called with the "combined" form that includes # the callback, so add an InterestFilterEntry. interestFilterId = self.getNextEntryId() self.setInterestFilter( interestFilterId, InterestFilter(prefix), onInterest, face) self._registeredPrefixTable.append(Node._RegisteredPrefix( registeredPrefixId, prefix, interestFilterId)) # Send the registration interest. response = Node._RegisterResponse( self, prefix, onInterest, onRegisterFailed, flags, wireFormat, False, face) self.expressInterest( self.getNextEntryId(), interest, response.onData, response.onTimeout, wireFormat, face) def _nfdRegisterPrefix( self, registeredPrefixId, prefix, onInterest, onRegisterFailed, flags, commandKeyChain, commandCertificateName, face): """ Do the work of registerPrefix to register with NFD. :param int registeredPrefixId: The getNextEntryId() which registerPrefix got so it could return it to the caller. If this is 0, then don't add to _registeredPrefixTable (assuming it has already been done). """ if commandKeyChain == None: raise RuntimeError( "registerPrefix: The command KeyChain has not been set. You must call setCommandSigningInfo.") if commandCertificateName.size() == 0: raise RuntimeError( "registerPrefix: The command certificate name has not been set. You must call setCommandSigningInfo.") controlParameters = ControlParameters() controlParameters.setName(prefix) controlParameters.setForwardingFlags(flags) commandInterest = Interest() if self.isLocal(): commandInterest.setName(Name("/localhost/nfd/rib/register")) # The interest is answered by the local host, so set a short timeout. commandInterest.setInterestLifetimeMilliseconds(2000.0) else: commandInterest.setName(Name("/localhop/nfd/rib/register")) # The host is remote, so set a longer timeout. commandInterest.setInterestLifetimeMilliseconds(4000.0) # NFD only accepts TlvWireFormat packets. commandInterest.getName().append(controlParameters.wireEncode(TlvWireFormat.get())) self.makeCommandInterest( commandInterest, commandKeyChain, commandCertificateName, TlvWireFormat.get()) if registeredPrefixId != 0: interestFilterId = 0 if onInterest != None: # registerPrefix was called with the "combined" form that includes # the callback, so add an InterestFilterEntry. interestFilterId = self.getNextEntryId() self.setInterestFilter( interestFilterId, InterestFilter(prefix), onInterest, face) self._registeredPrefixTable.append(Node._RegisteredPrefix( registeredPrefixId, prefix, interestFilterId)) # Send the registration interest. response = Node._RegisterResponse( self, prefix, onInterest, onRegisterFailed, flags, TlvWireFormat.get(), True, face) self.expressInterest( self.getNextEntryId(), commandInterest, response.onData, response.onTimeout, TlvWireFormat.get(), face) def callLater(self, delayMilliseconds, callback): """ Call callback() after the given delay. This adds to self._delayedCallTable which is used by processEvents(). :param float delayMilliseconds: The delay in milliseconds. :param callback: This calls callback() after the delay. :type callback: function object """ delayedCall = Node._DelayedCall(delayMilliseconds, callback) # Insert into _delayedCallTable, sorted on delayedCall.getCallTime(). # Search from the back since we expect it to go there. i = len(self._delayedCallTable) - 1 while i >= 0: if (self._delayedCallTable[i].getCallTime() <= delayedCall.getCallTime()): break i -= 1 # Element i is the greatest less than or equal to # delayedCall.getCallTime(), so insert after it. self._delayedCallTable.insert(i + 1, delayedCall) def _processInterestTimeout(self, pendingInterest): """ This is used in callLater for when the pending interest expires. If the pendingInterest is still in the _pendingInterestTable, remove it and call its onTimeout callback. """ if pendingInterest.getIsRemoved(): # _extractEntriesForExpressedInterest or removePendingInterest has # removed pendingInterest from _pendingInterestTable, so we don't # need to look for it. Do nothing. return try: index = self._pendingInterestTable.index(pendingInterest) except ValueError: # The pending interest has been removed. Do nothing. return del self._pendingInterestTable[index] pendingInterest.callTimeout() def getNextEntryId(self): """ Get the next unique entry ID for the pending interest table, interest filter table, etc. This uses a threading.Lock() to be thread safe. Most entry IDs are for the pending interest table (there usually are not many interest filter table entries) so we use a common pool to only have to do the thread safe lock in one method which is called by Face. :return: The next entry ID. :rtype: int """ with self._lastEntryIdLock: self._lastEntryId += 1 return self._lastEntryId class _ConnectStatus(object): UNCONNECTED = 1 CONNECT_REQUESTED = 2 CONNECT_COMPLETE = 3 class _DelayedCall(object): """ _DelayedCall is a private class for the members of the _delayedCallTable. Create a new _DelayedCall and set the call time based on the current time and the delayMilliseconds. :param float delayMilliseconds: The delay in milliseconds. :param callback: This calls callback() after the delay. :type callback: function object """ def __init__(self, delayMilliseconds, callback): self._callback = callback self._callTime = Common.getNowMilliseconds() + delayMilliseconds def getCallTime(self): """ Get the time at which the callback should be called. :return: The call time in milliseconds, similar to Common.getNowMilliseconds(). :rtype: float """ return self._callTime def callCallback(self): """ Call the callback given to the constructor. This does not catch exceptions. """ self._callback() class _PendingInterest(object): """ _PendingInterest is a private class for the members of the _pendingInterestTable. :param int pendingInterestId: A unique ID for this entry, which you should get with getNextEntryId(). :param Interest interest: The interest. :param onData: A function object to call when a matching data packet is received. :type onData: function object :param onTimeout: A function object to call if the interest times out. If onTimeout is None, this does not use it. :type onTimeout: function object """ def __init__(self, pendingInterestId, interest, onData, onTimeout): self._pendingInterestId = pendingInterestId self._interest = interest self._onData = onData self._onTimeout = onTimeout self._isRemoved = False def getPendingInterestId(self): """ Get the pendingInterestId given to the constructor. :return: The pending interest ID. :rtype: int """ return self._pendingInterestId def getInterest(self): """ Get the interest given to the constructor. :return: The interest. :rtype: int """ return self._interest def getOnData(self): """ Get the onData function object given to the constructor. :return: The onData function object. :rtype: function object """ return self._onData def callTimeout(self): """ Call _onTimeout (if defined). This ignores exceptions from _onTimeout. """ if self._onTimeout: # Ignore all exceptions. try: self._onTimeout(self._interest) except: pass def setIsRemoved(self): """ Set the isRemoved flag which is returned by getIsRemoved(). """ self._isRemoved = True def getIsRemoved(self): """ Check if setIsRemoved() was called. :return: True if setIsRemoved() was called. :rtype: bool """ return self._isRemoved class _RegisteredPrefix(object): """ A _RegisteredPrefix holds a registeredPrefixId and information necessary to remove the registration later. It optionally holds a related interestFilterId if the InterestFilter was set in the same registerPrefix operation. :param int registeredPrefixId: A unique ID for this entry, which you should get with getNextEntryId(). :param Name prefix: The name prefix. :param int relatedInterestFilterId: (optional) The related interestFilterId for the filter set in the same registerPrefix operation. If omitted, set to 0. """ def __init__(self, registeredPrefixId, prefix, relatedInterestFilterId): self._registeredPrefixId = registeredPrefixId self._prefix = prefix self._relatedInterestFilterId = relatedInterestFilterId def getRegisteredPrefixId(self): """ Get the registeredPrefixId given to the constructor. :return: The registered prefix ID. :rtype: int """ return self._registeredPrefixId def getPrefix(self): """ Get the name prefix to the constructor. :return: The name prefix. :rtype: Name """ return self._prefix def getRelatedInterestFilterId(self): """ Get the related interestFilterId given to the constructor. :return: The related interestFilterId. :rtype: int """ return self._relatedInterestFilterId class _InterestFilterEntry(object): """ An _InterestFilterEntry holds an interestFilterId, an InterestFilter and the OnInterestCallback with its related Face. Create a new InterestFilterEntry with the given values. :param int interestFilterId: The ID from getNextEntryId(). :param InterestFilter filter: The InterestFilter for this entry. :param onInterest: The callback to call. :type onInterest: function object :param Face face: The face on which was called registerPrefix or setInterestFilter which is passed to the onInterest callback. """ def __init__(self, interestFilterId, filter, onInterest, face): self._interestFilterId = interestFilterId self._filter = filter self._onInterest = onInterest self._face = face def getInterestFilterId(self): """ Get the interestFilterId given to the constructor. :return: The interestFilterId. :rtype: int """ return self._interestFilterId def getFilter(self): """ Get the InterestFilter given to the constructor. :return: The InterestFilter. :rtype: InterestFilter """ return self._filter def getOnInterest(self): """ Get the OnInterestCallback given to the constructor. :return: The OnInterestCallback. :rtype: function object """ return self._onInterest def getFace(self): """ Get the Face given to the constructor. :return: The Face. :rtype: Face """ return self._face class _NdndIdFetcher(object): """ An _NdndIdFetcher receives the Data packet with the publisher public key digest for the connected NDN hub. """ def __init__(self, node, registeredPrefixId, prefix, onInterest, onRegisterFailed, flags, wireFormat, face): self._node = node self._registeredPrefixId = registeredPrefixId self._prefix = prefix self._onInterest = onInterest self._onRegisterFailed = onRegisterFailed self._flags = flags self._wireFormat = wireFormat self._face = face def onData(self, interest, ndndIdData): """ We received the ndnd ID. """ # Assume that the content is a DER encoded public key of the ndnd. # Do a quick check that the first byte is for DER encoding. if (ndndIdData.getContent().size() < 1 or ndndIdData.getContent().buf()[0] != 0x30): logging.getLogger(__name__).info( "Register prefix failed: The content returned when fetching the NDNx ID does not appear to be a public key") self._onRegisterFailed(self._prefix) return # Get the digest of the public key. digest = bytearray( hashlib.sha256(ndndIdData.getContent().toBuffer()).digest()) # Set the _ndndId and continue. # TODO: If there are multiple connected hubs, the NDN ID is really # stored per connected hub. self._node._ndndId = Blob(digest, False) self._node._registerPrefixHelper( self._registeredPrefixId, self._prefix, self._onInterest, self._onRegisterFailed, self._flags, self._wireFormat, self._face) def onTimeout(self, interest): """ We timed out fetching the ndnd ID. """ logging.getLogger(__name__).info( "Register prefix failed: Timeout fetching the NDNx ID") self._onRegisterFailed(self._prefix) class _RegisterResponse(object): """ A _RegisterResponse receives the response Data packet from the register prefix interest sent to the connected NDN hub. If this gets a bad response or a timeout, call onRegisterFailed. """ def __init__(self, node, prefix, onInterest, onRegisterFailed, flags, wireFormat, isNfdCommand, face): self._node = node self._prefix = prefix self._onInterest = onInterest self._onRegisterFailed = onRegisterFailed self._flags = flags self._wireFormat = wireFormat self._isNfdCommand = isNfdCommand self._face = face def onData(self, interest, responseData): """ We received the response. Do a quick check of expected name components. """ if self._isNfdCommand: # Decode responseData->getContent() and check for a success code. # TODO: Move this into the TLV code. statusCode = None try: decoder = TlvDecoder(responseData.getContent().buf()) decoder.readNestedTlvsStart(Tlv.NfdCommand_ControlResponse) statusCode = decoder.readNonNegativeIntegerTlv(Tlv.NfdCommand_StatusCode) except ValueError as ex: logging.getLogger(__name__).info( "Register prefix failed: Error decoding the NFD response: %s", str(ex)) self._onRegisterFailed(self._prefix) return # Status code 200 is "OK". if statusCode != 200: logging.getLogger(__name__).info( "Register prefix failed: Expected NFD status code 200, got: %d", statusCode) self._onRegisterFailed(self._prefix) return logging.getLogger(__name__).info( "Register prefix succeeded with the NFD forwarder for prefix %s", self._prefix.toUri()) else: expectedName = Name("/ndnx/.../selfreg") if (responseData.getName().size() < 4 or responseData.getName()[0] != expectedName[0] or responseData.getName()[2] != expectedName[2]): logging.getLogger(__name__).info( "Register prefix failed: Unexpected name in NDNx response: %s", responseData.getName().toUri()) self._onRegisterFailed(self._prefix) return logging.getLogger(__name__).info( "Register prefix succeeded with the NDNx forwarder for prefix %s", self._prefix.toUri()) def onTimeout(self, interest): """ We timed out waiting for the response. """ if self._isNfdCommand: logging.getLogger(__name__).info( "Timeout for NFD register prefix command. Attempting an NDNx command...") # The application set the commandKeyChain, but we may be # connected to NDNx. if self._node._ndndId == None: # First fetch the ndndId of the connected hub. # Pass 0 for registeredPrefixId since the entry was already added to # _registeredPrefixTable on the first try. fetcher = Node._NdndIdFetcher( self._node, 0, self._prefix, self._onInterest, self._onRegisterFailed, self._flags, self._wireFormat, self._face) # We send the interest using the given wire format so that the hub # receives (and sends) in the application's desired wire format. self._node.expressInterest( self._node.getNextEntryId(), self._node._ndndIdFetcherInterest, fetcher.onData, fetcher.onTimeout, self._wireFormat, self._face) else: # Pass 0 for registeredPrefixId since the entry was already # added to _registeredPrefixTable on the first try. self._node._registerPrefixHelper( 0, self._prefix, self._onInterest, self._onRegisterFailed, self._flags, self._wireFormat) else: # An NDNx command was sent because there is no commandKeyChain, # so we can't try an NFD command. Or it was sent from this # callback after trying an NFD command. Fail. logging.getLogger(__name__).info( "Register prefix failed: Timeout waiting for the response from the register prefix interest") self._onRegisterFailed(self._prefix)
class Consumer(object): """ Create a Consumer to use the given ConsumerDb, Face and other values. :param Face face: The face used for data packet and key fetching. :param KeyChain keyChain: The keyChain used to verify data packets. :param Name groupName: The reading group name that the consumer belongs to. This makes a copy of the Name. :param Name consumerName: The identity of the consumer. This makes a copy of the Name. :param ConsumerDb database: The ConsumerDb database for storing decryption keys. :param Link cKeyLink: (optional) The Link object to use in Interests for C-KEY retrieval. This makes a copy of the Link object. If the Link object's getDelegations().size() is zero, don't use it. If omitted, don't use a Link object. :param Link dKeyLink: (optional) The Link object to use in Interests for D-KEY retrieval. This makes a copy of the Link object. If the Link object's getDelegations().size() is zero, don't use it. If omitted, don't use a Link object. """ def __init__(self, face, keyChain, groupName, consumerName, database, cKeyLink=None, dKeyLink=None): self._database = database self._keyChain = keyChain self._face = face self._groupName = Name(groupName) self._consumerName = Name(consumerName) self._cKeyLink = (Consumer.NO_LINK if cKeyLink == None else Link(cKeyLink)) self._dKeyLink = (Consumer.NO_LINK if dKeyLink == None else Link(dKeyLink)) # The dictionary key is the C-KEY name. The value is the encoded key Blob. self._cKeyMap = {} # The dictionary key is the D-KEY name. The value is the encoded key Blob. self._dKeyMap = {} def consume(self, contentName, onConsumeComplete, onError, link=None): """ Express an Interest to fetch the content packet with contentName, and decrypt it, fetching keys as needed. :param Name contentName: The name of the content packet. :param onConsumeComplete: When the content packet is fetched and decrypted, this calls onConsumeComplete(contentData, result) where contentData is the fetched Data packet and result is the decrypted plain text Blob. NOTE: The library will log any exceptions raised by this callback, but for better error handling the callback should catch and properly handle any exceptions. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. NOTE: The library will log any exceptions raised by this callback, but for better error handling the callback should catch and properly handle any exceptions. :type onError: function object :param Link link: (optional) The Link object to use in Interests for data retrieval. This makes a copy of the Link object. If the Link object's getDelegations().size() is zero, don't use it. If omitted, don't use a Link object. """ if link == None: link = Consumer.NO_LINK interest = Interest(contentName) def onVerified(validData): # Decrypt the content. def onPlainText(plainText): try: onConsumeComplete(validData, plainText) except: logging.exception("Error in onConsumeComplete") self._decryptContent(validData, onPlainText, onError) # Copy the Link object since the passed link may become invalid. self._sendInterest(interest, 1, Link(link), onVerified, onError) def setGroup(self, groupName): """ Set the group name. :param Name groupName: The reading group name that the consumer belongs to. This makes a copy of the Name. """ self._groupName = Name(groupName) def addDecryptionKey(self, keyName, keyBlob): """ Add a new decryption key with keyName and keyBlob to the database. :param Name keyName: The key name. :param Blob keyBlob: The encoded key. :raises ConsumerDb.Error: If a key with the same keyName already exists in the database, or other database error. :raises RuntimeError: if the consumer name is not a prefix of the key name. """ if not self._consumerName.match(keyName): raise RuntimeError( "addDecryptionKey: The consumer name must be a prefix of the key name" ) self._database.addKey(keyName, keyBlob) @staticmethod def _decrypt(encryptedContent, keyBits, onPlainText, onError): """ Decrypt encryptedContent using keyBits. :param encryptedContent: The EncryptedContent to decrypt, or a Blob which is first decoded as an EncryptedContent. :type encryptedContent: Blob or EncryptedContent :param {Blob} keyBits The key value. :param onPlainText: When encryptedBlob is decrypted, this calls onPlainText(decryptedBlob) with the decrypted Blob. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ if isinstance(encryptedContent, Blob): # Decode as EncryptedContent. encryptedBlob = encryptedContent encryptedContent = EncryptedContent() encryptedContent.wireDecode(encryptedBlob) payload = encryptedContent.getPayload() if encryptedContent.getAlgorithmType() == EncryptAlgorithmType.AesCbc: # Prepare the parameters. decryptParams = EncryptParams(EncryptAlgorithmType.AesCbc) decryptParams.setInitialVector(encryptedContent.getInitialVector()) # Decrypt the content. try: content = AesAlgorithm.decrypt(keyBits, payload, decryptParams) except Exception as ex: try: onError(EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) except: logging.exception("Error in onError") return onPlainText(content) elif encryptedContent.getAlgorithmType( ) == EncryptAlgorithmType.RsaOaep: # Prepare the parameters. decryptParams = EncryptParams(EncryptAlgorithmType.RsaOaep) # Decrypt the content. try: content = RsaAlgorithm.decrypt(keyBits, payload, decryptParams) except Exception as ex: Consumer._callOnError( onError, EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) return onPlainText(content) else: Consumer._callOnError( onError, EncryptError.ErrorCode.UnsupportedEncryptionScheme, repr(encryptedContent.getAlgorithmType())) def _decryptContent(self, data, onPlainText, onError): """ Decrypt the data packet. :param Data data: The data packet. This does not verify the packet. :param onPlainText: When the data packet is decrypted, this calls onPlainText(decryptedBlob) with the decrypted blob. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ # Get the encrypted content. dataEncryptedContent = EncryptedContent() try: dataEncryptedContent.wireDecode(data.getContent()) except Exception as ex: Consumer._callOnError( onError, EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) return cKeyName = dataEncryptedContent.getKeyLocator().getKeyName() # Check if the content key is already in the store. if cKeyName in self._cKeyMap: cKey = self._cKeyMap[cKeyName] self._decrypt(dataEncryptedContent, cKey, onPlainText, onError) else: # Retrieve the C-KEY Data from the network. interestName = Name(cKeyName) interestName.append(Encryptor.NAME_COMPONENT_FOR).append( self._groupName) interest = Interest(interestName) def onVerified(validCKeyData): def localOnPlainText(cKeyBits): # cKeyName is already a copy inside the local # dataEncryptedContent. self._cKeyMap[cKeyName] = cKeyBits Consumer._decrypt(dataEncryptedContent, cKeyBits, onPlainText, onError) self._decryptCKey(validCKeyData, localOnPlainText, onError) self._sendInterest(interest, 1, self._cKeyLink, onVerified, onError) def _decryptCKey(self, cKeyData, onPlainText, onError): """ Decrypt cKeyData. :param Data cKeyData: The C-KEY data packet. :param onPlainText: When the data packet is decrypted, this calls onPlainText(decryptedBlob) with the decrypted blob. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ # Get the encrypted content. cKeyContent = cKeyData.getContent() cKeyEncryptedContent = EncryptedContent() try: cKeyEncryptedContent.wireDecode(cKeyContent) except Exception as ex: try: onError(EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) except: logging.exception("Error in onError") return eKeyName = cKeyEncryptedContent.getKeyLocator().getKeyName() dKeyName = eKeyName.getPrefix(-3) dKeyName.append(Encryptor.NAME_COMPONENT_D_KEY).append( eKeyName.getSubName(-2)) # Check if the decryption key is already in the store. if dKeyName in self._dKeyMap: dKey = self._dKeyMap[dKeyName] Consumer._decrypt(cKeyEncryptedContent, dKey, onPlainText, onError) else: # Get the D-Key Data. interestName = Name(dKeyName) interestName.append(Encryptor.NAME_COMPONENT_FOR).append( self._consumerName) interest = Interest(interestName) def onVerified(validDKeyData): def localOnPlainText(dKeyBits): # dKeyName is already a local copy. self._dKeyMap[dKeyName] = dKeyBits Consumer._decrypt(cKeyEncryptedContent, dKeyBits, onPlainText, onError) self._decryptDKey(validDKeyData, localOnPlainText, onError) self._sendInterest(interest, 1, self._dKeyLink, onVerified, onError) def _decryptDKey(self, dKeyData, onPlainText, onError): """ Decrypt dKeyData. :param Data dKeyData: The D-KEY data packet. :param onPlainText: When the data packet is decrypted, this calls onPlainText(decryptedBlob) with the decrypted blob. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ # Get encrypted content. dataContent = dKeyData.getContent() # Process the nonce. # dataContent is a sequence of the two EncryptedContent. encryptedNonce = EncryptedContent() try: encryptedNonce.wireDecode(dataContent) except Exception as ex: try: onError(EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) except: logging.exception("Error in onError") return consumerKeyName = encryptedNonce.getKeyLocator().getKeyName() # Get consumer decryption key. try: consumerKeyBlob = self._getDecryptionKey(consumerKeyName) except Exception as ex: Consumer._callOnError(onError, EncryptError.ErrorCode.NoDecryptKey, "Database error: " + repr(ex)) return if consumerKeyBlob.size() == 0: try: onError( EncryptError.ErrorCode.NoDecryptKey, "The desired consumer decryption key in not in the database" ) except: logging.exception("Error in onError") return # Process the D-KEY. # Use the size of encryptedNonce to find the start of encryptedPayload. encryptedPayloadBlob = Blob( dataContent.buf()[encryptedNonce.wireEncode().size():], False) if encryptedPayloadBlob.size() == 0: try: onError( EncryptError.ErrorCode.InvalidEncryptedFormat, "The data packet does not satisfy the D-KEY packet format") except: logging.exception("Error in onError") return # Decrypt the D-KEY. Consumer._decrypt( encryptedNonce, consumerKeyBlob, lambda nonceKeyBits: Consumer._decrypt( encryptedPayloadBlob, nonceKeyBits, onPlainText, onError), onError) def _sendInterest(self, interest, nRetrials, link, onVerified, onError): """ Express the interest, call verifyData for the fetched Data packet and call onVerified if verify succeeds. If verify fails, call onError(EncryptError.ErrorCode.Validation, "verifyData failed"). If the interest times out, re-express nRetrials times. If the interest times out nRetrials times, or for a network Nack, call onError(EncryptError.ErrorCode.DataRetrievalFailure, interest.getName().toUri()). :param Interest interest: The Interest to express. :param int nRetrials: The number of retrials left after a timeout. :param Link link: The Link object to use in the Interest. This does not make a copy of the Link object. If the Link object's getDelegations().size() is zero, don't use it. :param onVerified: When the fetched Data packet validation succeeds, this calls onVerified(data). :type onVerified: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ # Prepare the callback functions. def onData(contentInterest, contentData): # The Interest has no selectors, so assume the library correctly # matched with the Data name before calling onData. try: self._keyChain.verifyData( contentData, onVerified, lambda d, reason: Consumer._callOnError( onError, EncryptError.ErrorCode.Validation, "verifyData failed. Reason: " + reason)) except Exception as ex: try: onError(EncryptError.ErrorCode.General, "verifyData error: " + repr(ex)) except: logging.exception("Error in onError") def onNetworkNack(interest, networkNack): # We have run out of options. Report a retrieval failure. try: onError(EncryptError.ErrorCode.DataRetrievalFailure, interest.getName().toUri()) except: logging.exception("Error in onError") def onTimeout(interest): if nRetrials > 0: self._sendInterest(interest, nRetrials - 1, link, onVerified, onError) else: onNetworkNack(interest, NetworkNack()) if link.getDelegations().size() == 0: # We can use the supplied interest without copying. request = interest else: # Copy the supplied interest and add the Link. request = Interest(interest) # This will use a cached encoding if available. request.setLinkWireEncoding(link.wireEncode()) try: self._face.expressInterest(request, onData, onTimeout, onNetworkNack) except Exception as ex: try: onError(EncryptError.ErrorCode.General, "expressInterest error: " + repr(ex)) except: logging.exception("Error in onError") def _getDecryptionKey(self, decryptionKeyName): """ Get the encoded blob of the decryption key with decryptionKeyName from the database. :param Name decryptionKeyName: The key name. :return: A Blob with the encoded key, or an isNull Blob if cannot find the key with keyName. :rtype: Blob :raises ConsumerDb.Error: For a database error. """ return self._database.getKey(decryptionKeyName) @staticmethod def _callOnError(onError, errorCode, message): """ Call onError(errorCode, message) within a try block to log exceptions that it throws. We name this separate helper function to use inside lambda expressions. """ try: onError(errorCode, message) except: logging.exception("Error in onError") NO_LINK = Link()
class Consumer(object): """ Create a Consumer to use the given ConsumerDb, Face and other values. :param Face face: The face used for data packet and key fetching. :param KeyChain keyChain: The keyChain used to verify data packets. :param Name groupName: The reading group name that the consumer belongs to. This makes a copy of the Name. :param Name consumerName: The identity of the consumer. This makes a copy of the Name. :param ConsumerDb database: The ConsumerDb database for storing decryption keys. :param Link cKeyLink: (optional) The Link object to use in Interests for C-KEY retrieval. This makes a copy of the Link object. If the Link object's getDelegations().size() is zero, don't use it. If omitted, don't use a Link object. :param Link dKeyLink: (optional) The Link object to use in Interests for D-KEY retrieval. This makes a copy of the Link object. If the Link object's getDelegations().size() is zero, don't use it. If omitted, don't use a Link object. """ def __init__(self, face, keyChain, groupName, consumerName, database, cKeyLink = None, dKeyLink = None): self._database = database self._keyChain = keyChain self._face = face self._groupName = Name(groupName) self._consumerName = Name(consumerName) self._cKeyLink = (Consumer.NO_LINK if cKeyLink == None else Link(cKeyLink)) self._dKeyLink = (Consumer.NO_LINK if dKeyLink == None else Link(dKeyLink)) # The dictionary key is the C-KEY name. The value is the encoded key Blob. self._cKeyMap = {} # The dictionary key is the D-KEY name. The value is the encoded key Blob. self._dKeyMap = {} def consume(self, contentName, onConsumeComplete, onError, link = None): """ Express an Interest to fetch the content packet with contentName, and decrypt it, fetching keys as needed. :param Name contentName: The name of the content packet. :param onConsumeComplete: When the content packet is fetched and decrypted, this calls onConsumeComplete(contentData, result) where contentData is the fetched Data packet and result is the decrypted plain text Blob. NOTE: The library will log any exceptions raised by this callback, but for better error handling the callback should catch and properly handle any exceptions. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. NOTE: The library will log any exceptions raised by this callback, but for better error handling the callback should catch and properly handle any exceptions. :type onError: function object :param Link link: (optional) The Link object to use in Interests for data retrieval. This makes a copy of the Link object. If the Link object's getDelegations().size() is zero, don't use it. If omitted, don't use a Link object. """ if link == None: link = Consumer.NO_LINK interest = Interest(contentName) def onVerified(validData): # Decrypt the content. def onPlainText(plainText): try: onConsumeComplete(validData, plainText) except: logging.exception("Error in onConsumeComplete") self._decryptContent(validData, onPlainText, onError) # Copy the Link object since the passed link may become invalid. self._sendInterest(interest, 1, Link(link), onVerified, onError) def setGroup(self, groupName): """ Set the group name. :param Name groupName: The reading group name that the consumer belongs to. This makes a copy of the Name. """ self._groupName = Name(groupName) def addDecryptionKey(self, keyName, keyBlob): """ Add a new decryption key with keyName and keyBlob to the database. :param Name keyName: The key name. :param Blob keyBlob: The encoded key. :raises ConsumerDb.Error: If a key with the same keyName already exists in the database, or other database error. :raises RuntimeError: if the consumer name is not a prefix of the key name. """ if not self._consumerName.match(keyName): raise RuntimeError( "addDecryptionKey: The consumer name must be a prefix of the key name") self._database.addKey(keyName, keyBlob) @staticmethod def _decrypt(encryptedContent, keyBits, onPlainText, onError): """ Decrypt encryptedContent using keyBits. :param encryptedContent: The EncryptedContent to decrypt, or a Blob which is first decoded as an EncryptedContent. :type encryptedContent: Blob or EncryptedContent :param {Blob} keyBits The key value. :param onPlainText: When encryptedBlob is decrypted, this calls onPlainText(decryptedBlob) with the decrypted Blob. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ if isinstance(encryptedContent, Blob): # Decode as EncryptedContent. encryptedBlob = encryptedContent encryptedContent = EncryptedContent() encryptedContent.wireDecode(encryptedBlob) payload = encryptedContent.getPayload() if encryptedContent.getAlgorithmType() == EncryptAlgorithmType.AesCbc: # Prepare the parameters. decryptParams = EncryptParams(EncryptAlgorithmType.AesCbc) decryptParams.setInitialVector(encryptedContent.getInitialVector()) # Decrypt the content. try: content = AesAlgorithm.decrypt(keyBits, payload, decryptParams) except Exception as ex: try: onError(EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) except: logging.exception("Error in onError") return onPlainText(content) elif encryptedContent.getAlgorithmType() == EncryptAlgorithmType.RsaOaep: # Prepare the parameters. decryptParams = EncryptParams(EncryptAlgorithmType.RsaOaep) # Decrypt the content. try: content = RsaAlgorithm.decrypt(keyBits, payload, decryptParams) except Exception as ex: Consumer._callOnError(onError, EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) return onPlainText(content) else: Consumer._callOnError(onError, EncryptError.ErrorCode.UnsupportedEncryptionScheme, repr(encryptedContent.getAlgorithmType())) def _decryptContent(self, data, onPlainText, onError): """ Decrypt the data packet. :param Data data: The data packet. This does not verify the packet. :param onPlainText: When the data packet is decrypted, this calls onPlainText(decryptedBlob) with the decrypted blob. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ # Get the encrypted content. dataEncryptedContent = EncryptedContent() try: dataEncryptedContent.wireDecode(data.getContent()) except Exception as ex: Consumer._callOnError(onError, EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) return cKeyName = dataEncryptedContent.getKeyLocator().getKeyName() # Check if the content key is already in the store. if cKeyName in self._cKeyMap: cKey = self._cKeyMap[cKeyName] self._decrypt(dataEncryptedContent, cKey, onPlainText, onError) else: # Retrieve the C-KEY Data from the network. interestName = Name(cKeyName) interestName.append(Encryptor.NAME_COMPONENT_FOR).append(self._groupName) interest = Interest(interestName) def onVerified(validCKeyData): def localOnPlainText(cKeyBits): # cKeyName is already a copy inside the local # dataEncryptedContent. self._cKeyMap[cKeyName] = cKeyBits Consumer._decrypt( dataEncryptedContent, cKeyBits, onPlainText, onError) self._decryptCKey(validCKeyData, localOnPlainText, onError) self._sendInterest(interest, 1, self._cKeyLink, onVerified, onError) def _decryptCKey(self, cKeyData, onPlainText, onError): """ Decrypt cKeyData. :param Data cKeyData: The C-KEY data packet. :param onPlainText: When the data packet is decrypted, this calls onPlainText(decryptedBlob) with the decrypted blob. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ # Get the encrypted content. cKeyContent = cKeyData.getContent() cKeyEncryptedContent = EncryptedContent() try: cKeyEncryptedContent.wireDecode(cKeyContent) except Exception as ex: try: onError(EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) except: logging.exception("Error in onError") return eKeyName = cKeyEncryptedContent.getKeyLocator().getKeyName() dKeyName = eKeyName.getPrefix(-3) dKeyName.append(Encryptor.NAME_COMPONENT_D_KEY).append( eKeyName.getSubName(-2)) # Check if the decryption key is already in the store. if dKeyName in self._dKeyMap: dKey = self._dKeyMap[dKeyName] Consumer._decrypt(cKeyEncryptedContent, dKey, onPlainText, onError) else: # Get the D-Key Data. interestName = Name(dKeyName) interestName.append(Encryptor.NAME_COMPONENT_FOR).append( self._consumerName) interest = Interest(interestName) def onVerified(validDKeyData): def localOnPlainText(dKeyBits): # dKeyName is already a local copy. self._dKeyMap[dKeyName] = dKeyBits Consumer._decrypt( cKeyEncryptedContent, dKeyBits, onPlainText, onError) self._decryptDKey(validDKeyData, localOnPlainText, onError) self._sendInterest(interest, 1, self._dKeyLink, onVerified, onError) def _decryptDKey(self, dKeyData, onPlainText, onError): """ Decrypt dKeyData. :param Data dKeyData: The D-KEY data packet. :param onPlainText: When the data packet is decrypted, this calls onPlainText(decryptedBlob) with the decrypted blob. :type onPlainText: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ # Get encrypted content. dataContent = dKeyData.getContent() # Process the nonce. # dataContent is a sequence of the two EncryptedContent. encryptedNonce = EncryptedContent() try: encryptedNonce.wireDecode(dataContent) except Exception as ex: try: onError(EncryptError.ErrorCode.InvalidEncryptedFormat, repr(ex)) except: logging.exception("Error in onError") return consumerKeyName = encryptedNonce.getKeyLocator().getKeyName() # Get consumer decryption key. try: consumerKeyBlob = self._getDecryptionKey(consumerKeyName) except Exception as ex: Consumer._callOnError(onError, EncryptError.ErrorCode.NoDecryptKey, "Database error: " + repr(ex)) return if consumerKeyBlob.size() == 0: try: onError(EncryptError.ErrorCode.NoDecryptKey, "The desired consumer decryption key in not in the database") except: logging.exception("Error in onError") return # Process the D-KEY. # Use the size of encryptedNonce to find the start of encryptedPayload. encryptedPayloadBlob = Blob( dataContent.buf()[encryptedNonce.wireEncode().size():], False) if encryptedPayloadBlob.size() == 0: try: onError(EncryptError.ErrorCode.InvalidEncryptedFormat, "The data packet does not satisfy the D-KEY packet format") except: logging.exception("Error in onError") return # Decrypt the D-KEY. Consumer._decrypt( encryptedNonce, consumerKeyBlob, lambda nonceKeyBits: Consumer._decrypt( encryptedPayloadBlob, nonceKeyBits, onPlainText, onError), onError) def _sendInterest(self, interest, nRetrials, link, onVerified, onError): """ Express the interest, call verifyData for the fetched Data packet and call onVerified if verify succeeds. If verify fails, call onError(EncryptError.ErrorCode.Validation, "verifyData failed"). If the interest times out, re-express nRetrials times. If the interest times out nRetrials times, or for a network Nack, call onError(EncryptError.ErrorCode.DataRetrievalFailure, interest.getName().toUri()). :param Interest interest: The Interest to express. :param int nRetrials: The number of retrials left after a timeout. :param Link link: The Link object to use in the Interest. This does not make a copy of the Link object. If the Link object's getDelegations().size() is zero, don't use it. :param onVerified: When the fetched Data packet validation succeeds, this calls onVerified(data). :type onVerified: function object :param onError: This calls onError(errorCode, message) for an error, where errorCode is from EncryptError.ErrorCode and message is a str. :type onError: function object """ # Prepare the callback functions. def onData(contentInterest, contentData): # The Interest has no selectors, so assume the library correctly # matched with the Data name before calling onData. try: self._keyChain.verifyData( contentData, onVerified, lambda d, reason: Consumer._callOnError( onError, EncryptError.ErrorCode.Validation, "verifyData failed. Reason: " + reason)) except Exception as ex: try: onError(EncryptError.ErrorCode.General, "verifyData error: " + repr(ex)) except: logging.exception("Error in onError") def onNetworkNack(interest, networkNack): # We have run out of options. Report a retrieval failure. try: onError(EncryptError.ErrorCode.DataRetrievalFailure, interest.getName().toUri()) except: logging.exception("Error in onError") def onTimeout(interest): if nRetrials > 0: self._sendInterest(interest, nRetrials - 1, link, onVerified, onError) else: onNetworkNack(interest, NetworkNack()) if link.getDelegations().size() == 0: # We can use the supplied interest without copying. request = interest else: # Copy the supplied interest and add the Link. request = Interest(interest) # This will use a cached encoding if available. request.setLinkWireEncoding(link.wireEncode()) try: self._face.expressInterest(request, onData, onTimeout, onNetworkNack) except Exception as ex: try: onError(EncryptError.ErrorCode.General, "expressInterest error: " + repr(ex)) except: logging.exception("Error in onError") def _getDecryptionKey(self, decryptionKeyName): """ Get the encoded blob of the decryption key with decryptionKeyName from the database. :param Name decryptionKeyName: The key name. :return: A Blob with the encoded key, or an isNull Blob if cannot find the key with keyName. :rtype: Blob :raises ConsumerDb.Error: For a database error. """ return self._database.getKey(decryptionKeyName) @staticmethod def _callOnError(onError, errorCode, message): """ Call onError(errorCode, message) within a try block to log exceptions that it throws. We name this separate helper function to use inside lambda expressions. """ try: onError(errorCode, message) except: logging.exception("Error in onError") NO_LINK = Link()
class Node(object): """ Create a new Node for communication with an NDN hub with the given Transport object and connectionInfo. :param Transport transport: An object of a subclass of Transport used for communication. :param Transport.ConnectionInfo connectionInfo: An object of a subclass of Transport.ConnectionInfo to be used to connect to the transport. """ def __init__(self, transport, connectionInfo): self._transport = transport self._connectionInfo = connectionInfo # An array of PendintInterest self._pendingInterestTable = [] # An array of RegisteredPrefix self._registeredPrefixTable = [] self._ndndIdFetcherInterest = Interest( Name("/%C1.M.S.localhost/%C1.M.SRV/ndnd/KEY")) self._ndndIdFetcherInterest.setInterestLifetimeMilliseconds(4000.0) self._ndndId = None self._commandInterestGenerator = CommandInterestGenerator() self._timeoutPrefix = Name("/local/timeout") def expressInterest(self, interest, onData, onTimeout, wireFormat): """ Send the Interest through the transport, read the entire response and call onData(interest, data). :param Interest interest: The Interest which is NOT copied for this internal Node method. The Face expressInterest is reponsible for making a copy for Node to use. :param onData: A function object to call when a matching data packet is received. :type onData: function object :param onTimeout: A function object to call if the interest times out. If onTimeout is None, this does not use it. :type onTimeout: function object :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat """ # TODO: Properly check if we are already connected to the expected host. if not self._transport.getIsConnected(): self._transport.connect(self._connectionInfo, self) pendingInterestId = Node._PendingInterest.getNextPendingInterestId() self._pendingInterestTable.append( Node._PendingInterest(pendingInterestId, interest, onData, onTimeout)) # Special case: For _timeoutPrefix we don't actually send the interest. if not self._timeoutPrefix.match(interest.getName()): self._transport.send(interest.wireEncode(wireFormat).toBuffer()) return pendingInterestId def removePendingInterest(self, pendingInterestId): """ Remove the pending interest entry with the pendingInterestId from the pending interest table. This does not affect another pending interest with a different pendingInterestId, even if it has the same interest name. If there is no entry with the pendingInterestId, do nothing. :param int pendingInterestId: The ID returned from expressInterest. """ # Go backwards through the list so we can erase entries. # Remove all entries even though pendingInterestId should be unique. i = len(self._pendingInterestTable) - 1 while i >= 0: if (self._pendingInterestTable[i].getPendingInterestId() == pendingInterestId): self._pendingInterestTable.pop(i) i -= 1 def makeCommandInterest(self, interest, keyChain, certificateName, wireFormat): """ Append a timestamp component and a random value component to interest's name. Then use the keyChain and certificateName to sign the interest. If the interest lifetime is not set, this sets it. :param Interest interest: The interest whose name is append with components. :param KeyChain keyChain: The KeyChain for calling sign. :param Name certificateName: The certificate name of the key to use for signing. :param wireFormat: A WireFormat object used to encode the SignatureInfo and to encode the interest name for signing. :type wireFormat: A subclass of WireFormat """ self._commandInterestGenerator.generate( interest, keyChain, certificateName, wireFormat) def registerPrefix( self, prefix, onInterest, onRegisterFailed, flags, wireFormat, commandKeyChain, commandCertificateName): """ Register prefix with the connected NDN hub and call onInterest when a matching interest is received. :param Name prefix: The Name for the prefix to register which is NOT copied for this internal Node method. The Face registerPrefix is reponsible for making a copy for Node to use.. :param onInterest: A function object to call when a matching interest is received. :type onInterest: function object :param onRegisterFailed: A function object to call if failed to retrieve the connected hub's ID or failed to register the prefix. :type onRegisterFailed: function object :param ForwardingFlags flags: The flags for finer control of which interests are forwardedto the application. :param wireFormat: A WireFormat object used to encode the message. :type wireFormat: a subclass of WireFormat :param KeyChain commandKeyChain: The KeyChain object for signing interests. If null, assume we are connected to a legacy NDNx forwarder. :param Name commandCertificateName: The certificate name for signing interests. """ # Get the registeredPrefixId now so we can return it to the caller. registeredPrefixId = Node._RegisteredPrefix.getNextRegisteredPrefixId() # If we have an _ndndId, we know we already connected to NDNx. if self._ndndId != None or commandKeyChain == None: # Assume we are connected to a legacy NDNx server. if self._ndndId == None: # First fetch the ndndId of the connected hub. fetcher = Node._NdndIdFetcher( self, registeredPrefixId, prefix, onInterest, onRegisterFailed, flags, wireFormat) # We send the interest using the given wire format so that the hub # receives (and sends) in the application's desired wire format. self.expressInterest( self._ndndIdFetcherInterest, fetcher.onData, fetcher.onTimeout, wireFormat) else: self._registerPrefixHelper( registeredPrefixId, Name(prefix), onInterest, onRegisterFailed, flags, wireFormat) else: # The application set the KeyChain for signing NFD interests. self._nfdRegisterPrefix( registeredPrefixId, Name(prefix), onInterest, onRegisterFailed, flags, commandKeyChain, commandCertificateName) return registeredPrefixId def removeRegisteredPrefix(self, registeredPrefixId): """ Remove the registered prefix entry with the registeredPrefixId from the registered prefix table. This does not affect another registered prefix with a different registeredPrefixId, even if it has the same prefix name. If there is no entry with the registeredPrefixId, do nothing. :param int registeredPrefixId: The ID returned from registerPrefix. """ # Go backwards through the list so we can erase entries. # Remove all entries even though registeredPrefixId should be unique. i = len(self._registeredPrefixTable) - 1 while i >= 0: if (self._registeredPrefixTable[i].getRegisteredPrefixId() == registeredPrefixId): self._registeredPrefixTable.pop(i) i -= 1 def processEvents(self): """ Process any packets to receive and call callbacks such as onData, onInterest or onTimeout. This returns immediately if there is no data to receive. This blocks while calling the callbacks. You should repeatedly call this from an event loop, with calls to sleep as needed so that the loop doesn't use 100% of the CPU. Since processEvents modifies the pending interest table, your application should make sure that it calls processEvents in the same thread as expressInterest (which also modifies the pending interest table). :raises: This may raise an exception for reading data or in the callback for processing the data. If you call this from an main event loop, you may want to catch and log/disregard all exceptions. """ self._transport.processEvents() # Check for PIT entry timeouts. Go backwards through the list so we can # erase entries. nowMilliseconds = Common.getNowMilliseconds() i = len(self._pendingInterestTable) - 1 while i >= 0: if self._pendingInterestTable[i].isTimedOut(nowMilliseconds): # Save the PendingInterest and remove it from the PIT. Then # call the callback. pendingInterest = self._pendingInterestTable[i] self._pendingInterestTable.pop(i) pendingInterest.callTimeout() # Refresh now since the timeout callback might have delayed. nowMilliseconds = Common.getNowMilliseconds() i -= 1 def getTransport(self): """ Get the transport object given to the constructor. :return: The transport object. :rtype: Transport """ return self._transport def getConnectionInfo(self): """ Get the connectionInfo object given to the constructor. :return: The connectionInfo object. :rtype: Transport.ConnectionInfo """ return self._connectionInfo def onReceivedElement(self, element): """ This is called by the transport's ElementReader to process an entire received Data or Interest element. :param element: The bytes of the incoming element. :type element: An array type with int elements """ # The type codes for TLV Interest and Data packets are chosen to not # conflict with the first byte of a binary XML packet, so we canjust # look at the first byte. if not (element[0] == Tlv.Interest or element[0] == Tlv.Data): # Ignore non-TLV elements. return # First, decode as Interest or Data. interest = None data = None decoder = TlvDecoder(element) if decoder.peekType(Tlv.Interest, len(element)): interest = Interest() interest.wireDecode(element, TlvWireFormat.get()) elif decoder.peekType(Tlv.Data, len(element)): data = Data() data.wireDecode(element, TlvWireFormat.get()) # Now process as Interest or Data. if interest != None: entry = self._getEntryForRegisteredPrefix(interest.getName()) if entry != None: entry.getOnInterest()( entry.getPrefix(), interest, self._transport, entry.getRegisteredPrefixId()) elif data != None: pendingInterests = self._extractEntriesForExpressedInterest( data.getName()) for pendingInterest in pendingInterests: pendingInterest.getOnData()(pendingInterest.getInterest(), data) def shutdown(self): """ Call getTransport().close(). """ self._transport.close() def _extractEntriesForExpressedInterest(self, name): """ Find all entries from the _pendingInterestTable where the name conforms to the entry's interest selectors, remove the entries from the table and return them. :param Name name: The name to find the interest for (from the incoming data packet). :return: The matching entries from the _pendingInterestTable, or [] if none are found. :rtype: array of _PendingInterest """ result = [] # Go backwards through the list so we can erase entries. i = len(self._pendingInterestTable) - 1 while i >= 0: if self._pendingInterestTable[i].getInterest().matchesName(name): result.append(self._pendingInterestTable[i]) self._pendingInterestTable.pop(i) i -= 1 return result def _getEntryForRegisteredPrefix(self, name): """ Find the first entry from the _registeredPrefixTable where the entry prefix is the longest that matches name. :param Name name: The name to find the RegisteredPrefix for (from the incoming interest packet). :return: The registered prefix entry, or None of not found. :rtype: _RegisteredPrefix """ iResult = -1 for i in range(len(self._registeredPrefixTable)): if self._registeredPrefixTable[i].getPrefix().match(name): if (iResult < 0 or self._registeredPrefixTable[i].getPrefix().size() > self._registeredPrefixTable[iResult].getPrefix().size()): # Update to the longer match. iResult = i if iResult >= 0: return self._registeredPrefixTable[iResult] else: return None def _registerPrefixHelper( self, registeredPrefixId, prefix, onInterest, onRegisterFailed, flags, wireFormat): """ Do the work of registerPrefix to register with NDNx once we have an _ndndId. :param int registeredPrefixId: The _RegisteredPrefix.getNextRegisteredPrefixId() which registerPrefix got so it could return it to the caller. If this is 0, then don't add to _registeredPrefixTable (assuming it has already been done). """ # Create a ForwardingEntry. # Note: ndnd ignores any freshness that is larger than 3600 seconds and # sets 300 seconds instead. To register "forever", (=2000000000 sec), # the freshness period must be omitted. forwardingEntry = ForwardingEntry() forwardingEntry.setAction("selfreg") forwardingEntry.setPrefix(prefix) forwardingEntry.setForwardingFlags(flags) content = forwardingEntry.wireEncode(wireFormat) # Set the ForwardingEntry as the content of a Data packet and sign. data = Data() data.setContent(content) # Set the name to a random value so that each request is unique. nonce = bytearray(4) for i in range(len(nonce)): nonce[i] = _systemRandom.randint(0, 0xff) data.getName().append(nonce) # The ndnd ignores the signature, so set to blank values. data.getSignature().getKeyLocator().setType( KeyLocatorType.KEY_LOCATOR_DIGEST) data.getSignature().getKeyLocator().setKeyData( Blob(bytearray(32), False)) data.getSignature().setSignature(Blob(bytearray(128), False)) encodedData = data.wireEncode(wireFormat) # Create an interest where the name has the encoded Data packet. interestName = Name().append("ndnx").append(self._ndndId).append( "selfreg").append(encodedData) interest = Interest(interestName) interest.setInterestLifetimeMilliseconds(4000.0) interest.setScope(1) encodedInterest = interest.wireEncode(wireFormat) if registeredPrefixId != 0: # Save the onInterest callback and send the registration interest. self._registeredPrefixTable.append(Node._RegisteredPrefix( registeredPrefixId, prefix, onInterest)) response = Node._RegisterResponse( self, prefix, onInterest, onRegisterFailed, flags, wireFormat, False) self.expressInterest( interest, response.onData, response.onTimeout, wireFormat) def _nfdRegisterPrefix( self, registeredPrefixId, prefix, onInterest, onRegisterFailed, flags, commandKeyChain, commandCertificateName): """ Do the work of registerPrefix to register with NFD. :param int registeredPrefixId: The _RegisteredPrefix.getNextRegisteredPrefixId() which registerPrefix got so it could return it to the caller. If this is 0, then don't add to _registeredPrefixTable (assuming it has already been done). """ if commandKeyChain == None: raise RuntimeError( "registerPrefix: The command KeyChain has not been set. You must call setCommandSigningInfo.") if commandCertificateName.size() == 0: raise RuntimeError( "registerPrefix: The command certificate name has not been set. You must call setCommandSigningInfo.") controlParameters = ControlParameters() controlParameters.setName(prefix) commandInterest = Interest(Name("/localhost/nfd/rib/register")) # NFD only accepts TlvWireFormat packets. commandInterest.getName().append(controlParameters.wireEncode(TlvWireFormat.get())) self.makeCommandInterest( commandInterest, commandKeyChain, commandCertificateName, TlvWireFormat.get()) # The interest is answered by the local host, so set a short timeout. commandInterest.setInterestLifetimeMilliseconds(2000.0) if registeredPrefixId != 0: # Save the onInterest callback and send the registration interest. self._registeredPrefixTable.append(Node._RegisteredPrefix( registeredPrefixId, prefix, onInterest)) response = Node._RegisterResponse( self, prefix, onInterest, onRegisterFailed, flags, TlvWireFormat.get(), True) self.expressInterest( commandInterest, response.onData, response.onTimeout, TlvWireFormat.get()) class _PendingInterest(object): """ _PendingInterest is a private class for the members of the _pendingInterestTable. Create a new PendingInterest and set the _timeoutTime based on the current time and the interest lifetime. :param int pendingInterestId: A unique ID for this entry, which you should get with getNextPendingInteresId(). :param Interest interest: The interest. :param onData: A function object to call when a matching data packet is received. :type onData: function object :param onTimeout: A function object to call if the interest times out. If onTimeout is None, this does not use it. :type onTimeout: function object """ def __init__(self, pendingInterestId, interest, onData, onTimeout): self._pendingInterestId = pendingInterestId self._interest = interest self._onData = onData self._onTimeout = onTimeout # Set up _timeoutTimeMilliseconds. if (self._interest.getInterestLifetimeMilliseconds() != None and self._interest.getInterestLifetimeMilliseconds() >= 0.0): self._timeoutTimeMilliseconds = (Common.getNowMilliseconds() + self._interest.getInterestLifetimeMilliseconds()) else: # No timeout. self._timeoutTimeMilliseconds = None _lastPendingInterestId = 0 @staticmethod def getNextPendingInterestId(): """ Get the next unique pending interest ID. :return: The next pending interest ID. :rtype: int """ Node._PendingInterest._lastPendingInterestId += 1 return Node._PendingInterest._lastPendingInterestId def getPendingInterestId(self): """ Get the pendingInterestId given to the constructor. :return: The pending interest ID. :rtype: int """ return self._pendingInterestId def getInterest(self): """ Get the interest given to the constructor. :return: The interest. :rtype: int """ return self._interest def getOnData(self): """ Get the onData function object given to the constructor. :return: The onData function object. :rtype: function object """ return self._onData def isTimedOut(self, nowMilliseconds): """ Check if this interest is timed out. :param float nowMilliseconds: The current time in milliseconds from Common.getNowMilliseconds(). :return: True if this interest timed out, otherwise False. :rtype: bool """ return (self._timeoutTimeMilliseconds != None and nowMilliseconds >= self._timeoutTimeMilliseconds) def callTimeout(self): """ Call _onTimeout (if defined). This ignores exceptions from _onTimeout. """ if self._onTimeout: # Ignore all exceptions. try: self._onTimeout(self._interest) except: pass class _RegisteredPrefix(object): """ _RegisteredPrefix is a private class for the members of the _registeredPrefixTable. Create a new RegisteredPrefix with the given values. :param int registeredPrefixId: A unique ID for this entry, which you should get with getNextRegisteredPrefixId(). :param Name prefix: The name prefix. :param onInterest: A function object to call when a matching data packet is received. :type onInterest: function object """ def __init__(self, registeredPrefixId, prefix, onInterest): self._registeredPrefixId = registeredPrefixId self._prefix = prefix self._onInterest = onInterest _lastRegisteredPrefixId = 0 @staticmethod def getNextRegisteredPrefixId(): """ Get the next unique registered prefix ID. :return: The next registered prefix ID. :rtype: int """ Node._RegisteredPrefix._lastRegisteredPrefixId += 1 return Node._RegisteredPrefix._lastRegisteredPrefixId def getRegisteredPrefixId(self): """ Get the registeredPrefixId given to the constructor. :return: The registered prefix ID. :rtype: int """ return self._registeredPrefixId def getPrefix(self): """ Get the name prefix to the constructor. :return: The name prefix. :rtype: Name """ return self._prefix def getOnInterest(self): """ Get the onInterest function object given to the constructor. :return: The onInterest function object. :rtype: function object """ return self._onInterest class _NdndIdFetcher(object): """ An _NdndIdFetcher receives the Data packet with the publisher public key digest for the connected NDN hub. """ def __init__(self, node, registeredPrefixId, prefix, onInterest, onRegisterFailed, flags, wireFormat): self._node = node self._registeredPrefixId = registeredPrefixId self._prefix = prefix self._onInterest = onInterest self._onRegisterFailed = onRegisterFailed self._flags = flags self._wireFormat = wireFormat def onData(self, interest, ndndIdData): """ We received the ndnd ID. """ # Assume that the content is a DER encoded public key of the ndnd. # Do a quick check that the first byte is for DER encoding. if (ndndIdData.getContent().size() < 1 or ndndIdData.getContent().buf()[0] != 0x30): logging.getLogger(__name__).info( "Register prefix failed: The content returned when fetching the NDNx ID does not appear to be a public key") self._onRegisterFailed(self._prefix) return # Get the digest of the public key. digest = bytearray( hashlib.sha256(ndndIdData.getContent().toBuffer()).digest()) # Set the _ndndId and continue. # TODO: If there are multiple connected hubs, the NDN ID is really # stored per connected hub. self._node._ndndId = Blob(digest, False) self._node._registerPrefixHelper( self._registeredPrefixId, self._prefix, self._onInterest, self._onRegisterFailed, self._flags, self._wireFormat) def onTimeout(self, interest): """ We timed out fetching the ndnd ID. """ logging.getLogger(__name__).info( "Register prefix failed: Timeout fetching the NDNx ID") self._onRegisterFailed(self._prefix) class _RegisterResponse(object): """ A _RegisterResponse receives the response Data packet from the register prefix interest sent to the connected NDN hub. If this gets a bad response or a timeout, call onRegisterFailed. """ def __init__(self, node, prefix, onInterest, onRegisterFailed, flags, wireFormat, isNfdCommand): self._node = node self._prefix = prefix self._onInterest = onInterest self._onRegisterFailed = onRegisterFailed self._flags = flags self._wireFormat = wireFormat self._isNfdCommand = isNfdCommand def onData(self, interest, responseData): """ We received the response. Do a quick check of expected name components. """ if self._isNfdCommand: # Decode responseData->getContent() and check for a success code. # TODO: Move this into the TLV code. statusCode = None try: decoder = TlvDecoder(responseData.getContent().buf()) decoder.readNestedTlvsStart(Tlv.NfdCommand_ControlResponse) statusCode = decoder.readNonNegativeIntegerTlv(Tlv.NfdCommand_StatusCode) except ValueError as ex: logging.getLogger(__name__).info( "Register prefix failed: Error decoding the NFD response: %s", str(ex)) self._onRegisterFailed(self._prefix) return # Status code 200 is "OK". if statusCode != 200: logging.getLogger(__name__).info( "Register prefix failed: Expected NFD status code 200, got: %d", statusCode) self._onRegisterFailed(self._prefix) logging.getLogger(__name__).info( "Register prefix succeeded with the NFD forwarder for prefix %s", self._prefix.toUri()) else: expectedName = Name("/ndnx/.../selfreg") if (responseData.getName().size() < 4 or responseData.getName()[0] != expectedName[0] or responseData.getName()[2] != expectedName[2]): logging.getLogger(__name__).info( "Register prefix failed: Unexpected name in NDNx response: %s", responseData.getName().toUri()) self._onRegisterFailed(self._prefix) return logging.getLogger(__name__).info( "Register prefix succeeded with the NDNx forwarder for prefix %s", self._prefix.toUri()) def onTimeout(self, interest): """ We timed out waiting for the response. """ if self._isNfdCommand: logging.getLogger(__name__).info( "Timeout for NFD register prefix command. Attempting an NDNx command...") # The application set the commandKeyChain, but we may be # connected to NDNx. if self._node._ndndId == None: # First fetch the ndndId of the connected hub. # Pass 0 for registeredPrefixId since the entry was already added to # _registeredPrefixTable on the first try. fetcher = Node._NdndIdFetcher( self._node, 0, self._prefix, self._onInterest, self._onRegisterFailed, self._flags, self._wireFormat) # We send the interest using the given wire format so that the hub # receives (and sends) in the application's desired wire format. self._node.expressInterest( self._node._ndndIdFetcherInterest, fetcher.onData, fetcher.onTimeout, self._wireFormat) else: # Pass 0 for registeredPrefixId since the entry was already # added to _registeredPrefixTable on the first try. self._node._registerPrefixHelper( 0, self._prefix, self._onInterest, self._onRegisterFailed, self._flags, self._wireFormat) else: # An NDNx command was sent because there is no commandKeyChain, # so we can't try an NFD command. Or it was sent from this # callback after trying an NFD command. Fail. logging.getLogger(__name__).info( "Register prefix failed: Timeout waiting for the response from the register prefix interest") self._onRegisterFailed(self._prefix)