def main():
    loop = asyncio.get_event_loop()
    face = ThreadsafeFace(loop, "aleph.ndn.ucla.edu")

    counter = Counter()
    face.stopWhen(lambda: counter._callbackCount >= 1)

    name1 = Name("/") 
    dump("Express name ", name1.toUri())
    # This call to exressIinterest is thread safe because face is a ThreadsafeFace.
    face.expressInterest(name1, counter.onData, counter.onTimeout)

    # Run until stopWhen stops the loop.
    loop.run_forever()
    face.shutdown()
Esempio n. 2
0
def main():
    loop = asyncio.get_event_loop()
    face = ThreadsafeFace(loop, "aleph.ndn.ucla.edu")

    counter = Counter()
    face.stopWhen(lambda: counter._callbackCount >= 1)

    name1 = Name("/")
    dump("Express name ", name1.toUri())
    # This call to exressIinterest is thread safe because face is a ThreadsafeFace.
    face.expressInterest(name1, counter.onData, counter.onTimeout)

    # Run until stopWhen stops the loop.
    loop.run_forever()
    face.shutdown()
Esempio n. 3
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)
class RepoPublisher:
    def __init__(self, repoPrefix, dataPrefix, dataSuffix, keychain=None):
        self.currentInsertion = -1
        self.currentStatus = -1
        self.face = None
        self.loop = None
        self.repoPrefix = Name(repoPrefix)
        self.dataName = Name(dataPrefix).append(dataSuffix)
        self.dataPrefix = Name(dataPrefix)

        if keychain is not None:
            self.keychain = keychain
        else:
            self.keychain = KeyChain()

        self.certificateName = self.keychain.getDefaultCertificateName()

        self.failureCount = 0
        self.successCount = 0

        self.processIdToVersion = {}

    def onRegisterFailed(self):
        logger.error("Could not register data publishing face!")
        self.stop()

    def versionFromCommandMessage(self, component):
        command = RepoCommandParameterMessage()
        try:
            ProtobufTlv.decode(command, component.getValue())
        except Exception as e:
            logger.warn(e)

        # last component of name to insert is version
        versionStr = command.repo_command_parameter.name.component[-1]

        return versionStr

    def stop(self):
        self.loop.close()
        self.face.shutdown()

    def onPublishInterest(self, prefix, interest, transport, pxID):
        '''
           For publishing face
        '''
        # just make up some data and return it
        interestName = interest.getName()
        logger.info("Interest for " + interestName.toUri())

        ## CURRENTLY ASSUMES THERE'S A VERSION+SEGMENT SUFFIX!
        dataName = Name(interestName)
        ts = (time.time())
        segmentId = 0
        #try:
        #   segmentId = interestName.get(-1).toSegment()
        #except:
            #logger.debug("Could not find segment id!")
            #dataName.appendSegment(segmentId)
        versionStr = str(interestName.get(-2).getValue())
        logger.debug('Publishing ' + versionStr + ' @ ' + str(ts))

        d = Data(dataName)
        content = "(" + str(ts) +  ") Data named " + dataName.toUri()
        d.setContent(content)
        d.getMetaInfo().setFinalBlockID(segmentId)
        d.getMetaInfo().setFreshnessPeriod(1000)
        self.keychain.sign(d, self.certificateName)

        encodedData = d.wireEncode()

        stats.insertDataForVersion(versionStr, {'publish_time': time.time()})
        transport.send(encodedData.toBuffer())
        #yield from asyncio.sleep()

    def generateVersionedName(self):
        fullName = Name(self.dataName)
        # currently we need to provide the version ourselves when we
        # poke the repo
        ts = int(time.time()*1000)
        fullName.appendVersion(int(ts))
        return fullName

    def onTimeout(self, prefix):
        logger.warn('Timeout waiting for '+prefix.toUri())

    
    def start(self):
        self.loop = asyncio.new_event_loop()
        self.face = ThreadsafeFace(self.loop, "")

        asyncio.set_event_loop(self.loop)
        self.face.setCommandSigningInfo(self.keychain, self.certificateName)
        self.face.registerPrefix(self.dataPrefix,self.onPublishInterest, self.onRegisterFailed)
        try:
            self.loop.call_soon(self.kickRepo)
            self.loop.run_forever()
        finally:
           self.stop() 


    def kickRepo(self):
        # command the repo to insert a new bit of data
        fullName = self.generateVersionedName()
        versionStr = str(fullName.get(-1).getValue())
        command = self.createInsertInterest(fullName)

        logger.debug('inserting: ' + versionStr)

        self.face.makeCommandInterest(command)
        def timeoutLoop(interest):
            logger.warn('Timed out on ' + interest.toUri())
            self.face.expressInterest(command, self.onCommandData, self.onTimeout)

        self.face.expressInterest(command, self.onCommandData, timeoutLoop)
        stats.insertDataForVersion(versionStr, {'insert_request':time.time()})

    def checkInsertion(self, versionStr, processID):
        fullName = Name(self.dataName).append(Name.fromEscapedString(versionStr))
        checkCommand = self.createCheckInterest(fullName, processID)
        self.face.makeCommandInterest(checkCommand)
        def timeoutLoop(interest):
            logger.warn('Timed out waiting on: '+interest.toUri())
            self.face.expressInterest(checkCommand, self.onCommandData, self.onTimeout)
        self.face.expressInterest(checkCommand, self.onCommandData, timeoutLoop)

    def createInsertInterest(self, fullName):
        '''
            For poking the repo
        '''
        # we have to do the versioning before we poke the repo
        interestName = Name(fullName)
        logger.debug('Creating insert interest for: '+interestName.toUri())
        
        insertionName = Name(self.repoPrefix).append('insert')
        commandParams = RepoCommandParameterMessage()

        for i in range(interestName.size()):
            commandParams.repo_command_parameter.name.component.append(interestName.get(i).getValue().toRawStr())

        commandParams.repo_command_parameter.start_block_id = 0
        commandParams.repo_command_parameter.end_block_id = 0

        commandName = insertionName.append(ProtobufTlv.encode(commandParams))
        interest = Interest(commandName)

        interest.setInterestLifetimeMilliseconds(2000)

        return interest

    def createCheckInterest(self, fullName, checkNum):
        insertionName = Name(self.repoPrefix).append('insert check')
        commandParams = RepoCommandParameterMessage()
        interestName = Name(fullName)

        commandParams.repo_command_parameter.process_id = checkNum
        for i in range(interestName.size()):
            commandParams.repo_command_parameter.name.component.append(str(interestName.get(i).getValue()))

        commandName = insertionName.append(ProtobufTlv.encode(commandParams))
        interest = Interest(commandName)

        return interest

    def onCommandData(self, interest, data):
        # assume it's a command response
        now = time.time()
        response = RepoCommandResponseMessage()
        ProtobufTlv.decode(response, data.getContent())


        self.currentStatus = response.repo_command_response.status_code
        self.currentInsertion =  response.repo_command_response.process_id
        logger.debug("Response status code: " + str(self.currentStatus) + ", process id: " + str(self.currentInsertion) + ", insert #" + str(response.repo_command_response.insert_num))

        command_idx = self.repoPrefix.size()
        # we also need to keep track of the mapping from version to processID for stats
        commandName = interest.getName().get(command_idx).getValue().toRawStr()
        if commandName == 'insert check':
            try:
                versionStr = self.processIdToVersion[self.currentInsertion]
                if self.currentStatus == 200:
                    stats.insertDataForVersion(versionStr, {'insert_complete': now})
                    self.loop.call_soon(self.kickRepo)
                elif self.currentStatus >= 400:
                    self.failureCount += 1
                    self.loop.call_soon(self.kickRepo)
                else:
                    self.loop.call_soon(self.checkInsertion, versionStr, self.currentInserion)
            except:
                logger.warn('Missing version for process ID {}'.format(self.currentInsertion))
        elif commandName == 'insert':
            if self.currentStatus == 100:
                versionStr = self.versionFromCommandMessage(interest.getName().get(command_idx+1))
                self.processIdToVersion[self.currentInsertion] = versionStr
                stats.insertDataForVersion(versionStr, {'insert_begin': now})
                self.loop.call_soon(self.checkInsertion, versionStr, self.currentInsertion)
            else:
                self.failureCount += 1
                self.loop.call_soon(self.kickRepo)
