blob: 03c7555be3c1b37cbdd41edb7e7f2ed3d1e7d5b6 [file] [log] [blame]
philoL5ddbce12015-04-20 23:35:34 -07001
2# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
3#
4# Copyright (C) 2014 Regents of the University of California.
5# Author: Adeola Bannis <thecodemaiden@gmail.com>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19# A copy of the GNU General Public License is in the file COPYING.
20import logging
21import time
22import sys
23import random
24
25from pyndn import Name, Face, Interest, Data, ThreadsafeFace
26from pyndn.security import KeyChain
27from pyndn.security.identity import IdentityManager, BasicIdentityStorage
28from pyndn.security.security_exception import SecurityException
29
30
31from collections import namedtuple
32
33try:
34 import asyncio
35except ImportError:
36 import trollius as asyncio
37
38Command = namedtuple('Command', ['suffix', 'function', 'keywords', 'isSigned'])
39
40class BaseNode(object):
41 """
42 This class contains methods/attributes common to both node and controller.
43
44 """
45 def __init__(self):
46 """
47 Initialize the network and security classes for the node
48 """
49 super(BaseNode, self).__init__()
50
51
52 self._identityStorage = BasicIdentityStorage()
53 self._identityManager = IdentityManager(self._identityStorage)
54
55
56 # hopefully there is some private/public key pair available
57 self._keyChain = KeyChain(self._identityManager)
58
59 self._registrationFailures = 0
60 self._prepareLogging()
61
62 self._setupComplete = False
63 self._instanceSerial = None
64
65 # waiting devices register this prefix and respond to discovery
66 # or configuration interest
67 self._bootstrapPrefix = Name('/home/controller/bootstrap')
68
69 def getSerial(self):
70 """
71 Since you may wish to run two nodes on a Raspberry Pi, each
72 node will generate a unique serial number each time it starts up.
73 """
74 if self._instanceSerial is None:
75 prefixLen = 4
76 prefix = ''
77 for i in range(prefixLen):
78 prefix += (chr(random.randint(0,0xff)))
79 suffix = self.getDeviceSerial().lstrip('0')
80 self._instanceSerial = '-'.join([prefix.encode('hex'), suffix])
81 return self._instanceSerial
82
83##
84# Logging
85##
86 def _prepareLogging(self):
87 self.log = logging.getLogger(str(self.__class__))
88 self.log.setLevel(logging.DEBUG)
89 logFormat = "%(asctime)-15s %(name)-20s %(funcName)-20s (%(levelname)-8s):\n\t%(message)s"
90 self._console = logging.StreamHandler()
91 self._console.setFormatter(logging.Formatter(logFormat))
92 self._console.setLevel(logging.INFO)
93 # without this, a lot of ThreadsafeFace errors get swallowed up
94 logging.getLogger("trollius").addHandler(self._console)
95 self.log.addHandler(self._console)
96
97 def setLogLevel(self, level):
98 """
99 Set the log level that will be output to standard error
100 :param level: A log level constant defined in the logging module (e.g. logging.INFO)
101 """
102 self._console.setLevel(level)
103
104 def getLogger(self):
105 """
106 :return: The logger associated with this node
107 :rtype: logging.Logger
108 """
109 return self.log
110
111###
112# Startup and shutdown
113###
114 def beforeLoopStart(self):
115 """
116 Called before the event loop starts.
117 """
118 pass
119
120 def getDefaultCertificateName(self):
121 try:
122 certName = self._identityStorage.getDefaultCertificateNameForIdentity(
123 self._identityManager.getDefaultIdentity())
124 except SecurityException:
125 certName = self._keyChain.getDefaultCertificateName()
126
127 return certName
128
129 def start(self):
130 """
131 Begins the event loop. After this, the node's Face is set up and it can
132 send/receive interests+data
133 """
134 self.log.info("Starting up")
135 self.loop = asyncio.get_event_loop()
136 self.face = ThreadsafeFace(self.loop, '')
137 self.face.setCommandSigningInfo(self._keyChain, self.getDefaultCertificateName())
138 self._keyChain.setFace(self.face)
139
140 self._isStopped = False
141 self.face.stopWhen(lambda:self._isStopped)
142 self.beforeLoopStart()
143
144 try:
145 self.loop.run_forever()
146 except KeyboardInterrupt:
147 pass
148 except Exception as e:
149 self.log.exception(e, exc_info=True)
150 finally:
151 self._isStopped = True
152
153 def stop(self):
154 """
155 Stops the node, taking it off the network
156 """
157 self.log.info("Shutting down")
158 self._isStopped = True
159
160###
161# Data handling
162###
163 def signData(self, data):
164 """
165 Sign the data with our network certificate
166 :param pyndn.Data data: The data to sign
167 """
168 self._keyChain.sign(data, self.getDefaultCertificateName())
169
170 def sendData(self, data, transport, sign=True):
171 """
172 Reply to an interest with a data packet, optionally signing it.
173 :param pyndn.Data data: The response data packet
174 :param pyndn.Transport transport: The transport to send the data through. This is
175 obtained from an incoming interest handler
176 :param boolean sign: (optional, default=True) Whether the response must be signed.
177 """
178 if sign:
179 self.signData(data)
180 transport.send(data.wireEncode().buf())
181
182###
183#
184#
185##
186 def onRegisterFailed(self, prefix):
187 """
188 Called when the node cannot register its name with the forwarder
189 :param pyndn.Name prefix: The network name that failed registration
190 """
191 if self._registrationFailures < 5:
192 self._registrationFailures += 1
193 self.log.warn("Could not register {}, retry: {}/{}".format(prefix.toUri(), self._registrationFailures, 5))
194 self.face.registerPrefix(self.prefix, self._onCommandReceived, self.onRegisterFailed)
195 else:
196 self.log.critical("Could not register device prefix, ABORTING")
197 self._isStopped = True
198
199 def verificationFailed(self, dataOrInterest):
200 """
201 Called when verification of a data packet or command interest fails.
202 :param pyndn.Data or pyndn.Interest: The packet that could not be verified
203 """
204 self.log.info("Received invalid" + dataOrInterest.getName().toUri())
205
206 @staticmethod
207 def getDeviceSerial():
208 """
209 Find and return the serial number of the Raspberry Pi. Provided in case
210 you wish to distinguish data from nodes with the same name by serial.
211 :return: The serial number extracted from device information in /proc/cpuinfo
212 :rtype: str
213 """
214 with open('/proc/cpuinfo') as f:
215 for line in f:
216 if line.startswith('Serial'):
217 return line.split(':')[1].strip()
218