예제 #1
0
    def __init__(self, networkName, nodeName):
        super(IotConsole, self).__init__()

        self.deviceSuffix = Name(nodeName)
        self.networkPrefix = Name(networkName)
        self.prefix = Name(self.networkPrefix).append(self.deviceSuffix)

        self._identityStorage = IotIdentityStorage()
        self._policyManager = IotPolicyManager(self._identityStorage)
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._policyManager.setEnvironmentPrefix(self.networkPrefix)
        self._policyManager.setTrustRootIdentity(self.prefix)
        self._policyManager.setDeviceIdentity(self.prefix)
        self._policyManager.updateTrustRules()

        self.foundCommands = {}
        self.unconfiguredDevices = []

        # TODO: use xDialog in XWindows
        self.ui = Dialog(backtitle='NDN IoT User Console', height=18, width=78)

        trolliusLogger = logging.getLogger('trollius')
        trolliusLogger.addHandler(logging.StreamHandler())
예제 #2
0
    def __init__(self, networkName, nodeName):
        super(IotConsole, self).__init__()

        self.deviceSuffix = Name(nodeName)
        self.networkPrefix = Name(networkName)
        self.prefix = Name(self.networkPrefix).append(self.deviceSuffix)

        self._identityStorage = IotIdentityStorage()
        self._policyManager = IotPolicyManager(self._identityStorage)
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._policyManager.setEnvironmentPrefix(self.networkPrefix)
        self._policyManager.setTrustRootIdentity(self.prefix)
        self._policyManager.setDeviceIdentity(self.prefix)
        self._policyManager.updateTrustRules()

        self.foundCommands = {}
        self.unconfiguredDevices = []

        # TODO: use xDialog in XWindows
        self.ui = Dialog(backtitle="NDN IoT User Console", height=18, width=78)

        trolliusLogger = logging.getLogger("trollius")
        trolliusLogger.addHandler(logging.StreamHandler())
예제 #3
0
    def __init__(self):
        """
        Initialize the network and security classes for the node
        """
        super(BaseNode, self).__init__()

        self._identityStorage = IotIdentityStorage()
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._policyManager = IotPolicyManager(self._identityStorage)

        # hopefully there is some private/public key pair available
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._registrationFailures = 0
        self._prepareLogging()

        self._setupComplete = False
        self._instanceSerial = None

        # waiting devices register this prefix and respond to discovery
        # or configuration interest
        self._hubPrefix = Name('/localhop/configure')
예제 #4
0
    def __init__(self, transport = None, conn = None):
        """
        Initialize the network and security classes for the node
        """
        super(BaseNode, self).__init__()
        self.faceTransport = transport
        self.faceConn = conn
        
        self._identityStorage = IotIdentityStorage()
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._policyManager = IotPolicyManager(self._identityStorage)

        # hopefully there is some private/public key pair available
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._registrationFailures = 0
        self._prepareLogging()

        self._setupComplete = False
예제 #5
0
    def __init__(self):
        """
        Initialize the network and security classes for the node
        """
        super(BaseNode, self).__init__()

        self._identityStorage = IotIdentityStorage()
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._policyManager = IotPolicyManager(self._identityStorage)

        # hopefully there is some private/public key pair available
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._registrationFailures = 0
        self._prepareLogging()

        self._setupComplete = False
        self._instanceSerial = None

        # waiting devices register this prefix and respond to discovery
        # or configuration interest
        self._hubPrefix = Name('/localhop/configure')
예제 #6
0
class BaseNode(object):
    """
    This class contains methods/attributes common to both node and controller.
    
    """
    def __init__(self):
        """
        Initialize the network and security classes for the node
        """
        super(BaseNode, self).__init__()

        self._identityStorage = IotIdentityStorage()
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._policyManager = IotPolicyManager(self._identityStorage)

        # hopefully there is some private/public key pair available
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._registrationFailures = 0
        self._prepareLogging()

        self._setupComplete = False
        self._instanceSerial = None

        # waiting devices register this prefix and respond to discovery
        # or configuration interest
        self._hubPrefix = Name('/localhop/configure')

    def getSerial(self):
        """
         Since you may wish to run two nodes on a Raspberry Pi, each
         node will generate a unique serial number each time it starts up.
        """
        if self._instanceSerial is None:
            prefixLen = 4
            prefix = ''
            for i in range(prefixLen):
                prefix += (chr(random.randint(0,0xff)))
            suffix = self.getDeviceSerial().lstrip('0')
            self._instanceSerial = '-'.join([prefix.encode('hex'), suffix])
        return self._instanceSerial

