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