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()
+