##
# Logging
##
    def _prepareLogging(self):
        self.log = logging.getLogger(str(self.__class__))
        self.log.setLevel(logging.DEBUG)
        logFormat = "%(asctime)-15s %(name)-20s %(funcName)-20s (%(levelname)-8s):\n\t%(message)s"
        self._console = logging.StreamHandler()
        self._console.setFormatter(logging.Formatter(logFormat))
        self._console.setLevel(logging.INFO)
        # without this, a lot of ThreadsafeFace errors get swallowed up
        logging.getLogger("trollius").addHandler(self._console)
        self.log.addHandler(self._console)

    def setLogLevel(self, level):
        """
        Set the log level that will be output to standard error
        :param level: A log level constant defined in the logging module (e.g. logging.INFO) 
        """
        self._console.setLevel(level)

    def getLogger(self):
        """
        :return: The logger associated with this node
        :rtype: logging.Logger
        """
        return self.log

###
# Startup and shutdown
###
    def beforeLoopStart(self):
        """
        Called before the event loop starts.
        """
        pass

    def getDefaultCertificateName(self):
        try:
            certName = self._identityStorage.getDefaultCertificateNameForIdentity( 
                self._policyManager.getDeviceIdentity())
        except SecurityException:
            certName = self._keyChain.getDefaultCertificateName()

        return certName

    def start(self):
        """
        Begins the event loop. After this, the node's Face is set up and it can
        send/receive interests+data
        """
        self.log.info("Starting up")
        self.loop = asyncio.get_event_loop()
        self.face = ThreadsafeFace(self.loop, '')
        self.face.setCommandSigningInfo(self._keyChain, self.getDefaultCertificateName())
        self._keyChain.setFace(self.face)

        self._isStopped = False
        self.face.stopWhen(lambda:self._isStopped)
        self.beforeLoopStart()
        
        try:
            self.loop.run_forever()
        except KeyboardInterrupt:
            pass
        except Exception as e:
            self.log.exception(e, exc_info=True)
        finally:
            self._isStopped = True

    def stop(self):
        """
        Stops the node, taking it off the network
        """
        self.log.info("Shutting down")
        self._isStopped = True 
        
###
# Data handling
###
    def signData(self, data):
        """
        Sign the data with our network certificate
        :param pyndn.Data data: The data to sign
        """
        self._keyChain.sign(data, self.getDefaultCertificateName())

    def sendData(self, data, transport, sign=True):
        """
        Reply to an interest with a data packet, optionally signing it.
        :param pyndn.Data data: The response data packet
        :param pyndn.Transport transport: The transport to send the data through. This is 
            obtained from an incoming interest handler
        :param boolean sign: (optional, default=True) Whether the response must be signed. 
        """
        if sign:
            self.signData(data)
        transport.send(data.wireEncode().buf())

###
# 
# 
##
    def onRegisterFailed(self, prefix):
        """
        Called when the node cannot register its name with the forwarder
        :param pyndn.Name prefix: The network name that failed registration
        """
        if self._registrationFailures < 5:
            self._registrationFailures += 1
            self.log.warn("Could not register {}, retry: {}/{}".format(prefix.toUri(), self._registrationFailures, 5)) 
            self.face.registerPrefix(self.prefix, self._onCommandReceived, self.onRegisterFailed)
        else:
            self.log.critical("Could not register device prefix, ABORTING")
            self._isStopped = True

    def verificationFailed(self, dataOrInterest):
        """
        Called when verification of a data packet or command interest fails.
        :param pyndn.Data or pyndn.Interest: The packet that could not be verified
        """
        self.log.info("Received invalid" + dataOrInterest.getName().toUri())

    @staticmethod
    def getDeviceSerial():
        """
        Find and return the serial number of the Raspberry Pi. Provided in case
        you wish to distinguish data from nodes with the same name by serial.
        :return: The serial number extracted from device information in /proc/cpuinfo
        :rtype: str
        """
        with open('/proc/cpuinfo') as f:
            for line in f:
                if line.startswith('Serial'):
                    return line.split(':')[1].strip()