Esempio n. 5
0
class RepoConsumer:
    TIMEOUT = 100
    def __init__(self, dataPrefix, interestLifetime=100, lastVersion=None, start=True):
        self.prefix = Name(dataPrefix)
        
        #used in the exclude to make sure we get new data only
        self.lastVersion = lastVersion

        self.face = None
        self.dataFormat = re.compile("\((\d+\.?\d*)\)") # data should start with a timestamp in 
        logFormat = '%(asctime)-10s %(message)s'
        self.logger = logging.getLogger('RepoConsumer')
        self.logger.setLevel(logging.DEBUG)
        self.logger.addHandler(logging.StreamHandler())
        fh = logging.FileHandler('repo_consumer.log', mode='w')
        fh.setLevel(logging.DEBUG)
        fh.setFormatter(logging.Formatter(logFormat))
        self.logger.addHandler(fh)
        self.createTiming = []
        self.receiveTiming = []
        self.timeouts = 0
        self.notReady = 0

        self.lastReceivedTime = 0
        self.lastCreatedTime = 0
        self.backoffCounter = 0
        
        self.interestLifetime = interestLifetime

        self.nextIssue = None
        self.loop = None

        self.isCancelled = False

    def start(self):
        self.loop =  asyncio.get_event_loop()
        self.face = ThreadsafeFace(self.loop, "")
        self.face.stopWhen(lambda:self.isCancelled)
        self.reissueInterest()
        self.loop.run_forever()

    def stop(self):
        self.loop.close()
        self.face.shutdown()
        self.loop = None
        self.face = None

    def onData(self, interest, data):
        now = time.time()
        # for now, assume there is a version appended to the interest
        nameLength = interest.getName().size()
        dataStr = data.getContent().toRawStr()
        try:
            lastComponent = data.getName().get(-1).toEscapedString()
            if str(lastComponent) == 'MISSING':
                self.notReady += 1
                #self.backoffCounter += 1
                logger.info('repo not ready')
                self.reissueInterest()
                return

            self.lastVersion = data.getName().get(nameLength)
            self.logger.debug(interest.getName().toUri() + ": version " + self.lastVersion.toEscapedString())
            match = self.dataFormat.match(data.getContent().toRawStr())
            ts = float(match.group(1))
            if self.lastReceivedTime != 0 and self.lastCreatedTime != 0:
                self.createTiming.append(now-self.lastReceivedTime)
                self.receiveTiming.append(now-self.lastCreatedTime)

            self.lastReceivedTime = now
            self.lastCreatedTime = ts
            self.logger.debug("Created: " + str(ts) +  ", received: " + str(now))
        except Exception as  e:
            self.logger.exception(str(e))
        #self.backoffCounter -= 1
        self.reissueInterest()

    def onTimeout(self, interest):
        self.logger.debug("timeout")
        self.timeouts += 1
        #self.backoffCounter += 1
        self.reissueInterest()

    def reissueInterest(self):
        BACKOFF_THRESHOLD = 10
        if self.backoffCounter > BACKOFF_THRESHOLD:
            self.TIMEOUT += 50
            self.backoffCounter = 0
            self.logger.debug('Backing off interval to ' + str(self.TIMEOUT))
        if self.backoffCounter < -BACKOFF_THRESHOLD:
            self.TIMEOUT -= 50
            self.backoffCounter = 0
            self.logger.debug('Reducing backoff interval to ' + str(self.TIMEOUT))
        if self.nextIssue is not None:
            now = time.clock()
            if self.nextIssue > now:
                pass
               # time.sleep(self.nextIssue-now)
        interest = Interest(Name(self.prefix))
        interest.setInterestLifetimeMilliseconds(self.interestLifetime)
        interest.setMustBeFresh(False)
        if self.lastVersion is not None:
            e = Exclude()
            e.appendAny()
            e.appendComponent(self.lastVersion)
            interest.setExclude(e)
        interest.setChildSelector(1) #rightmost == freshest
        self.face.expressInterest(interest, self.onData, self.onTimeout)
        self.nextIssue = time.clock()+self.TIMEOUT/2000

    def printStats(self):
        # the first value may have been sitting in the repo forever, so ignore the first time
        timing = self.createTiming
        self.logger.info('***** Statistics ***** ')
        if len(timing) > 1:
            self.logger.info('{1:3.2f}/{2:3.2f}/{3:3.2f} min/mean/max delay(creation)'.format(len(timing), min(timing), mean(timing), max(timing)))
        timing = self.receiveTiming
        if len(timing) > 1:
            self.logger.info('{1:3.2f}/{2:3.2f}/{3:3.2f} min/mean/max delay(receipt)'.format(len(timing), min(timing), mean(timing), max(timing)))
            self.logger.info('{} data requests satisfied'.format(len(timing)))
        self.logger.info('{} timeouts'.format(self.timeouts))
        self.logger.info('{} not ready responses'.format(self.notReady))
        self.logger.info('*'*22)
