# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
#
# Copyright (C) 2014-2016 Regents of the University of California.
# Author: Jeff Thompson <jefft0@remap.ucla.edu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# A copy of the GNU Lesser General Public License is in the file COPYING.
"""
This module defines the Face class which provides the main methods for NDN
communication.
"""
import os
import collections
from pyndn.name import Name
from pyndn.interest import Interest
from pyndn.forwarding_flags import ForwardingFlags
from pyndn.interest_filter import InterestFilter
from pyndn.encoding.wire_format import WireFormat
from pyndn.transport.tcp_transport import TcpTransport
from pyndn.transport.unix_transport import UnixTransport
from pyndn.util.blob import Blob
from pyndn.util.common import Common
from pyndn.node import Node
[docs]class Face(object):
"""
Create a new Face for communication with an NDN hub. This constructor
has the forms Face(), Face(transport, connectionInfo) or
Face(host, port). If the default Face() constructor is used, if the
forwarder's Unix socket file exists then connect using UnixTransport,
otherwise connect to "localhost" on port 6363 using TcpTransport.
: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.
:param str host: In the Face(host, port) form of the constructor, host is
the host of the NDN hub to connect using TcpTransport.
:param int port: (optional) In the Face(host, port) form of the constructor,
port is the port of the NDN hub. If omitted. use 6363.
"""
def __init__(self, arg1 = None, arg2 = None):
if arg1 == None or Common.typeIsString(arg1):
filePath = ""
if arg1 == None and arg2 == None:
# Check if we can connect using UnixSocket.
filePath = self._getUnixSocketFilePathForLocalhost()
if filePath == "":
transport = TcpTransport()
host = arg1 if arg1 != None else "localhost"
connectionInfo = TcpTransport.ConnectionInfo(
host, arg2 if type(arg2) is int else 6363)
else:
transport = UnixTransport()
connectionInfo = UnixTransport.ConnectionInfo(filePath)
else:
transport = arg1
connectionInfo = arg2
self._node = Node(transport, connectionInfo)
self._commandKeyChain = None
self._commandCertificateName = Name()
[docs] def expressInterest(
self, interestOrName, arg2, arg3 = None, arg4 = None, arg5 = None,
arg6 = None):
"""
Send the Interest through the transport, read the entire response and
call onData, onTimeout or onNetworkNack as described below.
There are two forms of expressInterest.
The first form takes the exact interest (including lifetime):
expressInterest(interest, onData [, onTimeout] [, onNetworkNack] [, wireFormat]).
The second form creates the interest from a name and optional
interest template:
expressInterest(name [, interestTemplate], onData [, onTimeout]
[, onNetworkNack] [, wireFormat]).
:param Interest interest: The Interest (if the first form is used). This
copies the Interest.
:param Name name: A name for the Interest (if the second form is used).
:param Interest interestTemplate: (optional) if not None, copy interest
selectors from the template (if the second form is used). If omitted,
use a default interest lifetime.
: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. NOTE: You must
not change the interest object - if you need to change it then make a
copy.
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 onData: function object
:param onTimeout: (optional) If the interest times out according to the
interest lifetime, this calls onTimeout(interest) where interest is
the interest given to expressInterest. However, if onTimeout is None
or omitted, this does not use it.
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 onTimeout: function object
:param onNetworkNack: (optional) 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. If onNetworkNack is supplied, then onTimeout must be
supplied too. However, if a network Nack is received and onNetworkNack
is null, do nothing and wait for the interest to time out. (Therefore,
an application which does not yet process a network Nack reason treats
a Nack the same as a timeout.)
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 onNetworkNack: function object
:param wireFormat: (optional) A WireFormat object used to encode the
message. If omitted, use WireFormat.getDefaultWireFormat().
:type wireFormat: A subclass of WireFormat
:return: The pending interest ID which can be used with
removePendingInterest.
:rtype: int
:throws: RuntimeError If the encoded interest size exceeds
Face.getMaxNdnPacketSize().
"""
args = self._getExpressInterestArgs(
interestOrName, arg2, arg3, arg4, arg5, arg6)
self._node.expressInterest(
args['pendingInterestId'], args['interestCopy'], args['onData'],
args['onTimeout'], args['onNetworkNack'], args['wireFormat'], self)
return args['pendingInterestId']
def _getExpressInterestArgs(self, interestOrName, arg2, arg3, arg4, arg5, arg6):
"""
This is a protected helper method to resolve the different overloaded
forms of Face.expressInterest and return the arguments to pass to
Node.expressInterest. This is necessary to prepare arguments such as
interestCopy before dispatching to Node.expressInterest.
:return: A dictionary with the following keys: 'pendingInterestId',
'interestCopy', 'onData', 'onTimeout', 'onNetworkNack' and 'wireFormat'.
:rtype: dict
"""
if type(interestOrName) is Interest:
# Node.expressInterest requires a copy of the interest.
interestCopy = Interest(interestOrName)
else:
# The first argument is a name. Make the interest from the name and
# possible template.
if type(arg2) is Interest:
template = arg2
# Copy the template.
interestCopy = Interest(template)
interestCopy.setName(interestOrName)
# Shift the remaining args to be processed below.
arg2 = arg3
arg3 = arg4
arg4 = arg5
arg5 = arg6
else:
# No template.
interestCopy = Interest(interestOrName)
# Set a default interest lifetime.
interestCopy.setInterestLifetimeMilliseconds(4000.0)
onData = arg2
# arg3, arg4, arg5 may be:
# OnTimeout, OnNetworkNack, WireFormat
# OnTimeout, OnNetworkNack, None
# OnTimeout, WireFormat, None
# OnTimeout, None, None
# WireFormat, None, None
# None, None, None
if isinstance(arg3, collections.Callable):
onTimeout = arg3
else:
onTimeout = None
if isinstance(arg4, collections.Callable):
onNetworkNack = arg4
else:
onNetworkNack = None
if isinstance(arg3, WireFormat):
wireFormat = arg3
elif isinstance(arg4, WireFormat):
wireFormat = arg4
elif isinstance(arg5, WireFormat):
wireFormat = arg5
else:
wireFormat = WireFormat.getDefaultWireFormat()
return { 'pendingInterestId': self._node.getNextEntryId(),
'interestCopy': interestCopy, 'onData': onData, 'onTimeout': onTimeout,
'onNetworkNack': onNetworkNack, 'wireFormat': wireFormat }
[docs] 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._node.removePendingInterest(pendingInterestId)
[docs] def setCommandSigningInfo(self, keyChain, certificateName):
"""
Set the KeyChain and certificate name used to sign command interests
(e.g. for registerPrefix).
:param KeyChain keyChain: The KeyChain object for signing interests,
which must remain valid for the life of this Face. You must create the
KeyChain object and pass it in. You can create a default KeyChain for
your system with the default KeyChain constructor.
:param Name certificateName: The certificate name for signing interests.
This makes a copy of the Name. You can get the default certificate
name with keyChain.getDefaultCertificateName() .
"""
self._commandKeyChain = keyChain
self._commandCertificateName = Name(certificateName)
[docs] def setCommandCertificateName(self, certificateName):
"""
Set the certificate name used to sign command interest (e.g. for
registerPrefix), using the KeyChain that was set with
setCommandSigningInfo.
:param Name certificateName: The certificate name for signing interest.
This makes a copy of the Name.
"""
self._commandCertificateName = Name(certificateName)
[docs] def makeCommandInterest(self, interest, wireFormat = None):
"""
Append a timestamp component and a random value component to interest's
name. Then use the keyChain and certificateName from
setCommandSigningInfo to sign the interest. If the interest lifetime is
not set, this sets it.
:note: This method is an experimental feature. See the API docs for more
detail at
http://named-data.net/doc/ndn-ccl-api/face.html#face-makecommandinterest-method .
:param Interest interest: The interest whose name is appended with
components.
:param wireFormat: (optional) A WireFormat object used to encode the
SignatureInfo and to encode the interest name for signing. If omitted, use
WireFormat.getDefaultWireFormat().
:type wireFormat: A subclass of WireFormat
"""
if wireFormat == None:
# Don't use a default argument since getDefaultWireFormat can change.
wireFormat = WireFormat.getDefaultWireFormat()
self._node.makeCommandInterest(
interest, self._commandKeyChain, self._commandCertificateName,
wireFormat)
[docs] def registerPrefix(
self, prefix, onInterest, onRegisterFailed, onRegisterSuccess = None,
flags = None, wireFormat = None):
"""
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 Name prefix: The Name for the prefix to register. This copies the
Name.
:param onInterest: If not None, this creates an interest filter from
prefix 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.
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 onInterest: function object
:param onRegisterFailed: If register prefix fails for any reason, this
calls onRegisterFailed(prefix).
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 onRegisterFailed: function object
:param onRegisterSuccess: (optional) This calls
onRegisterSuccess(prefix, registeredPrefixId) when this receives a
success message from the forwarder. If onRegisterSuccess is None or
omitted, this does not use it. (The onRegisterSuccess parameter comes
after onRegisterFailed because it can be None or omitted, unlike
onRegisterFailed.)
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 onRegisterSuccess: function object
:param ForwardingFlags flags: (optional) The flags for finer control of
which interests are forwardedto the application.
:param wireFormat: (optional) A WireFormat object used to encode this
ControlParameters. If omitted, use WireFormat.getDefaultWireFormat().
:type wireFormat: A subclass of WireFormat
:raises: This raises an exception if setCommandSigningInfo has not been
called to set the KeyChain, etc. for signing the command interest.
"""
registeredPrefixId = self._node.getNextEntryId()
# Node.registerPrefix requires a copy of the prefix.
self._registerPrefixHelper(
registeredPrefixId, Name(prefix), onInterest, onRegisterFailed,
onRegisterSuccess, flags, wireFormat)
return registeredPrefixId
def _registerPrefixHelper(
self, registeredPrefixId, prefixCopy, onInterest, onRegisterFailed,
arg5 = None, arg6 = None, arg7 = None):
"""
This is a protected helper method to do the work of registerPrefix to
resolve the different overloaded forms. The registeredPrefixId is from
getNextEntryId(). This has no return value and can be used in a callback.
"""
# arg5, arg6, arg7 may be:
# OnRegisterSuccess, ForwardingFlags, WireFormat
# OnRegisterSuccess, ForwardingFlags, None
# OnRegisterSuccess, WireFormat, None
# OnRegisterSuccess, None, None
# ForwardingFlags, WireFormat, None
# ForwardingFlags, None, None
# WireFormat, None, None
# None, None, None
if isinstance(arg5, collections.Callable):
onRegisterSuccess = arg5
else:
onRegisterSuccess = None
if isinstance(arg5, ForwardingFlags):
flags = arg5
elif isinstance(arg6, ForwardingFlags):
flags = arg6
else:
flags = ForwardingFlags()
if isinstance(arg5, WireFormat):
wireFormat = arg5
elif isinstance(arg6, WireFormat):
wireFormat = arg6
elif isinstance(arg7, WireFormat):
wireFormat = arg7
else:
# Don't use a default argument since getDefaultWireFormat can change.
wireFormat = WireFormat.getDefaultWireFormat()
return self._node.registerPrefix(
registeredPrefixId, prefixCopy, onInterest, onRegisterFailed,
onRegisterSuccess, flags, wireFormat, self._commandKeyChain,
self._commandCertificateName, self)
[docs] 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.
"""
self._node.removeRegisteredPrefix(registeredPrefixId)
[docs] def setInterestFilter(self, filterOrPrefix, onInterest):
"""
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. There are two forms of setInterestFilter.
The first form uses the exact given InterestFilter:
setInterestFilter(filter, onInterest).
The second form creates an InterestFilter from the given prefix Name:
setInterestFilter(prefix, onInterest).
:param InterestFilter filter: The InterestFilter with a prefix and
optional regex filter used to match the name of an incoming Interest.
This makes a copy of filter.
:param Name prefix: The Name prefix used to match the name of an
incoming Interest. This makes a copy of the Name.
:param onInterest: When an Interest is received which matches the filter,
this calls onInterest(prefix, interest, face, interestFilterId, filter).
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 onInterest: function object
:return: The interest filter ID which can be used with unsetInterestFilter.
:rtype: int
"""
interestFilterId = self._node.getNextEntryId()
# If filterOrPrefix is already an InterestFilter, the InterestFilter
# constructor will make a copy as required by Node.setInterestFilter.
filterCopy = InterestFilter(filterOrPrefix)
self._node.setInterestFilter(
interestFilterId, filterCopy, onInterest, self)
return interestFilterId
[docs] 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._node.unsetInterestFilter(interestFilterId)
[docs] def putData(self, data, wireFormat = None):
"""
The OnInterest callback calls this to put a Data packet which satisfies
an Interest.
:param Data data: The Data packet which satisfies the interest.
:param WireFormat wireFormat: (optional) A WireFormat object used to
encode the Data packet. If omitted, use
WireFormat.getDefaultWireFormat().
:throws: RuntimeError If the encoded Data packet size exceeds
getMaxNdnPacketSize().
"""
if wireFormat == None:
# Don't use a default argument since getDefaultWireFormat can change.
wireFormat = WireFormat.getDefaultWireFormat()
# We get the encoding now before calling send because it may dispatch to
# asyncio to be called later, and the caller may modify data before then.
encoding = data.wireEncode(wireFormat)
if encoding.size() > self.getMaxNdnPacketSize():
raise RuntimeError(
"The encoded Data packet size exceeds the maximum limit getMaxNdnPacketSize()")
self.send(encoding)
[docs] def send(self, encoding):
"""
Send the encoded packet out through the face.
:param encoding: The blob or array with the the encoded packet to send.
:type encoding: Blob or an array type with int elements
:throws: RuntimeError If the packet size exceeds getMaxNdnPacketSize().
"""
# If encoding is a Blob, get its buf().
encodingBuffer = encoding.buf() if isinstance(encoding, Blob) else encoding
self._node.send(encodingBuffer)
[docs] 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.
"""
# Just call Node's processEvents.
self._node.processEvents()
[docs] 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._node.isLocal()
[docs] def shutdown(self):
"""
Shut down and disconnect this Face.
"""
self._node.shutdown()
@staticmethod
[docs] 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
[docs] def callLater(self, delayMilliseconds, callback):
"""
Call callback() after the given delay. Even though this is public, it is
not part of the public API of Face.This default implementation just
calls Node.callLater, but a subclass can override.
:param float delayMilliseconds: The delay in milliseconds.
:param callback: This calls callback() after the delay.
:type callback: function object
"""
self._node.callLater(delayMilliseconds, callback)
@staticmethod
def _getUnixSocketFilePathForLocalhost():
"""
If the forwarder's Unix socket file path exists, then return the file
path. Otherwise return an empty string.
:return: The Unix socket file path to use, or an empty string.
:rtype: str
"""
filePath = "/var/run/nfd.sock"
# Use listdir because isfile doesn't see socket file types.
if (os.path.basename(filePath) in
os.listdir(os.path.dirname(filePath))):
return filePath
else:
filePath = "/tmp/.ndnd.sock"
if (os.path.basename(filePath) in
os.listdir(os.path.dirname(filePath))):
return filePath
else:
return ""