예제 #7
0
class IotConsole(object):
    """
    This uses the controller's credentials to provide a management interface
    to the user.
    It does not go through the security handshake (as it should be run on the
    same device as the controller) and so does not inherit from the BaseNode.
    """

    def __init__(self, networkName, nodeName):
        super(IotConsole, self).__init__()

        self.deviceSuffix = Name(nodeName)
        self.networkPrefix = Name(networkName)
        self.prefix = Name(self.networkPrefix).append(self.deviceSuffix)

        self._identityStorage = IotIdentityStorage()
        self._policyManager = IotPolicyManager(self._identityStorage)
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._policyManager.setEnvironmentPrefix(self.networkPrefix)
        self._policyManager.setTrustRootIdentity(self.prefix)
        self._policyManager.setDeviceIdentity(self.prefix)
        self._policyManager.updateTrustRules()

        self.foundCommands = {}
        self.unconfiguredDevices = []

        # TODO: use xDialog in XWindows
        self.ui = Dialog(backtitle="NDN IoT User Console", height=18, width=78)

        trolliusLogger = logging.getLogger("trollius")
        trolliusLogger.addHandler(logging.StreamHandler())

    def start(self):
        """
        Start up the UI
        """
        self.loop = asyncio.get_event_loop()
        self.face = ThreadsafeFace(self.loop, "")

        controllerCertificateName = self._identityStorage.getDefaultCertificateNameForIdentity(self.prefix)
        self.face.setCommandSigningInfo(self._keyChain, controllerCertificateName)
        self._keyChain.setFace(self.face)  # shouldn't be necessarym but doesn't hurt

        self._isStopped = False
        self.face.stopWhen(lambda: self._isStopped)

        self.loop.call_soon(self.displayMenu)
        try:
            self.loop.run_forever()
        except KeyboardInterrupt:
            pass
        except Exception as e:
            print(e)
            # self.log('Exception', e)
        finally:
            self._isStopped = True

    def stop(self):
        self._isStopped = True

    #######
    # GUI
    #######

    def displayMenu(self):

        menuOptions = OrderedDict(
            [
                ("List network services", self.listCommands),
                ("Pair a device", self.pairDevice),
                ("Express interest", self.expressInterest),
                ("Quit", self.stop),
            ]
        )
        (retCode, retStr) = self.ui.mainMenu("Main Menu", menuOptions.keys())

        if retCode == Dialog.DIALOG_ESC or retCode == Dialog.DIALOG_CANCEL:
            # TODO: ask if you're sure you want to quit
            self.stop()
        if retCode == Dialog.DIALOG_OK:
            menuOptions[retStr]()

    #######
    # List all commands
    ######
    def listCommands(self):
        self._requestDeviceList(self._showCommandList, self.displayMenu)

    def _requestDeviceList(self, successCallback, timeoutCallback):
        self.ui.alert("Requesting services list...", False)
        interestName = Name(self.prefix).append("listCommands")
        interest = Interest(interestName)
        interest.setInterestLifetimeMilliseconds(3000)
        # self.face.makeCommandInterest(interest)
        self.face.expressInterest(
            interest,
            self._makeOnCommandListCallback(successCallback),
            self._makeOnCommandListTimeoutCallback(timeoutCallback),
        )

    def _makeOnCommandListTimeoutCallback(self, callback):
        def onCommandListTimeout(interest):
            self.ui.alert("Timed out waiting for services list")
            self.loop.call_soon(callback)

        return onCommandListTimeout

    def _makeOnCommandListCallback(self, callback):
        def onCommandListReceived(interest, data):
            try:
                commandInfo = json.loads(str(data.getContent()))
            except:
                self.ui.alert("An error occured while reading the services list")
                self.loop.call_soon(self.displayMenu)
            else:
                self.foundCommands = commandInfo
                self.loop.call_soon(callback)

        return onCommandListReceived

    def _showCommandList(self):
        try:
            commandList = []
            for capability, commands in self.foundCommands.items():
                commandList.append("\Z1{}:".format(capability))
                for info in commands:
                    signingStr = "signed" if info["signed"] else "unsigned"
                    commandList.append("\Z0\t{} ({})".format(info["name"], signingStr))
                commandList.append("")

            if len(commandList) == 0:
                # should not happen
                commandList = ["----NONE----"]
            allCommands = "\n".join(commandList)
            oldTitle = self.ui.title
            self.ui.title = "Available services"
            self.ui.alert(allCommands, preExtra=["--colors"])
            self.ui.title = oldTitle
            # self.ui.menu('Available services', commandList, prefix='', extras=['--no-cancel'])
        finally:
            self.loop.call_soon(self.displayMenu)

    #######
    # New device
    ######

    def pairDevice(self):
        self._scanForUnconfiguredDevices()

    def _enterPairingInfo(self, serial, pin="", newName=""):
        fields = [Dialog.FormField("PIN", pin), Dialog.FormField("Device name", newName)]
        (retCode, retList) = self.ui.form("Pairing device {}".format(serial), fields)
        if retCode == Dialog.DIALOG_OK:
            pin, newName = retList
            if len(pin) == 0 or len(newName) == 0:
                self.ui.alert("All fields are required")
                self.loop.call_soon(self._enterPairingInfo, serial, pin, newName)
            else:
                try:
                    pinBytes = pin.decode("hex")
                except TypeError:
                    self.ui.alert("Pin is invalid")
                    self.loop.call_soon(self._enterPairingInfo, serial, pin, newName)
                else:
                    self._addDeviceToNetwork(serial, newName, pin.decode("hex"))
        elif retCode == Dialog.DIALOG_CANCEL or retCode == Dialog.DIALOG_ESC:
            self.loop.call_soon(self._showConfigurationList)

    def _showConfigurationList(self):
        foundDevices = self.unconfiguredDevices[:]
        emptyStr = "----NONE----"
        if len(foundDevices) == 0:
            foundDevices.append(emptyStr)
        retCode, retStr = self.ui.menu(
            "Select a device to configure",
            foundDevices,
            preExtras=["--ok-label", "Configure", "--extra-button", "--extra-label", "Refresh"],
        )
        if retCode == Dialog.DIALOG_CANCEL or retCode == Dialog.DIALOG_ESC:
            self.loop.call_soon(self.displayMenu)
        elif retCode == Dialog.DIALOG_EXTRA:
            self.loop.call_soon(self._scanForUnconfiguredDevices)
        elif retCode == Dialog.DIALOG_OK and retStr != emptyStr:
            self.loop.call_soon(self._enterPairingInfo, retStr)
        else:
            self.loop.call_soon(self._showConfigurationList)

    def _scanForUnconfiguredDevices(self):
        # unconfigured devices should register '/localhop/configure'
        # we keep asking for unconfigured devices until we stop getting replies

        foundDevices = []

        self.ui.alert("Scanning for unconfigured devices...", False)

        def onDeviceTimeout(interest):
            # assume we're done - everyone is excluded
            self.unconfiguredDevices = foundDevices
            self.loop.call_soon(self._showConfigurationList)

        def onDeviceResponse(interest, data):
            updatedInterest = Interest(interest)
            deviceSerial = str(data.getContent())
            if len(deviceSerial) > 0:
                foundDevices.append(deviceSerial)
                updatedInterest.getExclude().appendComponent(Name.Component(deviceSerial))
            # else ignore the malformed response
            self.face.expressInterest(updatedInterest, onDeviceResponse, onDeviceTimeout)

        interest = Interest(Name("/localhop/configure"))
        interest.setInterestLifetimeMilliseconds(2000)
        self.face.expressInterest(interest, onDeviceResponse, onDeviceTimeout)

    def _addDeviceToNetwork(self, serial, suffix, pin):
        self.ui.alert("Sending pairing info to gateway...", False)
        # we must encrypt this so no one can see the pin!
        message = DevicePairingInfoMessage()
        message.info.deviceSuffix = suffix
        message.info.deviceSerial = serial
        message.info.devicePin = pin
        rawBytes = ProtobufTlv.encode(message)
        encryptedBytes = self._identityManager.encryptForIdentity(rawBytes, self.prefix)
        encodedBytes = base64.urlsafe_b64encode(str(encryptedBytes))
        interestName = Name(self.prefix).append("addDevice").append(encodedBytes)
        interest = Interest(interestName)
        # todo: have the controller register this console as a listener
        # and update it with pairing status
        interest.setInterestLifetimeMilliseconds(5000)
        self.face.makeCommandInterest(interest)

        self.face.expressInterest(interest, self._onAddDeviceResponse, self._onAddDeviceTimeout)

    def _onAddDeviceResponse(self, interest, data):
        try:
            responseCode = int(str(data.getContent()))
            if responseCode == 202:
                self.ui.alert("Gateway received pairing info")
            else:
                self.ui.alert("Error encountered while sending pairing info")
        except:
            self.ui.alert("Exception encountered while decoding gateway reply")
        finally:
            self.loop.call_soon(self.displayMenu)

    def _onAddDeviceTimeout(self, interest):
        self.ui.alert("Timed out sending pairing info to gateway")
        self.loop.call_soon(self.displayMenu)

    ######
    # Express interest
    #####

    def expressInterest(self):
        if len(self.foundCommands) == 0:
            self._requestDeviceList(self._showInterestMenu, self._showInterestMenu)
        else:
            self.loop.call_soon(self._showInterestMenu)

    def _showInterestMenu(self):
        # display a menu of all available interests, on selection allow user to
        # (1) extend it
        # (2) send it signed/unsigned
        # NOTE: should it add custom ones to the list?
        commandSet = set()
        wildcard = "<Enter Interest Name>"
        try:
            for commands in self.foundCommands.values():
                commandSet.update([c["name"] for c in commands])
            commandList = list(commandSet)
            commandList.append(wildcard)
            (returnCode, returnStr) = self.ui.menu("Choose a command", commandList, prefix=" ")
            if returnCode == Dialog.DIALOG_OK:
                if returnStr == wildcard:
                    returnStr = self.networkPrefix.toUri()
                self.loop.call_soon(self._expressCustomInterest, returnStr)
            else:
                self.loop.call_soon(self.displayMenu)
        except:
            self.loop.call_soon(self.displayMenu)

    def _expressCustomInterest(self, interestName):
        # TODO: make this a form, add timeout field
        try:
            handled = False
            (returnCode, returnStr) = self.ui.prompt(
                "Send interest",
                interestName,
                preExtra=["--extra-button", "--extra-label", "Signed", "--ok-label", "Unsigned"],
            )
            if returnCode == Dialog.DIALOG_ESC or returnCode == Dialog.DIALOG_CANCEL:
                self.loop.call_soon(self.expressInterest)
            else:
                interestName = Name(returnStr)
                doSigned = returnCode == Dialog.DIALOG_EXTRA
                interest = Interest(interestName)
                interest.setInterestLifetimeMilliseconds(5000)
                interest.setChildSelector(1)
                interest.setMustBeFresh(True)
                if doSigned:
                    self.face.makeCommandInterest(interest)
                self.ui.alert("Waiting for response to {}".format(interestName.toUri()), False)
                self.face.expressInterest(interest, self.onDataReceived, self.onInterestTimeout)
        except:
            self.loop.call_soon(self.expressInterest)

    def onInterestTimeout(self, interest):
        try:
            self.ui.alert("Interest timed out:\n{}".format(interest.getName().toUri()))
        except:
            self.ui.alert("Interest timed out")
        finally:
            self.loop.call_soon(self.expressInterest)

    def onDataReceived(self, interest, data):
        try:
            dataString = "{}\n\n".format(data.getName().toUri())
            dataString += "Contents:\n{}".format(repr(data.getContent().toRawStr()))
            self.ui.alert(dataString)
        except:
            self.ui.alert("Exception occured displaying data contents")
        finally:
            self.loop.call_soon(self.expressInterest)
