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