blob: 7c2e1731eacd1c7092d0f5181af8148a404a194d [file] [log] [blame]
# -*- 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.policy import ConfigPolicyManager
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 end device and controller.
"""
def __init__(self,configFileName):
"""
Initialize the network and security classes for the node
"""
super(BaseNode, self).__init__()
self._identityStorage = BasicIdentityStorage()
self._identityManager = IdentityManager(self._identityStorage)
self._policyManager = ConfigPolicyManager(configFileName)
# hopefully there is some private/public key pair available
self._keyChain = KeyChain(self._identityManager,self._policyManager)
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 = '/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)))
self._instanceSerial = prefix.encode('hex')
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)-2s (%(levelname)-2s):\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 getKeyChain(self):
return self._keyChain
def getDefaultIdentity(self):
try:
defaultIdentity = self._identityManager.getDefaultIdentity()
except SecurityException:
defaultIdentity = ""
return defaultIdentity
def getDefaultCertificateName(self):
#exception - no certficate, return ''
try:
certName = self._identityStorage.getDefaultCertificateNameForIdentity(
self._identityManager.getDefaultIdentity())
except SecurityException:
certName = ""
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._keyChain.setFace(self.face)
self._isStopped = False
self.face.stopWhen(lambda:self._isStopped)
self.beforeLoopStart()
self.face.setCommandSigningInfo(self._keyChain, self.getDefaultCertificateName())
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()