예제 #8
0
class BaseNode(object):
    """
    This class contains methods/attributes common to both node and controller.
    
    """
    def __init__(self):
        """
        Initialize the network and security classes for the node
        """
        super(BaseNode, self).__init__()

        self._identityStorage = IotIdentityStorage()
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._policyManager = IotPolicyManager(self._identityStorage)

        # hopefully there is some private/public key pair available
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._registrationFailures = 0
        self._prepareLogging()

        self._setupComplete = False
        self._instanceSerial = None

        # waiting devices register this prefix and respond to discovery
        # or configuration interest
        self._hubPrefix = Name('/localhop/configure')

    def getSerial(self):
        """
         Since you may wish to run two nodes on a Raspberry Pi, each
         node will generate a unique serial number each time it starts up.
        """
        if self._instanceSerial is None:
            prefixLen = 4
            prefix = ''
            for i in range(prefixLen):
                prefix += (chr(random.randint(0, 0xff)))
            suffix = self.getDeviceSerial().lstrip('0')
            self._instanceSerial = '-'.join([prefix.encode('hex'), suffix])
        return self._instanceSerial

##
# Logging
##

    def _prepareLogging(self):
        self.log = logging.getLogger(str(self.__class__))
        self.log.setLevel(logging.DEBUG)
        logFormat = "%(asctime)-15s %(name)-20s %(funcName)-20s (%(levelname)-8s):\n\t%(message)s"
        self._console = logging.StreamHandler()
        self._console.setFormatter(logging.Formatter(logFormat))
        self._console.setLevel(logging.INFO)
        # without this, a lot of ThreadsafeFace errors get swallowed up
        logging.getLogger("trollius").addHandler(self._console)
        self.log.addHandler(self._console)

    def setLogLevel(self, level):
        """
        Set the log level that will be output to standard error
        :param level: A log level constant defined in the logging module (e.g. logging.INFO) 
        """
        self._console.setLevel(level)

    def getLogger(self):
        """
        :return: The logger associated with this node
        :rtype: logging.Logger
        """
        return self.log

