blob: ec4b4690d81f217b526e307cf7b4ffb4c37169c9 [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
Teng Lianga0b49372015-05-15 05:30:27 -070026from pyndn.security.policy import ConfigPolicyManager
philoL5ddbce12015-04-20 23:35:34 -070027from pyndn.security import KeyChain
28from pyndn.security.identity import IdentityManager, BasicIdentityStorage
29from pyndn.security.security_exception import SecurityException
30
31
32from collections import namedtuple
33
34try:
35 import asyncio
36except ImportError:
37 import trollius as asyncio
38
Teng Liang4662b372015-05-27 15:48:36 -070039#Command = namedtuple('Command', ['suffix', 'function', 'keywords', 'isSigned'])
philoL5ddbce12015-04-20 23:35:34 -070040
41class BaseNode(object):
42 """
Teng Liang4662b372015-05-27 15:48:36 -070043 This class contains methods/attributes common to both end device and controller.
philoL5ddbce12015-04-20 23:35:34 -070044
45 """
Teng Lianga0b49372015-05-15 05:30:27 -070046 def __init__(self,configFileName):
philoL5ddbce12015-04-20 23:35:34 -070047 """
48 Initialize the network and security classes for the node
49 """
50 super(BaseNode, self).__init__()
51
Teng Lianga0b49372015-05-15 05:30:27 -070052
philoL5ddbce12015-04-20 23:35:34 -070053 self._identityStorage = BasicIdentityStorage()
54 self._identityManager = IdentityManager(self._identityStorage)
Teng Lianga0b49372015-05-15 05:30:27 -070055 self._policyManager = ConfigPolicyManager(configFileName)
philoL5ddbce12015-04-20 23:35:34 -070056
57 # hopefully there is some private/public key pair available
Teng Lianga0b49372015-05-15 05:30:27 -070058 self._keyChain = KeyChain(self._identityManager,self._policyManager)
philoL5ddbce12015-04-20 23:35:34 -070059
60 self._registrationFailures = 0
61 self._prepareLogging()
62
63 self._setupComplete = False
64 self._instanceSerial = None
65
66 # waiting devices register this prefix and respond to discovery
67 # or configuration interest
Teng Liang50429402015-05-22 16:01:17 -070068 self._bootstrapPrefix = '/home/controller/bootstrap'
philoL5ddbce12015-04-20 23:35:34 -070069
70 def getSerial(self):
71 """
72 Since you may wish to run two nodes on a Raspberry Pi, each
73 node will generate a unique serial number each time it starts up.
74 """
75 if self._instanceSerial is None:
76 prefixLen = 4
77 prefix = ''
78 for i in range(prefixLen):
79 prefix += (chr(random.randint(0,0xff)))
Teng Liang4662b372015-05-27 15:48:36 -070080
81 self._instanceSerial = prefix.encode('hex')
philoL5ddbce12015-04-20 23:35:34 -070082 return self._instanceSerial
83
84##
85# Logging
86##
87 def _prepareLogging(self):
88 self.log = logging.getLogger(str(self.__class__))
89 self.log.setLevel(logging.DEBUG)
Teng Liang4662b372015-05-27 15:48:36 -070090 logFormat = "%(asctime)-15s %(name)-20s %(funcName)-2s (%(levelname)-2s):\t%(message)s"
philoL5ddbce12015-04-20 23:35:34 -070091 self._console = logging.StreamHandler()
92 self._console.setFormatter(logging.Formatter(logFormat))
93 self._console.setLevel(logging.INFO)
94 # without this, a lot of ThreadsafeFace errors get swallowed up
95 logging.getLogger("trollius").addHandler(self._console)
96 self.log.addHandler(self._console)
97
98 def setLogLevel(self, level):
99 """
100 Set the log level that will be output to standard error
101 :param level: A log level constant defined in the logging module (e.g. logging.INFO)
102 """
103 self._console.setLevel(level)
104
105 def getLogger(self):
106 """
107 :return: The logger associated with this node
108 :rtype: logging.Logger
109 """
110 return self.log
111
112###
113# Startup and shutdown
114###
115 def beforeLoopStart(self):
116 """
117 Called before the event loop starts.
118 """
119 pass
philo5d4724e2014-11-10 19:34:05 +0000120
121 def getKeyChain(self):
122 return self._keyChain
philoL5ddbce12015-04-20 23:35:34 -0700123
Teng Liang4662b372015-05-27 15:48:36 -0700124 def getDefaultIdentity(self):
125 try:
126 defaultIdentity = self._identityManager.getDefaultIdentity()
127 except SecurityException:
128 defaultIdentity = ""
129
130 return defaultIdentity
131
132
133 def getDefaultCertificateName(self):
134 #exception - no certficate, return ''
135
136 try:
137 certName = self._identityStorage.getDefaultCertificateNameForIdentity(
138 self._identityManager.getDefaultIdentity())
139 except SecurityException:
140 certName = ""
141
142 return certName
philoL5ddbce12015-04-20 23:35:34 -0700143
144 def start(self):
145 """
146 Begins the event loop. After this, the node's Face is set up and it can
147 send/receive interests+data
148 """
149 self.log.info("Starting up")
150 self.loop = asyncio.get_event_loop()
151 self.face = ThreadsafeFace(self.loop, '')
Teng Liang4662b372015-05-27 15:48:36 -0700152
philoL5ddbce12015-04-20 23:35:34 -0700153 self._keyChain.setFace(self.face)
154
155 self._isStopped = False
156 self.face.stopWhen(lambda:self._isStopped)
philoL5ddbce12015-04-20 23:35:34 -0700157
Teng Liang4662b372015-05-27 15:48:36 -0700158 self.beforeLoopStart()
159
160 self.face.setCommandSigningInfo(self._keyChain, self.getDefaultCertificateName())
161
philoL5ddbce12015-04-20 23:35:34 -0700162 try:
163 self.loop.run_forever()
164 except KeyboardInterrupt:
165 pass
166 except Exception as e:
167 self.log.exception(e, exc_info=True)
168 finally:
169 self._isStopped = True
170
171 def stop(self):
172 """
173 Stops the node, taking it off the network
174 """
175 self.log.info("Shutting down")
176 self._isStopped = True
177
178###
179# Data handling
180###
181 def signData(self, data):
182 """
183 Sign the data with our network certificate
184 :param pyndn.Data data: The data to sign
185 """
186 self._keyChain.sign(data, self.getDefaultCertificateName())
187
188 def sendData(self, data, transport, sign=True):
189 """
190 Reply to an interest with a data packet, optionally signing it.
191 :param pyndn.Data data: The response data packet
192 :param pyndn.Transport transport: The transport to send the data through. This is
193 obtained from an incoming interest handler
194 :param boolean sign: (optional, default=True) Whether the response must be signed.
195 """
196 if sign:
197 self.signData(data)
198 transport.send(data.wireEncode().buf())
199
200###
201#
202#
203##
204 def onRegisterFailed(self, prefix):
205 """
206 Called when the node cannot register its name with the forwarder
207 :param pyndn.Name prefix: The network name that failed registration
208 """
209 if self._registrationFailures < 5:
210 self._registrationFailures += 1
211 self.log.warn("Could not register {}, retry: {}/{}".format(prefix.toUri(), self._registrationFailures, 5))
212 self.face.registerPrefix(self.prefix, self._onCommandReceived, self.onRegisterFailed)
213 else:
214 self.log.critical("Could not register device prefix, ABORTING")
215 self._isStopped = True
216
217 def verificationFailed(self, dataOrInterest):
218 """
219 Called when verification of a data packet or command interest fails.
220 :param pyndn.Data or pyndn.Interest: The packet that could not be verified
221 """
222 self.log.info("Received invalid" + dataOrInterest.getName().toUri())
223
224 @staticmethod
225 def getDeviceSerial():
226 """
227 Find and return the serial number of the Raspberry Pi. Provided in case
228 you wish to distinguish data from nodes with the same name by serial.
229 :return: The serial number extracted from device information in /proc/cpuinfo
230 :rtype: str
231 """
232 with open('/proc/cpuinfo') as f:
233 for line in f:
234 if line.startswith('Serial'):
235 return line.split(':')[1].strip()
236
philo5eec5e32014-11-08 08:57:52 +0000237