blob: 76fb8543fa016c9375d08281136fdaa57de324ac [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
philo5d4724e2014-11-10 19:34:05 +0000119
120 def getKeyChain(self):
121 return self._keyChain
philoL5ddbce12015-04-20 23:35:34 -0700122
123 def getDefaultCertificateName(self):
124 try:
125 certName = self._identityStorage.getDefaultCertificateNameForIdentity(
126 self._identityManager.getDefaultIdentity())
127 except SecurityException:
128 certName = self._keyChain.getDefaultCertificateName()
129
130 return certName
131
132 def start(self):
133 """
134 Begins the event loop. After this, the node's Face is set up and it can
135 send/receive interests+data
136 """
137 self.log.info("Starting up")
138 self.loop = asyncio.get_event_loop()
139 self.face = ThreadsafeFace(self.loop, '')
140 self.face.setCommandSigningInfo(self._keyChain, self.getDefaultCertificateName())
141 self._keyChain.setFace(self.face)
142
143 self._isStopped = False
144 self.face.stopWhen(lambda:self._isStopped)
145 self.beforeLoopStart()
146
147 try:
148 self.loop.run_forever()
149 except KeyboardInterrupt:
150 pass
151 except Exception as e:
152 self.log.exception(e, exc_info=True)
153 finally:
154 self._isStopped = True
155
156 def stop(self):
157 """
158 Stops the node, taking it off the network
159 """
160 self.log.info("Shutting down")
161 self._isStopped = True
162
163###
164# Data handling
165###
166 def signData(self, data):
167 """
168 Sign the data with our network certificate
169 :param pyndn.Data data: The data to sign
170 """
171 self._keyChain.sign(data, self.getDefaultCertificateName())
172
173 def sendData(self, data, transport, sign=True):
174 """
175 Reply to an interest with a data packet, optionally signing it.
176 :param pyndn.Data data: The response data packet
177 :param pyndn.Transport transport: The transport to send the data through. This is
178 obtained from an incoming interest handler
179 :param boolean sign: (optional, default=True) Whether the response must be signed.
180 """
181 if sign:
182 self.signData(data)
183 transport.send(data.wireEncode().buf())
184
185###
186#
187#
188##
189 def onRegisterFailed(self, prefix):
190 """
191 Called when the node cannot register its name with the forwarder
192 :param pyndn.Name prefix: The network name that failed registration
193 """
194 if self._registrationFailures < 5:
195 self._registrationFailures += 1
196 self.log.warn("Could not register {}, retry: {}/{}".format(prefix.toUri(), self._registrationFailures, 5))
197 self.face.registerPrefix(self.prefix, self._onCommandReceived, self.onRegisterFailed)
198 else:
199 self.log.critical("Could not register device prefix, ABORTING")
200 self._isStopped = True
201
202 def verificationFailed(self, dataOrInterest):
203 """
204 Called when verification of a data packet or command interest fails.
205 :param pyndn.Data or pyndn.Interest: The packet that could not be verified
206 """
207 self.log.info("Received invalid" + dataOrInterest.getName().toUri())
208
209 @staticmethod
210 def getDeviceSerial():
211 """
212 Find and return the serial number of the Raspberry Pi. Provided in case
213 you wish to distinguish data from nodes with the same name by serial.
214 :return: The serial number extracted from device information in /proc/cpuinfo
215 :rtype: str
216 """
217 with open('/proc/cpuinfo') as f:
218 for line in f:
219 if line.startswith('Serial'):
220 return line.split(':')[1].strip()
221
philo5eec5e32014-11-08 08:57:52 +0000222