###
# Startup and shutdown
###

    def beforeLoopStart(self):
        """
        Called before the event loop starts.
        """
        pass

    def getDefaultCertificateName(self):
        try:
            certName = self._identityStorage.getDefaultCertificateNameForIdentity(
                self._policyManager.getDeviceIdentity())
        except SecurityException:
            certName = self._keyChain.getDefaultCertificateName()

        return certName

    def start(self):
        """
        Begins the event loop. After this, the node's Face is set up and it can
        send/receive interests+data
        """
        self.log.info("Starting up")
        self.loop = asyncio.get_event_loop()
        self.face = ThreadsafeFace(self.loop, '')
        self.face.setCommandSigningInfo(self._keyChain,
                                        self.getDefaultCertificateName())
        self._keyChain.setFace(self.face)

        self._isStopped = False
        self.face.stopWhen(lambda: self._isStopped)
        self.beforeLoopStart()

        try:
            self.loop.run_forever()
        except KeyboardInterrupt:
            pass
        except Exception as e:
            self.log.exception(e, exc_info=True)
        finally:
            self._isStopped = True

    def stop(self):
        """
        Stops the node, taking it off the network
        """
        self.log.info("Shutting down")
        self._isStopped = True

###
# Data handling
###

    def signData(self, data):
        """
        Sign the data with our network certificate
        :param pyndn.Data data: The data to sign
        """
        self._keyChain.sign(data, self.getDefaultCertificateName())

    def sendData(self, data, transport, sign=True):
        """
        Reply to an interest with a data packet, optionally signing it.
        :param pyndn.Data data: The response data packet
        :param pyndn.Transport transport: The transport to send the data through. This is 
            obtained from an incoming interest handler
        :param boolean sign: (optional, default=True) Whether the response must be signed. 
        """
        if sign:
            self.signData(data)
        transport.send(data.wireEncode().buf())

