new file: base_node.py
diff --git a/base_node.py b/base_node.py
new file mode 100644
index 0000000..03c7555
--- /dev/null
+++ b/base_node.py
@@ -0,0 +1,218 @@
+
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2014 Regents of the University of California.
+# Author: Adeola Bannis <thecodemaiden@gmail.com>
+#
+# 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# A copy of the GNU General Public License is in the file COPYING.
+import logging
+import time
+import sys
+import random
+
+from pyndn import Name, Face, Interest, Data, ThreadsafeFace
+from pyndn.security import KeyChain
+from pyndn.security.identity import IdentityManager, BasicIdentityStorage
+from pyndn.security.security_exception import SecurityException
+
+
+from collections import namedtuple
+
+try:
+ import asyncio
+except ImportError:
+ import trollius as asyncio
+
+Command = namedtuple('Command', ['suffix', 'function', 'keywords', 'isSigned'])
+
+class BaseNode(object):
+ """
+ This class contains methods/attributes common to both node and controller.
+
+ """
+ def __init__(self):
+ """
+ Initialize the network and security classes for the node
+ """
+ super(BaseNode, self).__init__()
+
+
+ self._identityStorage = BasicIdentityStorage()
+ self._identityManager = IdentityManager(self._identityStorage)
+
+
+ # hopefully there is some private/public key pair available
+ self._keyChain = KeyChain(self._identityManager)
+
+ self._registrationFailures = 0
+ self._prepareLogging()
+
+ self._setupComplete = False
+ self._instanceSerial = None
+
+ # waiting devices register this prefix and respond to discovery
+ # or configuration interest
+ self._bootstrapPrefix = Name('/home/controller/bootstrap')
+
+ def getSerial(self):
+ """
+ Since you may wish to run two nodes on a Raspberry Pi, each
+ node will generate a unique serial number each time it starts up.
+ """
+ if self._instanceSerial is None:
+ prefixLen = 4
+ prefix = ''
+ for i in range(prefixLen):
+ prefix += (chr(random.randint(0,0xff)))
+ suffix = self.getDeviceSerial().lstrip('0')
+ self._instanceSerial = '-'.join([prefix.encode('hex'), suffix])
+ return self._instanceSerial
+
+##
+# Logging
+##
+ def _prepareLogging(self):
+ self.log = logging.getLogger(str(self.__class__))
+ self.log.setLevel(logging.DEBUG)
+ logFormat = "%(asctime)-15s %(name)-20s %(funcName)-20s (%(levelname)-8s):\n\t%(message)s"
+ self._console = logging.StreamHandler()
+ self._console.setFormatter(logging.Formatter(logFormat))
+ self._console.setLevel(logging.INFO)
+ # without this, a lot of ThreadsafeFace errors get swallowed up
+ logging.getLogger("trollius").addHandler(self._console)
+ self.log.addHandler(self._console)
+
+ def setLogLevel(self, level):
+ """
+ Set the log level that will be output to standard error
+ :param level: A log level constant defined in the logging module (e.g. logging.INFO)
+ """
+ self._console.setLevel(level)
+
+ def getLogger(self):
+ """
+ :return: The logger associated with this node
+ :rtype: logging.Logger
+ """
+ return self.log
+
+###
+# Startup and shutdown
+###
+ def beforeLoopStart(self):
+ """
+ Called before the event loop starts.
+ """
+ pass
+
+ def getDefaultCertificateName(self):
+ try:
+ certName = self._identityStorage.getDefaultCertificateNameForIdentity(
+ self._identityManager.getDefaultIdentity())
+ except SecurityException:
+ certName = self._keyChain.getDefaultCertificateName()
+
+ return certName
+
+ def start(self):
+ """
+ Begins the event loop. After this, the node's Face is set up and it can
+ send/receive interests+data
+ """
+ self.log.info("Starting up")
+ self.loop = asyncio.get_event_loop()
+ self.face = ThreadsafeFace(self.loop, '')
+ self.face.setCommandSigningInfo(self._keyChain, self.getDefaultCertificateName())
+ self._keyChain.setFace(self.face)
+
+ self._isStopped = False
+ self.face.stopWhen(lambda:self._isStopped)
+ self.beforeLoopStart()
+
+ try:
+ self.loop.run_forever()
+ except KeyboardInterrupt:
+ pass
+ except Exception as e:
+ self.log.exception(e, exc_info=True)
+ finally:
+ self._isStopped = True
+
+ def stop(self):
+ """
+ Stops the node, taking it off the network
+ """
+ self.log.info("Shutting down")
+ self._isStopped = True
+
+###
+# Data handling
+###
+ def signData(self, data):
+ """
+ Sign the data with our network certificate
+ :param pyndn.Data data: The data to sign
+ """
+ self._keyChain.sign(data, self.getDefaultCertificateName())
+
+ def sendData(self, data, transport, sign=True):
+ """
+ Reply to an interest with a data packet, optionally signing it.
+ :param pyndn.Data data: The response data packet
+ :param pyndn.Transport transport: The transport to send the data through. This is
+ obtained from an incoming interest handler
+ :param boolean sign: (optional, default=True) Whether the response must be signed.
+ """
+ if sign:
+ self.signData(data)
+ transport.send(data.wireEncode().buf())
+
+###
+#
+#
+##
+ def onRegisterFailed(self, prefix):
+ """
+ Called when the node cannot register its name with the forwarder
+ :param pyndn.Name prefix: The network name that failed registration
+ """
+ if self._registrationFailures < 5:
+ self._registrationFailures += 1
+ self.log.warn("Could not register {}, retry: {}/{}".format(prefix.toUri(), self._registrationFailures, 5))
+ self.face.registerPrefix(self.prefix, self._onCommandReceived, self.onRegisterFailed)
+ else:
+ self.log.critical("Could not register device prefix, ABORTING")
+ self._isStopped = True
+
+ def verificationFailed(self, dataOrInterest):
+ """
+ Called when verification of a data packet or command interest fails.
+ :param pyndn.Data or pyndn.Interest: The packet that could not be verified
+ """
+ self.log.info("Received invalid" + dataOrInterest.getName().toUri())
+
+ @staticmethod
+ def getDeviceSerial():
+ """
+ Find and return the serial number of the Raspberry Pi. Provided in case
+ you wish to distinguish data from nodes with the same name by serial.
+ :return: The serial number extracted from device information in /proc/cpuinfo
+ :rtype: str
+ """
+ with open('/proc/cpuinfo') as f:
+ for line in f:
+ if line.startswith('Serial'):
+ return line.split(':')[1].strip()
+