Esempio n. 6
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)
Esempio n. 7
0
class NdnHierarchicalRepo(object):
    def __init__(self, repoPrefix=None):
        super(NdnHierarchicalRepo, self).__init__()
        self.schemaList = []
        if repoPrefix is None:
            self.repoPrefix = Name('/test/repo')
        else:
            self.repoPrefix = Name(repoPrefix)
        self.keyChain = KeyChain()
        self.currentProcessId = 0

        self.pendingProcesses = {}
        self.log = logging.getLogger(str(self.__class__))
        s = logging.StreamHandler()
        formatter = logging.Formatter(
                '%(asctime)-15s %(funcName)s\n\t %(message)s')
        s.setFormatter(formatter)
        s.setLevel(logging.INFO)
        self.log.addHandler(s)
        self.log.setLevel(logging.INFO)

    def addSchema(self, schema):
        if not isinstance(schema, NdnSchema):
            schema = NdnSchema(schema)
            schema.addField('value', NdnSchema.SCHEMA_BINARY)
            schema.addField('ts', NdnSchema.SCHEMA_TIMESTAMP)
        schema.log = self.log
        self.schemaList.append(schema)
        self.dataPrefix = schema.dbPrefix

    def initializeDatabase(self):
        self.dbClient = mongo.MongoClient()
        # TODO: different DB name for different schemata?
        self.db = self.dbClient['bms']

    def _registerFailed(self, prefix):
        self.log.info('Registration failure')
        if prefix == self.dataPrefix:
            callback = self.handleDataInterests
        elif prefix == self.repoPrefix:
            callback = self.handleCommandInterests

        self.loop.call_later(5, self._register, prefix, callback)

    def _register(self, prefix, callback):
        self.face.registerPrefix(prefix, callback, self._registerFailed) 
        
    def insertData(self, data):
        dataName = data.getName()
        self.log.info('Inserting {}'.format(dataName))
        useSchema = (s for s in self.schemaList 
                if s.dbPrefix.match(dataName)).next()
        
        #exclude timestamp...
        dataFields = useSchema.matchNameToSchema(dataName[:-1])
        if len(dataFields) == 0:
            self.log.error('Invalid data name for schema')
   
        dataValue = Binary(str(data.getContent()))
        tsComponent = str(dataName[-1].getValue())
        tsConverted = float(struct.unpack("!Q", tsComponent)[0])/1000
        dataFields.update({'value':dataValue, 'ts':tsConverted})
        dataFields = useSchema.sanitizeData(dataFields)
        dataCollection = self.db['data']
        new_id = dataCollection.update(dataFields, dataFields, upsert=True)
        self.log.debug('Inserted object {}'.format(new_id))

    def start(self):
        self.initializeDatabase()
        self.loop = asyncio.get_event_loop()
        self.face = ThreadsafeFace(self.loop, '')
        self.face.setCommandSigningInfo(self.keyChain, 
                self.keyChain.getDefaultCertificateName())
        self._register(self.dataPrefix, self.handleDataInterests)
        self._register(self.repoPrefix, self.handleCommandInterests)

        #TODO: figure out why nfdc doesn't forward to borges
        self._insertFace = ThreadsafeFace(self.loop, 'borges.metwi.ucla.edu')

        try:
            self.loop.run_forever()
        except Exception as e:
            self.log.exception(e, exc_info=True)
            self.face.shutdown()

    def _segmentResponseData(self, dataQuery, segmentNum=0):
        limit = 10
        startPos = segmentNum*limit
        results = self.db['data'].find(dataQuery).skip(segmentNum*limit).limit(limit)
        return(startPos, results)


    def handleDataInterests(self, prefix, interest, transport, prefixId):
        # TODO: verification
        
        # we match the components to the name, and any '_' components
        # are discarded. Then we run a MongoDB query and append the
        # object id to allow for excludes

        chosenSchema = (s for s in self.schemaList 
                if s.dbPrefix.match(prefix)).next()

        interestName = interest.getName()
        responseName = Name(interestName)
        nameFields = chosenSchema.matchNameToSchema(interestName)
        self.log.info("Data requested with params:\n\t{}".format(nameFields))
        allResults = []
        segment = 0
        try:
            segmentComponent = interest.getName()[-4]
            segment = segmentComponent.toSegment()
        except RuntimeError:
            pass
        
        (startPos, results) = self._segmentResponseData(nameFields, segment)
        for result in results:
            dataId = result[u'_id']
            self.log.debug("Found object {}".format(dataId))
            allResults.append(result)

        #responseName.append(str(dataId))
        totalCount = results.count(False)
        responseObject = {'count':totalCount, 'skip':startPos, 'results':allResults}
        responseData = Data(responseName)
        resultEncoded = BSON.encode(responseObject)
        responseData.setContent(resultEncoded)
        transport.send(responseData.wireEncode().buf())


    def _onInsertionDataReceived(self, interest, data):
        # TODO: update status in processingList
        self.log.debug("Got {} in response to {}".format(data.getName(), 
                interest.getName()))
        self.insertData(data)
        

    def _onInsertionDataTimeout(self, interest):
        self.log.warn("Timeout on {}".format(interest.getName()))

    def handleCommandInterests(self, prefix, interest, transport, prefixId):
        # TODO: verification
        interestName = interest.getName()
        if len(interestName) <= len(prefix)+4:
            self.log.info("Bad command interest")
        commandName = str(interestName[len(prefix)].getValue())
        responseMessage =  RepoCommandResponseMessage()
        if commandName == 'insert':
            commandParams = interestName[len(prefix)+1].getValue()
            commandMessage = RepoCommandParameterMessage()
            ProtobufTlv.decode(commandMessage, commandParams)
            dataName = Name()
            fullSchemaName = Name()
            for component in commandMessage.command.name.components:
                fullSchemaName.append(component)
                if component == '_':
                    continue
                dataName.append(component)
            self.log.info("Insert request for {}".format(dataName))
            responseMessage.response.status_code = 100
            processId = self.currentProcessId
            self.currentProcessId += 1
            responseMessage.response.process_id = processId
        else:
            responseMessage.response.status_code = 403
        responseData = Data(interestName)
        responseData.setContent(ProtobufTlv.encode(responseMessage))
        transport.send(responseData.wireEncode().buf())

        # now send the interest out to the publisher
        # TODO: pendingProcesses becomes list of all processes as objects
        i = Interest(dataName)
        i.setChildSelector(1)
        i.setInterestLifetimeMilliseconds(4000)
        try:
            self.pendingProcesses[processId] = (dataName, 100)
        except NameError:
            pass # wasn't valid insert request
        else:
            self._insertFace.expressInterest(i, 
                    self._onInsertionDataReceived,
                    self._onInsertionDataTimeout)