###
#
#
##

    def onRegisterFailed(self, prefix):
        """
        Called when the node cannot register its name with the forwarder
        :param pyndn.Name prefix: The network name that failed registration
        """
        if self._registrationFailures < 5:
            self._registrationFailures += 1
            self.log.warn("Could not register {}, retry: {}/{}".format(
                prefix.toUri(), self._registrationFailures, 5))
            self.face.registerPrefix(self.prefix, self._onCommandReceived,
                                     self.onRegisterFailed)
        else:
            self.log.critical("Could not register device prefix, ABORTING")
            self._isStopped = True

    def verificationFailed(self, dataOrInterest):
        """
        Called when verification of a data packet or command interest fails.
        :param pyndn.Data or pyndn.Interest: The packet that could not be verified
        """
        self.log.info("Received invalid" + dataOrInterest.getName().toUri())

    @staticmethod
    def getDeviceSerial():
        """
        Find and return the serial number of the Raspberry Pi. Provided in case
        you wish to distinguish data from nodes with the same name by serial.
        :return: The serial number extracted from device information in /proc/cpuinfo
        :rtype: str
        """
        with open('/proc/cpuinfo') as f:
            for line in f:
                if line.startswith('Serial'):
                    return line.split(':')[1].strip()
예제 #9
0
class IotConsole(object):
    """
    This uses the controller's credentials to provide a management interface
    to the user.
    It does not go through the security handshake (as it should be run on the
    same device as the controller) and so does not inherit from the BaseNode.
    """
    def __init__(self, networkName, nodeName):
        super(IotConsole, self).__init__()

        self.deviceSuffix = Name(nodeName)
        self.networkPrefix = Name(networkName)
        self.prefix = Name(self.networkPrefix).append(self.deviceSuffix)

        self._identityStorage = IotIdentityStorage()
        self._policyManager = IotPolicyManager(self._identityStorage)
        self._identityManager = IotIdentityManager(self._identityStorage)
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._policyManager.setEnvironmentPrefix(self.networkPrefix)
        self._policyManager.setTrustRootIdentity(self.prefix)
        self._policyManager.setDeviceIdentity(self.prefix)
        self._policyManager.updateTrustRules()

        self.foundCommands = {}
        self.unconfiguredDevices = []

        # TODO: use xDialog in XWindows
        self.ui = Dialog(backtitle='NDN IoT User Console', height=18, width=78)

        trolliusLogger = logging.getLogger('trollius')
        trolliusLogger.addHandler(logging.StreamHandler())

    def start(self):
        """
        Start up the UI
        """
        self.loop = asyncio.get_event_loop()
        self.face = ThreadsafeFace(self.loop, '')

        controllerCertificateName = self._identityStorage.getDefaultCertificateNameForIdentity(
            self.prefix)
        self.face.setCommandSigningInfo(self._keyChain,
                                        controllerCertificateName)
        self._keyChain.setFace(
            self.face)  # shouldn't be necessarym but doesn't hurt

        self._isStopped = False
        self.face.stopWhen(lambda: self._isStopped)

        self.loop.call_soon(self.displayMenu)
        try:
            self.loop.run_forever()
        except KeyboardInterrupt:
            pass
        except Exception as e:
            print(e)
            #self.log('Exception', e)
        finally:
            self._isStopped = True

    def stop(self):
        self._isStopped = True

#######
# GUI
#######

    def displayMenu(self):

        menuOptions = OrderedDict([('List network services',
                                    self.listCommands),
                                   ('Pair a device', self.pairDevice),
                                   ('Express interest', self.expressInterest),
                                   ('Quit', self.stop)])
        (retCode, retStr) = self.ui.mainMenu('Main Menu', menuOptions.keys())

        if retCode == Dialog.DIALOG_ESC or retCode == Dialog.DIALOG_CANCEL:
            # TODO: ask if you're sure you want to quit
            self.stop()
        if retCode == Dialog.DIALOG_OK:
            menuOptions[retStr]()

#######
# List all commands
######

    def listCommands(self):
        self._requestDeviceList(self._showCommandList, self.displayMenu)

    def _requestDeviceList(self, successCallback, timeoutCallback):
        self.ui.alert('Requesting services list...', False)
        interestName = Name(self.prefix).append('listCommands')
        interest = Interest(interestName)
        interest.setInterestLifetimeMilliseconds(3000)
        #self.face.makeCommandInterest(interest)
        self.face.expressInterest(
            interest, self._makeOnCommandListCallback(successCallback),
            self._makeOnCommandListTimeoutCallback(timeoutCallback))

    def _makeOnCommandListTimeoutCallback(self, callback):
        def onCommandListTimeout(interest):
            self.ui.alert('Timed out waiting for services list')
            self.loop.call_soon(callback)

        return onCommandListTimeout

    def _makeOnCommandListCallback(self, callback):
        def onCommandListReceived(interest, data):
            try:
                commandInfo = json.loads(str(data.getContent()))
            except:
                self.ui.alert(
                    'An error occured while reading the services list')
                self.loop.call_soon(self.displayMenu)
            else:
                self.foundCommands = commandInfo
                self.loop.call_soon(callback)

        return onCommandListReceived

    def _showCommandList(self):
        try:
            commandList = []
            for capability, commands in self.foundCommands.items():
                commandList.append('\Z1{}:'.format(capability))
                for info in commands:
                    signingStr = 'signed' if info['signed'] else 'unsigned'
                    commandList.append('\Z0\t{} ({})'.format(
                        info['name'], signingStr))
                commandList.append('')

            if len(commandList) == 0:
                # should not happen
                commandList = ['----NONE----']
            allCommands = '\n'.join(commandList)
            oldTitle = self.ui.title
            self.ui.title = 'Available services'
            self.ui.alert(allCommands, preExtra=['--colors'])
            self.ui.title = oldTitle
            #self.ui.menu('Available services', commandList, prefix='', extras=['--no-cancel'])
        finally:
            self.loop.call_soon(self.displayMenu)

#######
# New device
######

    def pairDevice(self):
        self._scanForUnconfiguredDevices()

    def _enterPairingInfo(self, serial, pin='', newName=''):
        fields = [
            Dialog.FormField('PIN', pin),
            Dialog.FormField('Device name', newName)
        ]
        (retCode, retList) = self.ui.form('Pairing device {}'.format(serial),
                                          fields)
        if retCode == Dialog.DIALOG_OK:
            pin, newName = retList
            if len(pin) == 0 or len(newName) == 0:
                self.ui.alert('All fields are required')
                self.loop.call_soon(self._enterPairingInfo, serial, pin,
                                    newName)
            else:
                try:
                    pinBytes = pin.decode('hex')
                except TypeError:
                    self.ui.alert('Pin is invalid')
                    self.loop.call_soon(self._enterPairingInfo, serial, pin,
                                        newName)
                else:
                    self._addDeviceToNetwork(serial, newName,
                                             pin.decode('hex'))
        elif retCode == Dialog.DIALOG_CANCEL or retCode == Dialog.DIALOG_ESC:
            self.loop.call_soon(self._showConfigurationList)

    def _showConfigurationList(self):
        foundDevices = self.unconfiguredDevices[:]
        emptyStr = '----NONE----'
        if len(foundDevices) == 0:
            foundDevices.append(emptyStr)
        retCode, retStr = self.ui.menu('Select a device to configure',
                                       foundDevices,
                                       preExtras=[
                                           '--ok-label', 'Configure',
                                           '--extra-button', '--extra-label',
                                           'Refresh'
                                       ])
        if retCode == Dialog.DIALOG_CANCEL or retCode == Dialog.DIALOG_ESC:
            self.loop.call_soon(self.displayMenu)
        elif retCode == Dialog.DIALOG_EXTRA:
            self.loop.call_soon(self._scanForUnconfiguredDevices)
        elif retCode == Dialog.DIALOG_OK and retStr != emptyStr:
            self.loop.call_soon(self._enterPairingInfo, retStr)
        else:
            self.loop.call_soon(self._showConfigurationList)

    def _scanForUnconfiguredDevices(self):
        # unconfigured devices should register '/localhop/configure'
        # we keep asking for unconfigured devices until we stop getting replies

        foundDevices = []

        self.ui.alert('Scanning for unconfigured devices...', False)

        def onDeviceTimeout(interest):
            # assume we're done - everyone is excluded
            self.unconfiguredDevices = foundDevices
            self.loop.call_soon(self._showConfigurationList)

        def onDeviceResponse(interest, data):
            updatedInterest = Interest(interest)
            deviceSerial = str(data.getContent())
            if len(deviceSerial) > 0:
                foundDevices.append(deviceSerial)
                updatedInterest.getExclude().appendComponent(
                    Name.Component(deviceSerial))
            # else ignore the malformed response
            self.face.expressInterest(updatedInterest, onDeviceResponse,
                                      onDeviceTimeout)

        interest = Interest(Name('/localhop/configure'))
        interest.setInterestLifetimeMilliseconds(2000)
        self.face.expressInterest(interest, onDeviceResponse, onDeviceTimeout)

    def _addDeviceToNetwork(self, serial, suffix, pin):
        self.ui.alert('Sending pairing info to gateway...', False)
        # we must encrypt this so no one can see the pin!
        message = DevicePairingInfoMessage()
        message.info.deviceSuffix = suffix
        message.info.deviceSerial = serial
        message.info.devicePin = pin
        rawBytes = ProtobufTlv.encode(message)
        encryptedBytes = self._identityManager.encryptForIdentity(
            rawBytes, self.prefix)
        encodedBytes = base64.urlsafe_b64encode(str(encryptedBytes))
        interestName = Name(
            self.prefix).append('addDevice').append(encodedBytes)
        interest = Interest(interestName)
        # todo: have the controller register this console as a listener
        # and update it with pairing status
        interest.setInterestLifetimeMilliseconds(5000)
        self.face.makeCommandInterest(interest)

        self.face.expressInterest(interest, self._onAddDeviceResponse,
                                  self._onAddDeviceTimeout)

    def _onAddDeviceResponse(self, interest, data):
        try:
            responseCode = int(str(data.getContent()))
            if responseCode == 202:
                self.ui.alert('Gateway received pairing info')
            else:
                self.ui.alert('Error encountered while sending pairing info')
        except:
            self.ui.alert('Exception encountered while decoding gateway reply')
        finally:
            self.loop.call_soon(self.displayMenu)

    def _onAddDeviceTimeout(self, interest):
        self.ui.alert('Timed out sending pairing info to gateway')
        self.loop.call_soon(self.displayMenu)


######
# Express interest
#####

    def expressInterest(self):
        if len(self.foundCommands) == 0:
            self._requestDeviceList(self._showInterestMenu,
                                    self._showInterestMenu)
        else:
            self.loop.call_soon(self._showInterestMenu)

    def _showInterestMenu(self):
        # display a menu of all available interests, on selection allow user to
        # (1) extend it
        # (2) send it signed/unsigned
        # NOTE: should it add custom ones to the list?
        commandSet = set()
        wildcard = '<Enter Interest Name>'
        try:
            for commands in self.foundCommands.values():
                commandSet.update([c['name'] for c in commands])
            commandList = list(commandSet)
            commandList.append(wildcard)
            (returnCode, returnStr) = self.ui.menu('Choose a command',
                                                   commandList,
                                                   prefix=' ')
            if returnCode == Dialog.DIALOG_OK:
                if returnStr == wildcard:
                    returnStr = self.networkPrefix.toUri()
                self.loop.call_soon(self._expressCustomInterest, returnStr)
            else:
                self.loop.call_soon(self.displayMenu)
        except:
            self.loop.call_soon(self.displayMenu)

    def _expressCustomInterest(self, interestName):
        #TODO: make this a form, add timeout field
        try:
            handled = False
            (returnCode,
             returnStr) = self.ui.prompt('Send interest',
                                         interestName,
                                         preExtra=[
                                             '--extra-button', '--extra-label',
                                             'Signed', '--ok-label', 'Unsigned'
                                         ])
            if returnCode == Dialog.DIALOG_ESC or returnCode == Dialog.DIALOG_CANCEL:
                self.loop.call_soon(self.expressInterest)
            else:
                interestName = Name(returnStr)
                doSigned = (returnCode == Dialog.DIALOG_EXTRA)
                interest = Interest(interestName)
                interest.setInterestLifetimeMilliseconds(5000)
                interest.setChildSelector(1)
                interest.setMustBeFresh(True)
                if (doSigned):
                    self.face.makeCommandInterest(interest)
                self.ui.alert(
                    'Waiting for response to {}'.format(interestName.toUri()),
                    False)
                self.face.expressInterest(interest, self.onDataReceived,
                                          self.onInterestTimeout)
        except:
            self.loop.call_soon(self.expressInterest)

    def onInterestTimeout(self, interest):
        try:
            self.ui.alert('Interest timed out:\n{}'.format(
                interest.getName().toUri()))
        except:
            self.ui.alert('Interest timed out')
        finally:
            self.loop.call_soon(self.expressInterest)

    def onDataReceived(self, interest, data):
        try:
            dataString = '{}\n\n'.format(data.getName().toUri())
            dataString += 'Contents:\n{}'.format(
                repr(data.getContent().toRawStr()))
            self.ui.alert(dataString)
        except:
            self.ui.alert('Exception occured displaying data contents')
        finally:
            self.loop.call_soon(self.expressInterest)