Exemplo n.º 1
0
def main():
    # The default Face will connect using a Unix socket, or to "localhost".
    face = Face()

    identityStorage = MemoryIdentityStorage()
    privateKeyStorage = MemoryPrivateKeyStorage()
    keyChain = KeyChain(IdentityManager(identityStorage, privateKeyStorage),
                        None)
    keyChain.setFace(face)

    # Initialize the storage.
    keyName = Name("/testname/DSK-123")
    certificateName = keyName.getSubName(
        0,
        keyName.size() - 1).append("KEY").append(
            keyName[-1]).append("ID-CERT").append("0")
    identityStorage.addKey(keyName, KeyType.RSA,
                           Blob(DEFAULT_RSA_PUBLIC_KEY_DER))
    privateKeyStorage.setKeyPairForKeyName(keyName, KeyType.RSA,
                                           DEFAULT_RSA_PUBLIC_KEY_DER,
                                           DEFAULT_RSA_PRIVATE_KEY_DER)

    echo = Echo(keyChain, certificateName)
    prefix = Name("/testecho")
    dump("Register prefix", prefix.toUri())
    face.registerPrefix(prefix, echo.onInterest, echo.onRegisterFailed)

    while echo._responseCount < 1:
        face.processEvents()
        # We need to sleep for a few milliseconds so we don't use 100% of the CPU.
        time.sleep(0.01)

    face.shutdown()
Exemplo n.º 2
0
def main():
    face = Face("localhost")

    identityStorage = MemoryIdentityStorage()
    privateKeyStorage = MemoryPrivateKeyStorage()
    keyChain = KeyChain(
      IdentityManager(identityStorage, privateKeyStorage), None)
    keyChain.setFace(face)

    # Initialize the storage.
    keyName = Name("/testname/DSK-reposerver")
    certificateName = keyName.getSubName(0, keyName.size() - 1).append(
      "KEY").append(keyName[-1]).append("ID-CERT").append("0")
    identityStorage.addKey(keyName, KeyType.RSA, Blob(DEFAULT_PUBLIC_KEY_DER))
    privateKeyStorage.setKeyPairForKeyName(
      keyName, DEFAULT_PUBLIC_KEY_DER, DEFAULT_PRIVATE_KEY_DER)

    echo = RepoServer(keyChain, certificateName)
    prefix = Name("/ndn/ucla.edu/bms")
    dump("Register prefix", prefix.toUri())
    face.registerPrefix(prefix, echo.onInterest, echo.onRegisterFailed)

    while True: 
        face.processEvents()
        # We need to sleep for a few milliseconds so we don't use 100% of the CPU.
        time.sleep(0.01)

    face.shutdown()
Exemplo n.º 3
0
def main():
    # The default Face will connect using a Unix socket, or to "localhost".
    face = Face()

    identityStorage = MemoryIdentityStorage()
    privateKeyStorage = MemoryPrivateKeyStorage()
    keyChain = KeyChain(
      IdentityManager(identityStorage, privateKeyStorage), None)
    keyChain.setFace(face)

    # Initialize the storage.
    keyName = Name("/testname/DSK-123")
    certificateName = keyName.getSubName(0, keyName.size() - 1).append(
      "KEY").append(keyName[-1]).append("ID-CERT").append("0")
    identityStorage.addKey(keyName, KeyType.RSA, Blob(DEFAULT_RSA_PUBLIC_KEY_DER))
    privateKeyStorage.setKeyPairForKeyName(
      keyName, KeyType.RSA, DEFAULT_RSA_PUBLIC_KEY_DER, DEFAULT_RSA_PRIVATE_KEY_DER)

    echo = Echo(keyChain, certificateName)
    prefix = Name("/testecho")
    dump("Register prefix", prefix.toUri())
    face.registerPrefix(prefix, echo.onInterest, echo.onRegisterFailed)

    while echo._responseCount < 1:
        face.processEvents()
        # We need to sleep for a few milliseconds so we don't use 100% of the CPU.
        time.sleep(0.01)    

    face.shutdown()
Exemplo n.º 4
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()
Exemplo n.º 5
0
def main():
    # Uncomment these lines to print ChronoSync debug messages.
    # logging.getLogger('').addHandler(logging.StreamHandler(sys.stdout))
    # logging.getLogger('').setLevel(logging.INFO)

    screenName = promptAndInput("Enter your chat username: "******"ndn/edu/ucla/remap"
    hubPrefix = promptAndInput("Enter your hub prefix [" + defaultHubPrefix + "]: ")
    if hubPrefix == "":
        hubPrefix = defaultHubPrefix

    defaultChatRoom = "ndnchat"
    chatRoom = promptAndInput("Enter the chatroom name [" + defaultChatRoom + "]: ")
    if chatRoom == "":
        chatRoom = defaultChatRoom

    host = "localhost"
    print("Connecting to " + host + ", Chatroom: " + chatRoom + ", Username: "******"")

    # Set up the key chain.
    face = Face(host)

    identityStorage = MemoryIdentityStorage()
    privateKeyStorage = MemoryPrivateKeyStorage()
    keyChain = KeyChain(IdentityManager(identityStorage, privateKeyStorage),
                        NoVerifyPolicyManager())
    keyChain.setFace(face)
    keyName = Name("/testname/DSK-123")
    certificateName = keyName.getSubName(0, keyName.size() - 1).append(
      "KEY").append(keyName[-1]).append("ID-CERT").append("0")
    identityStorage.addKey(keyName, KeyType.RSA, Blob(DEFAULT_RSA_PUBLIC_KEY_DER))
    privateKeyStorage.setKeyPairForKeyName(
      keyName, KeyType.RSA, DEFAULT_RSA_PUBLIC_KEY_DER, DEFAULT_RSA_PRIVATE_KEY_DER)
    face.setCommandSigningInfo(keyChain, certificateName)

    chat = Chat(
      screenName, chatRoom, Name(hubPrefix), face, keyChain, certificateName)

    # The main loop to process Chat while checking stdin to send a message.
    print("Enter your chat message. To quit, enter \"leave\" or \"exit\".")
    while True:
        # Set timeout to 0 for an immediate check.
        isReady, _, _ = select.select([sys.stdin], [], [], 0)
        if len(isReady) != 0:
            input = promptAndInput("")
            if input == "leave" or input == "exit":
                # We will send the leave message below.
                break

            chat.sendMessage(input)

        face.processEvents()
        # We need to sleep for a few milliseconds so we don't use 100% of the CPU.
        time.sleep(0.01)

    # The user entered the command to leave.
    chat.leave()
    # Wait a little bit to allow other applications to fetch the leave message.
    startTime = Chat.getNowMilliseconds()
    while True:
        if Chat.getNowMilliseconds() - startTime >= 1000.0:
            break

        face.processEvents()
        time.sleep(0.01)
Exemplo n.º 6
0
class BmsNode(object):
    def __init__(self):
        self.conf = None
        self._keyChain = None
        self._certificateName = None

        self._dataQueue = dict()
        self._memoryContentCache = None
        self._identityName = None

        self._aggregation = Aggregation()

    def setConfiguration(self, fileName, trustSchemaFile):
        self.conf = BoostInfoParser()
        self.conf.read(fileName)
        self._identityName = Name(self.conf.getNodePrefix())
        self._trustSchemaFile = trustSchemaFile

    def onDataNotFound(self, prefix, interest, face, interestFilterId, filter):
        #print('Data not found for ' + interest.getName().toUri())
        return

    def startPublishing(self):
        # One-time security setup
        self.prepareLogging()

        privateKeyStorage = FilePrivateKeyStorage()
        identityStorage = BasicIdentityStorage()
        policyManager = ConfigPolicyManager(self._trustSchemaFile)

        self._keyChain = KeyChain(
            IdentityManager(identityStorage, privateKeyStorage), policyManager)
        self._certificateName = self._keyChain.createIdentityAndCertificate(
            self._identityName)

        print("My Identity name: " + self._identityName.toUri())
        print("My certificate name: " + self._certificateName.toUri())
        certificateData = self._keyChain.getIdentityManager(
        )._identityStorage.getCertificate(self._certificateName)
        print("My certificate string: " +
              b64encode(certificateData.wireEncode().toBuffer()))
        # self._keyChain.getIdentityCertificate(self._certificateName).)

        self._loop = asyncio.get_event_loop()
        self._face = ThreadsafeFace(self._loop)
        self._keyChain.setFace(self._face)

        self._face.setCommandSigningInfo(self._keyChain, self._certificateName)
        self._memoryContentCache = MemoryContentCache(self._face)

        # We should only ask for cert to be signed upon the first run of a certain aggregator
        if DO_CERT_SETUP:
            if (KeyLocator.getFromSignature(
                    certificateData.getSignature()).getKeyName().equals(
                        self._certificateName.getPrefix(-1))):
                # Need to configure for mini-ndn; aggregation node runs outside of mini-ndn first so that signed cert get installed and mini-ndn won't ask for this again
                print("certificate " + self._certificateName.toUri() +
                      " asking for signature")
                response = urllib2.urlopen(
                    "http://192.168.56.1:5000/bms-cert-hack?cert=" +
                    b64encode(certificateData.wireEncode().toBuffer()) +
                    "&cert_prefix=" + self._identityName.toUri() +
                    '&subject_name=' + self._identityName.toUri()).read()

                signedCertData = Data()
                signedCertData.wireDecode(Blob(b64decode(response)))

                self._memoryContentCache.add(signedCertData)
                cmdline = ['ndnsec-install-cert', '-']
                p = subprocess.Popen(cmdline,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE)
                # desanitize + sign in GET request
                cert, err = p.communicate(response)
                if p.returncode != 0:
                    raise RuntimeError("ndnsec-install-cert error")
            else:
                self._memoryContentCache.add(certificateData)
        else:
            self._memoryContentCache.add(certificateData)

        dataNode = self.conf.getDataNode()
        childrenNode = self.conf.getChildrenNode()

        self._memoryContentCache.registerPrefix(Name(self._identityName),
                                                self.onRegisterFailed,
                                                self.onDataNotFound)

        # For each type of data, we refresh each type of aggregation according to the interval in the configuration
        for i in range(len(dataNode.subtrees)):
            dataType = dataNode.subtrees.keys()[i]
            aggregationParams = self.conf.getProducingParamsForAggregationType(
                dataNode.subtrees.items()[i][1])

            if childrenNode == None:
                self._dataQueue[dataType] = DataQueue(None, None, None)
                self.generateData(dataType, 2, 0)

            for aggregationType in aggregationParams:
                childrenList = OrderedDict()
                if childrenNode != None:

                    for j in range(len(childrenNode.subtrees)):
                        if dataType in childrenNode.subtrees.items(
                        )[j][1].subtrees['data'].subtrees:
                            if aggregationType in childrenNode.subtrees.items(
                            )[j][1].subtrees['data'].subtrees[
                                    dataType].subtrees:
                                childrenList[childrenNode.subtrees.items()[j][
                                    0]] = self.conf.getProducingParamsForAggregationType(
                                        childrenNode.subtrees.items()[j]
                                        [1].subtrees['data'].subtrees[dataType]
                                    )[aggregationType]

                self.startPublishingAggregation(
                    aggregationParams[aggregationType], childrenList, dataType,
                    aggregationType)
        return

    def startPublishingAggregation(self, params, childrenList, dataType,
                                   aggregationType):
        if __debug__:
            print('Start publishing for ' + dataType + '-' + aggregationType)

        # aggregation calculating and publishing mechanism
        publishingPrefix = Name(
            self._identityName).append(DATA_COMPONENT).append(dataType).append(
                AGGREGATION_COMPONENT).append(aggregationType)
        self._dataQueue[dataType + aggregationType] = DataQueue(
            params, childrenList, publishingPrefix)

        if len(childrenList.keys()) == 0:
            # TODO: make start_time optional for leaf nodes
            self._loop.call_later(int(params['producer_interval']),
                                  self.calculateAggregation, dataType,
                                  aggregationType, childrenList,
                                  int(params['start_time']),
                                  int(params['producer_interval']),
                                  publishingPrefix, True)
        else:
            # express interest for children who produce the same data and aggregation type
            for childName in childrenList.keys():
                name = Name(self._identityName).append(childName).append(
                    DATA_COMPONENT).append(dataType).append(
                        AGGREGATION_COMPONENT).append(aggregationType)
                interest = Interest(name)
                # if start_time is specified, we ask for data starting at start_time;
                # if not, we ask for the right most child and go from there
                if ('start_time' in childrenList[childName]):
                    endTime = int(childrenList[childName]['start_time']) + int(
                        childrenList[childName]['producer_interval'])
                    interest.getName().append(
                        str(childrenList[childName]['start_time'])).append(
                            str(endTime))
                else:
                    # TODO: For now we are playing with historical data, for each run we don't want to miss any data, thus we start with leftMost
                    interest.setChildSelector(0)
                    interest.setMustBeFresh(True)
                interest.setInterestLifetimeMilliseconds(
                    DEFAULT_INTEREST_LIFETIME)
                if __debug__:
                    print('  Issue interest: ' + interest.getName().toUri())
                self._face.expressInterest(interest, self.onData,
                                           self.onTimeout)

        return

    # TODO: once one calculation's decided a child has not answered, we should do another calculation
    def calculateAggregation(self,
                             dataType,
                             aggregationType,
                             childrenList,
                             startTime,
                             interval,
                             publishingPrefix,
                             repeat=False):
        doCalc = True
        dataList = []

        # TODO: an intermediate node cannot produce raw data for now
        if len(childrenList.keys()) != 0:
            for childName in childrenList.keys():
                dataDictKey = self.getDataDictKey(startTime,
                                                  (startTime + interval),
                                                  childName)
                if dataDictKey in self._dataQueue[dataType +
                                                  aggregationType]._dataDict:
                    data = self._dataQueue[
                        dataType + aggregationType]._dataDict[dataDictKey]
                    dataList.append(float(data.getContent().toRawStr()))
                else:
                    #print('Child ' + childName + ' has not replied yet')
                    doCalc = False
                    break
        else:
            for inst in self._dataQueue[dataType]._dataDict.keys():
                if int(inst) >= startTime and int(inst) < startTime + interval:
                    dataList.append(self._dataQueue[dataType]._dataDict[inst])
        if doCalc:
            content = self._aggregation.getAggregation(aggregationType,
                                                       dataList)
            if content:
                publishData = Data(
                    Name(publishingPrefix).append(str(startTime)).append(
                        str(startTime + interval)))
                publishData.setContent(str(content))
                publishData.getMetaInfo().setFreshnessPeriod(
                    DEFAULT_DATA_LIFETIME)
                self._keyChain.sign(publishData, self._certificateName)
                self._memoryContentCache.add(publishData)
                for childName in childrenList.keys():
                    dataDictKey = self.getDataDictKey(startTime,
                                                      (startTime + interval),
                                                      childName)
                    if dataDictKey in self._dataQueue[
                            dataType + aggregationType]._dataDict:
                        del self._dataQueue[
                            dataType + aggregationType]._dataDict[dataDictKey]
                if __debug__:
                    print("Produced: " + publishData.getName().toUri() + "; " +
                          publishData.getContent().toRawStr())

        # repetition of this function only happens for raw data producer, otherwise calculateAggregation is called by each onData
        if repeat:
            self._loop.call_later(interval, self.calculateAggregation,
                                  dataType, aggregationType, childrenList,
                                  startTime + interval, interval,
                                  publishingPrefix, repeat)
        return

    def generateData(self, dataType, interval, startTime):
        self._dataQueue[dataType]._dataDict[str(startTime)] = random.randint(
            0, 9)
        self._loop.call_later(interval, self.generateData, dataType, interval,
                              startTime + interval)
        return

    def onRegisterFailed(self, prefix):
        raise RuntimeError("Register failed for prefix", prefix.toUri())

    def onVerified(self, data):
        print('Data verified: ' + data.getName().toUri())
        return

    def onVerifyFailed(self, data):
        print('Data verification failed: ' + data.getName().toUri())
        return

    def onData(self, interest, data):
        self._keyChain.verifyData(data, self.onVerified, self.onVerifyFailed)

        dataName = data.getName()
        dataQueue = None

        if __debug__:
            print("Got data: " + dataName.toUri() + "; " +
                  data.getContent().toRawStr())
        for i in range(0, len(dataName)):
            if dataName.get(i).toEscapedString() == AGGREGATION_COMPONENT:
                dataType = dataName.get(i - 1).toEscapedString()
                aggregationType = dataName.get(i + 1).toEscapedString()

                startTime = int(dataName.get(i + 2).toEscapedString())
                endTime = int(dataName.get(i + 3).toEscapedString())
                childName = dataName.get(i - 3).toEscapedString()

                dataAndAggregationType = dataType + aggregationType

                dataDictKey = self.getDataDictKey(startTime, endTime,
                                                  childName)
                dataQueue = self._dataQueue[dataAndAggregationType]
                dataQueue._dataDict[dataDictKey] = data
                break

        # TODO: check what if interval/starttime is misconfigured
        if dataQueue:
            self.calculateAggregation(dataType, aggregationType,
                                      dataQueue._childrenList, startTime,
                                      endTime - startTime,
                                      dataQueue._publishingPrefix)

        # Always ask for the next piece of data when we receive this one; assumes interval does not change; this also assumes there are no more components after endTime
        #newInterestName = dataName.getPrefix(i + 2).append(str(endTime)).append(str(endTime + (endTime - startTime)))

        # We don't expect aggregated data name to be continuous within our given time window, so we ask with exclusion instead
        newInterestName = dataName.getPrefix(i + 2)
        newInterest = Interest(interest)
        newInterest.setName(newInterestName)
        newInterest.setChildSelector(0)

        exclude = Exclude()
        exclude.appendAny()
        exclude.appendComponent(dataName.get(i + 2))
        newInterest.setExclude(exclude)

        self._face.expressInterest(newInterest, self.onData, self.onTimeout)
        if __debug__:
            print("  issue interest: " + interest.getName().toUri())

        return

    def onTimeout(self, interest):
        if __debug__:
            print("  interest timeout: " + interest.getName().toUri() +
                  "; reexpress")
            pass
        self._face.expressInterest(interest, self.onData, self.onTimeout)
        return

    def stop(self):
        self._loop.stop()
        if __debug__:
            print("Stopped")
        return

    # This creation of dataDictKey means parent and child should not have the same name
    @staticmethod
    def getDataDictKey(startTime, endTime, childName):
        return str(startTime) + '/' + str(endTime) + '/' + childName


##
# 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
Exemplo n.º 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)
Exemplo n.º 8
0
def main():
    # Uncomment these lines to print ChronoSync debug messages.
    # logging.getLogger('').addHandler(logging.StreamHandler(sys.stdout))
    # logging.getLogger('').setLevel(logging.INFO)

    screenName = promptAndInput("Enter your chat username: "******"ndn/edu/ucla/remap"
    hubPrefix = promptAndInput("Enter your hub prefix [" + defaultHubPrefix +
                               "]: ")
    if hubPrefix == "":
        hubPrefix = defaultHubPrefix

    defaultChatRoom = "ndnchat"
    chatRoom = promptAndInput("Enter the chatroom name [" + defaultChatRoom +
                              "]: ")
    if chatRoom == "":
        chatRoom = defaultChatRoom

    host = "localhost"
    print("Connecting to " + host + ", Chatroom: " + chatRoom +
          ", Username: "******"")

    # Set up the key chain.
    face = Face(host)

    identityStorage = MemoryIdentityStorage()
    privateKeyStorage = MemoryPrivateKeyStorage()
    keyChain = KeyChain(IdentityManager(identityStorage, privateKeyStorage),
                        NoVerifyPolicyManager())
    keyChain.setFace(face)
    keyName = Name("/testname/DSK-123")
    certificateName = keyName.getSubName(
        0,
        keyName.size() - 1).append("KEY").append(
            keyName[-1]).append("ID-CERT").append("0")
    identityStorage.addKey(keyName, KeyType.RSA,
                           Blob(DEFAULT_RSA_PUBLIC_KEY_DER))
    privateKeyStorage.setKeyPairForKeyName(keyName, KeyType.RSA,
                                           DEFAULT_RSA_PUBLIC_KEY_DER,
                                           DEFAULT_RSA_PRIVATE_KEY_DER)
    face.setCommandSigningInfo(keyChain, certificateName)

    chat = Chat(screenName, chatRoom, Name(hubPrefix), face, keyChain,
                certificateName)

    # The main loop to process Chat while checking stdin to send a message.
    print("Enter your chat message. To quit, enter \"leave\" or \"exit\".")
    while True:
        # Set timeout to 0 for an immediate check.
        isReady, _, _ = select.select([sys.stdin], [], [], 0)
        if len(isReady) != 0:
            input = promptAndInput("")
            if input == "leave" or input == "exit":
                # We will send the leave message below.
                break

            chat.sendMessage(input)

        face.processEvents()
        # We need to sleep for a few milliseconds so we don't use 100% of the CPU.
        time.sleep(0.01)

    # The user entered the command to leave.
    chat.leave()
    # Wait a little bit to allow other applications to fetch the leave message.
    startTime = Chat.getNowMilliseconds()
    while True:
        if Chat.getNowMilliseconds() - startTime >= 1000.0:
            break

        face.processEvents()
        time.sleep(0.01)
Exemplo n.º 9
0
def main():
    # Uncomment these lines to print ChronoSync debug messages.
    # logging.getLogger('').addHandler(logging.StreamHandler(sys.stdout))
    # logging.getLogger('').setLevel(logging.INFO)

    defaultUserPrefix = "com/newspaper/USER/bob"
    userPrefix = promptAndInput("Enter user prefix: [" + defaultUserPrefix + "]")
    if userPrefix == "":
        userPrefix = defaultUserPrefix

    defaultNamespacePrefix = "/ndn/hackathon/cnl-demo/slides" #"com/newspaper"
    namespacePrefix = promptAndInput("Enter namespace prefix [" + defaultNamespacePrefix + "]: ")
    if namespacePrefix == "":
        namespacePrefix = defaultNamespacePrefix

    host = "localhost" #"memoria.ndn.ucla.edu"
    print("Connecting to " + host)
    print("")

    # Set up the key chain.
    face = Face(host)

    identityStorage = MemoryIdentityStorage()
    privateKeyStorage = MemoryPrivateKeyStorage()
    keyChain = KeyChain(IdentityManager(identityStorage, privateKeyStorage),
                        NoVerifyPolicyManager())
    keyChain.setFace(face)
    keyName = Name("/testname/DSK-123")
    certificateName = keyName.getSubName(0, keyName.size() - 1).append(
      "KEY").append(keyName[-1]).append("ID-CERT").append("0")
    identityStorage.addKey(keyName, KeyType.RSA, Blob(DEFAULT_RSA_PUBLIC_KEY_DER))
    privateKeyStorage.setKeyPairForKeyName(
      keyName, KeyType.RSA, DEFAULT_RSA_PUBLIC_KEY_DER, DEFAULT_RSA_PRIVATE_KEY_DER)
    face.setCommandSigningInfo(keyChain, certificateName)

    newspaper = Namespace(namespacePrefix)
    
    def onContentSet(namespace, contentNamespace, callbackId):
        global currentSlideName
        if contentNamespace == namespace:
            print("content size "+str(contentNamespace.content.size()))
            currentSlideName = contentNamespace.getName()
            displayImage(contentNamespace.content.toRawStr(), contentNamespace.getName().toUri())
            # dump("Got segmented content ", contentNamespace.content.toRawStr())

    def onNewName(namespace, addedNamespace, callbackId):
        print("namespace ("+addedNamespace.getName().toUri()+") added to "+namespace.getName().toUri())
        if addedNamespace.getName().get(-1).isSegment() and addedNamespace.getName().get(-1).toSegment() == 0:
            addedNamespace.getParent().addOnContentSet(onContentSet)
            SegmentedContent(addedNamespace.getParent()).start()

    newspaper.addOnNameAdded(onNewName)
    newspaper.setFace(face)

    namesync = NameSyncHandler(newspaper, userPrefix, keyChain, certificateName)

    # The main loop to process Chat while checking stdin to send a message.
    print("Enter your namespace update. To quit, enter \"exit\".")

    def process():
        # while True:
        # Set timeout to 0 for an immediate check.
        isReady, _, _ = select.select([sys.stdin], [], [], 0)
        if len(isReady) != 0:
            input = promptAndInput("")
            if input == "exit":
                stopGui()
                # We will send the leave message below.
                # break

            # before producer has namespace.publish call, we manually call onNameAdded as a hack to publish
            namesync.onNameAdded(None, Namespace(Name(input)), 0, True)
        face.processEvents()
        if root: 
            root.after(100, process)

    def leftKey(event):
        global currentSlideName
        allVersions = newspaper.getChildComponents()
        currentVersion = currentSlideName[-1]
        selected = allVersions[0]
        for c in allVersions:
            print(str(c.toVersion()))
            if c.toVersion() == currentVersion.toVersion():
                break
            selected = c
        currentSlideName = Name(newspaper.getName()).append(selected)
        displayImage(newspaper.getChild(selected).content.toRawStr(), currentSlideName.toUri())

    def rightKey(event):
        global currentSlideName
        allVersions = newspaper.getChildComponents()
        currentVersion = currentSlideName[-1]
        selected = None
        for c in allVersions[::-1]:
            if c.toVersion() == currentVersion.toVersion():
                break
            selected = c
        if selected:
            currentSlideName = Name(newspaper.getName()).append(selected)
            displayImage(newspaper.getChild(selected).content.toRawStr(), currentSlideName.toUri())
        else:
            print("no slides to show")

    runGui(process, leftKey, rightKey)
Exemplo n.º 10
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()
Exemplo n.º 11
0
class BaseNode(object):
    """
    This class contains methods/attributes common to both node and controller.
    """
    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 = BasicIdentityStorage()

        self._identityManager = IdentityManager(self._identityStorage, FilePrivateKeyStorage())
        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


##
# 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 as e:
            # zhehao: in the case of producer's /localhop prefixes, the default key is not defined in ndnsec-public-info.db
            certName = self._keyChain.createIdentityAndCertificate(self._policyManager.getDeviceIdentity())
            #certName = self._keyChain.getDefaultCertificateName()
            #print(certName.toUri())
        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()
        
        if (self.faceTransport == None or self.faceTransport == ''):
            self.face = ThreadsafeFace(self.loop)
        else:
            self.face = ThreadsafeFace(self.loop, self.faceTransport, self.faceConn)
        
        self.face.setCommandSigningInfo(self._keyChain, self.getDefaultCertificateName())
        
        self._keyChain.setFace(self.face)

        self._isStopped = False
        self.beforeLoopStart()
        
        try:
            self.loop.run_forever()
        except Exception as e:
            self.log.exception(exc_info=True)
        finally:
            self.stop()

    def stop(self):
        """
        Stops the node, taking it off the network
        """
        self.log.info("Shutting down")
        self._isStopped = True 
        self.loop.stop()
        
###
# 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, sign=True):
        """
        Reply to an interest with a data packet, optionally signing it.
        :param pyndn.Data data: The response data packet
        :param boolean sign: (optional, default=True) Whether the response must be signed. 
        """
        if sign:
            self.signData(data)
        self.face.putData(data)

###
# 
# 
##
    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.faceTransport != None and self.faceConn != None:
            self.log.warn("Explicit face transport and connectionInfo: Could not register {}; expect a manual or autoreg on the other side.".format(prefix.toUri()))
        elif 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.info("Prefix registration failed")
            self.stop()

    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 getSerial():
        """
        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
        """
        try:
            with open('/proc/cpuinfo') as f:
                for line in f:
                    if line.startswith('Serial'):
                        return line.split(':')[1].strip()
        except NameError:
            return "todo"
Exemplo n.º 12
0
class BmsNode(object):
    def __init__(self):
        self.conf = None
        self._keyChain = None
        self._certificateName = None

        self._dataQueue = dict()
        self._memoryContentCache = None
        self._identityName = None

        self._aggregation = Aggregation()

    def setConfiguration(self, fileName, trustSchemaFile):
        self.conf = BoostInfoParser()
        self.conf.read(fileName)
        self._identityName = Name(self.conf.getNodePrefix())
        self._trustSchemaFile = trustSchemaFile

    def onDataNotFound(self, prefix, interest, face, interestFilterId, filter):
        #print('Data not found for ' + interest.getName().toUri())
        return

    def startPublishing(self):
        # One-time security setup
        self.prepareLogging()

        privateKeyStorage = FilePrivateKeyStorage()
        identityStorage = BasicIdentityStorage()
        policyManager = ConfigPolicyManager(self._trustSchemaFile)

        self._keyChain = KeyChain(IdentityManager(identityStorage, privateKeyStorage), policyManager)
        self._certificateName = self._keyChain.createIdentityAndCertificate(self._identityName)

        print("My Identity name: " + self._identityName.toUri())
        print("My certificate name: " + self._certificateName.toUri())
        certificateData = self._keyChain.getIdentityManager()._identityStorage.getCertificate(self._certificateName, True)
        print("My certificate string: " + b64encode(certificateData.wireEncode().toBuffer()))
        # self._keyChain.getIdentityCertificate(self._certificateName).)

        self._loop = asyncio.get_event_loop()
        self._face = ThreadsafeFace(self._loop)
        self._keyChain.setFace(self._face)

        self._face.setCommandSigningInfo(self._keyChain, self._certificateName)
        self._memoryContentCache = MemoryContentCache(self._face)

        # We should only ask for cert to be signed upon the first run of a certain aggregator
        if DO_CERT_SETUP:
            if (KeyLocator.getFromSignature(certificateData.getSignature()).getKeyName().equals(self._certificateName.getPrefix(-1))):
                # Need to configure for mini-ndn; aggregation node runs outside of mini-ndn first so that signed cert get installed and mini-ndn won't ask for this again
                print("certificate " + self._certificateName.toUri() + " asking for signature")
                response = urllib2.urlopen("http://192.168.56.1:5000/bms-cert-hack?cert=" + b64encode(certificateData.wireEncode().toBuffer()) + "&cert_prefix=" + self._identityName.toUri() + '&subject_name=' + self._identityName.toUri()).read()
                
                signedCertData = Data()
                signedCertData.wireDecode(Blob(b64decode(response)))

                self._memoryContentCache.add(signedCertData)
                cmdline = ['ndnsec-install-cert', '-']
                p = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
                # desanitize + sign in GET request
                cert, err = p.communicate(response)
                if p.returncode != 0:
                    raise RuntimeError("ndnsec-install-cert error")
            else:
                self._memoryContentCache.add(certificateData)
        else:
            self._memoryContentCache.add(certificateData)

        dataNode = self.conf.getDataNode()
        childrenNode = self.conf.getChildrenNode()

        self._memoryContentCache.registerPrefix(Name(self._identityName), self.onRegisterFailed, self.onDataNotFound)

        # For each type of data, we refresh each type of aggregation according to the interval in the configuration
        for i in range(len(dataNode.subtrees)):
            dataType = dataNode.subtrees.keys()[i]
            aggregationParams = self.conf.getProducingParamsForAggregationType(dataNode.subtrees.items()[i][1])

            if childrenNode == None:
                self._dataQueue[dataType] = DataQueue(None, None, None)
                self.generateData(dataType, 2, 0)

            for aggregationType in aggregationParams:
                childrenList = OrderedDict()
                if childrenNode != None:

                    for j in range(len(childrenNode.subtrees)):
                        if dataType in childrenNode.subtrees.items()[j][1].subtrees['data'].subtrees:
                            if aggregationType in childrenNode.subtrees.items()[j][1].subtrees['data'].subtrees[dataType].subtrees:
                                childrenList[childrenNode.subtrees.items()[j][0]] = self.conf.getProducingParamsForAggregationType(childrenNode.subtrees.items()[j][1].subtrees['data'].subtrees[dataType])[aggregationType]

                self.startPublishingAggregation(aggregationParams[aggregationType], childrenList, dataType, aggregationType)
        return

    def startPublishingAggregation(self, params, childrenList, dataType, aggregationType):
        if __debug__:
            print('Start publishing for ' + dataType + '-' + aggregationType)
        
        # aggregation calculating and publishing mechanism
        publishingPrefix = Name(self._identityName).append(DATA_COMPONENT).append(dataType).append(AGGREGATION_COMPONENT).append(aggregationType)
        self._dataQueue[dataType + aggregationType] = DataQueue(params, childrenList, publishingPrefix)

        if len(childrenList.keys()) == 0:
            # TODO: make start_time optional for leaf nodes
            self._loop.call_later(int(params['producer_interval']), self.calculateAggregation, dataType, aggregationType, childrenList, int(params['start_time']), int(params['producer_interval']), publishingPrefix, True)
        else:
            # express interest for children who produce the same data and aggregation type
            for childName in childrenList.keys():
                name = Name(self._identityName).append(childName).append(DATA_COMPONENT).append(dataType).append(AGGREGATION_COMPONENT).append(aggregationType)
                interest = Interest(name)
                # if start_time is specified, we ask for data starting at start_time; 
                # if not, we ask for the right most child and go from there
                if ('start_time' in childrenList[childName]):
                    endTime = int(childrenList[childName]['start_time']) + int(childrenList[childName]['producer_interval'])
                    interest.getName().append(str(childrenList[childName]['start_time'])).append(str(endTime))
                else:
                    # TODO: For now we are playing with historical data, for each run we don't want to miss any data, thus we start with leftMost
                    interest.setChildSelector(0)
                    interest.setMustBeFresh(True)
                interest.setInterestLifetimeMilliseconds(DEFAULT_INTEREST_LIFETIME)
                if __debug__:
                    print('  Issue interest: ' + interest.getName().toUri())
                self._face.expressInterest(interest, self.onData, self.onTimeout)

        return

    # TODO: once one calculation's decided a child has not answered, we should do another calculation
    def calculateAggregation(self, dataType, aggregationType, childrenList, startTime, interval, publishingPrefix, repeat = False):
        doCalc = True
        dataList = []

        # TODO: an intermediate node cannot produce raw data for now
        if len(childrenList.keys()) != 0:
            for childName in childrenList.keys():
                dataDictKey = self.getDataDictKey(startTime, (startTime + interval), childName)
                if dataDictKey in self._dataQueue[dataType + aggregationType]._dataDict:
                    data = self._dataQueue[dataType + aggregationType]._dataDict[dataDictKey]
                    dataList.append(float(data.getContent().toRawStr()))
                else:
                    #print('Child ' + childName + ' has not replied yet')
                    doCalc = False
                    break
        else:
            for inst in self._dataQueue[dataType]._dataDict.keys():
                if int(inst) >= startTime and int(inst) < startTime + interval:
                    dataList.append(self._dataQueue[dataType]._dataDict[inst])
        if doCalc:
            content = self._aggregation.getAggregation(aggregationType, dataList)
            if content:
                publishData = Data(Name(publishingPrefix).append(str(startTime)).append(str(startTime + interval)))
                publishData.setContent(str(content))
                publishData.getMetaInfo().setFreshnessPeriod(DEFAULT_DATA_LIFETIME)
                self._keyChain.sign(publishData, self._certificateName)
                self._memoryContentCache.add(publishData)
                for childName in childrenList.keys():
                    dataDictKey = self.getDataDictKey(startTime, (startTime + interval), childName)
                    if dataDictKey in self._dataQueue[dataType + aggregationType]._dataDict:
                        del self._dataQueue[dataType + aggregationType]._dataDict[dataDictKey]
                if __debug__:
                    print("Produced: " + publishData.getName().toUri() + "; " + publishData.getContent().toRawStr())

        # repetition of this function only happens for raw data producer, otherwise calculateAggregation is called by each onData
        if repeat:
            self._loop.call_later(interval, self.calculateAggregation, dataType, aggregationType, childrenList, startTime + interval, interval, publishingPrefix, repeat)
        return

    def generateData(self, dataType, interval, startTime):
        self._dataQueue[dataType]._dataDict[str(startTime)] = random.randint(0,9)
        self._loop.call_later(interval, self.generateData, dataType, interval, startTime + interval)
        return

    def onRegisterFailed(self, prefix):
        raise RuntimeError("Register failed for prefix", prefix.toUri())

    def onVerified(self, data):
        print('Data verified: ' + data.getName().toUri())
        return

    def onVerifyFailed(self, data):
        print('Data verification failed: ' + data.getName().toUri())
        return

    def onData(self, interest, data):
        self._keyChain.verifyData(data, self.onVerified, self.onVerifyFailed)

        dataName = data.getName()
        dataQueue = None

        if __debug__:
            print("Got data: " + dataName.toUri() + "; " + data.getContent().toRawStr())
        for i in range(0, len(dataName)):
            if dataName.get(i).toEscapedString() == AGGREGATION_COMPONENT:
                dataType = dataName.get(i - 1).toEscapedString()
                aggregationType = dataName.get(i + 1).toEscapedString()
                
                startTime = int(dataName.get(i + 2).toEscapedString())
                endTime = int(dataName.get(i + 3).toEscapedString())
                childName = dataName.get(i - 3).toEscapedString()

                dataAndAggregationType = dataType + aggregationType
                
                dataDictKey = self.getDataDictKey(startTime, endTime, childName)
                dataQueue = self._dataQueue[dataAndAggregationType]
                dataQueue._dataDict[dataDictKey] = data
                break

        # TODO: check what if interval/starttime is misconfigured
        if dataQueue:
            self.calculateAggregation(dataType, aggregationType, dataQueue._childrenList, startTime, endTime - startTime, dataQueue._publishingPrefix)

        # Always ask for the next piece of data when we receive this one; assumes interval does not change; this also assumes there are no more components after endTime
        #newInterestName = dataName.getPrefix(i + 2).append(str(endTime)).append(str(endTime + (endTime - startTime)))
        
        # We don't expect aggregated data name to be continuous within our given time window, so we ask with exclusion instead
        newInterestName = dataName.getPrefix(i + 2)
        newInterest = Interest(interest)
        newInterest.setName(newInterestName)
        newInterest.setChildSelector(0)

        exclude = Exclude()
        exclude.appendAny()
        exclude.appendComponent(dataName.get(i + 2))
        newInterest.setExclude(exclude)

        self._face.expressInterest(newInterest, self.onData, self.onTimeout)
        if __debug__:
            print("  issue interest: " + interest.getName().toUri())

        return

    def onTimeout(self, interest):
        if __debug__:
            print("  interest timeout: " + interest.getName().toUri() + "; reexpress")
            pass
        self._face.expressInterest(interest, self.onData, self.onTimeout)
        return

    def stop(self):
        self._loop.stop()
        if __debug__:
            print("Stopped")
        return
    
    # This creation of dataDictKey means parent and child should not have the same name            
    @staticmethod
    def getDataDictKey(startTime, endTime, childName):
        return str(startTime) + '/' + str(endTime) + '/' + childName

##
# 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
Exemplo n.º 13
0
class NaiveEDLParserAndPublisher(object):
    def __init__(self, applyEDLAdjustment=True):
        # prepare trollius logging
        self.prepareLogging()

        self._events = dict()
        self._running = False
        self._applyEDLAdjustment = applyEDLAdjustment

        # NDN related variables
        self._loop = asyncio.get_event_loop()
        self._face = ThreadsafeFace(self._loop)

        # Use the system default key chain and certificate name to sign commands.
        self._keyChain = KeyChain()
        self._keyChain.setFace(self._face)
        self._certificateName = self._keyChain.getDefaultCertificateName()
        self._face.setCommandSigningInfo(self._keyChain, self._certificateName)
        self._memoryContentCache = MemoryContentCache(self._face)

        # Publishing parameters conf  iguration
        self._translationServiceUrl = "http://the-archive.la/losangeles/services/get-youtube-url"
        self._namePrefixString = "/ndn/edu/ucla/remap/test/edl/"

        self._dataLifetime = 2000
        self._publishBeforeSeconds = 3
        self._translateBeforeSeconds = 60
        self._currentIdx = 0

        # Youtube related variables:
        # Channel Global song: UCSMJaKICZKXkpvr7Gj8pPUg
        # Channel Los Angeles: UCeuQoBBzMW6SWkxd8_1I8NQ
        # self._channelID = 'UCSMJaKICZKXkpvr7Gj8pPUg'
        self._channelID = "UCSMJaKICZKXkpvr7Gj8pPUg"
        self._accessKey = "AIzaSyCe8t7PnmWjMKZ1gBouhP1zARpqNwHAs0s"
        # queryStr = 'https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails,statistics,status&key=' + apiKey + '&id='
        # Video query example
        # https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails,statistics,status&key=AIzaSyDUY_AX1iJQcwCW1mASEp5GcLtq1V9BM1Q&id=_ebELPKANxo
        # Channel query example
        # https://www.googleapis.com/youtube/v3/search?key=AIzaSyCe8t7PnmWjMKZ1gBouhP1zARpqNwHAs0s&channelId=UCSMJaKICZKXkpvr7Gj8pPUg&part=snippet,id&order=date&maxResults=20
        self._videoUrlDict = dict()

        self._edlAdjustmentDict = dict()
        return

    def getClipUrlOAuth(self):
        self._videoUrlDict = dict((k.lower(), v) for k, v in getAllVideosFromChannel().iteritems())

    # Old getClipUrl function that looks at the public Youtube channel without using Python API
    def getClipUrl(self, nextPageToken=None):
        options = {"part": "snippet,id", "order": "date", "maxResults": "20"}
        if nextPageToken is not None:
            options["pageToken"] = nextPageToken
        prefix = "https://www.googleapis.com/youtube/v3/search?"

        queryUrl = prefix + "key=" + self._accessKey + "&channelId=" + self._channelID
        for item in options:
            queryUrl += "&" + item + "=" + options[item]
        result = json.loads(urllib.urlopen(queryUrl).read())
        for item in result["items"]:
            if "snippet" in item and "id" in item and "videoId" in item["id"]:
                self._videoUrlDict[item["snippet"]["title"].lower()] = item["id"]["videoId"]
            else:
                print("Unexpected JSON from youtube channel query")
        if "nextPageToken" in result:
            self.getClipUrl(result["nextPageToken"])
        else:
            if __debug__:
                print("Building videoUrl dict finished; number of entries: " + str(len(self._videoUrlDict)))
                # for item in self._videoUrlDict:
                #  print("* " + item)
        return

    def parse(self, fileName):
        isEventBegin = False
        lastEventID = -1
        with open(fileName, "r") as edlFile:
            for line in edlFile:
                if isEventBegin:
                    components = line.split()
                    try:
                        eventID = int(components[0])
                    except ValueError:
                        print("Cannot cast " + components[0] + " to eventID")
                        continue
                    # We seem to have a fixed number of components here;
                    # reference: http://www.edlmax.com/maxguide.html
                    reelName = components[1]
                    channel = components[2]
                    trans = components[3]

                    timeComponentsIdx = len(components) - 4

                    srcStartTime = components[timeComponentsIdx]
                    srcEndTime = components[timeComponentsIdx + 1]
                    dstStartTime = components[timeComponentsIdx + 2]
                    dstEndTime = components[timeComponentsIdx + 3]

                    self._events[eventID] = json.loads(
                        '{ \
              "event_id": "%s", \
              "reel_name": "%s", \
              "channel": "%s", \
              "trans": "%s", \
              "src_start_time": "%s", \
              "src_end_time": "%s", \
              "dst_start_time": "%s", \
              "dst_end_time": "%s", \
              "src_url": "%s", \
              "translated": "%s", \
              "clipName": "%s", \
              "ytPresent": "%s" \
             }'
                        % (
                            str(eventID),
                            reelName,
                            channel,
                            trans,
                            srcStartTime,
                            srcEndTime,
                            dstStartTime,
                            dstEndTime,
                            "none",
                            "none",
                            "n/a",
                            "n/a",
                        )
                    )

                    isEventBegin = False
                    lastEventID = eventID
                elif re.match(r"\s+", line) is not None or line == "":
                    isEventBegin = True
                elif lastEventID > 0:
                    # Skipping events that do not have right offset
                    if not eventID in self._events:
                        print("Line skipped because of missing start time adjustment")
                        continue

                    fromClipNameMatch = re.match(r"\* FROM CLIP NAME: ([^\n]*)\n", line)
                    if fromClipNameMatch is not None:
                        clipName = fromClipNameMatch.group(1).strip()
                        parsedClipName = clipName.lower().replace("_", " ").replace("-", " ")

                        if self._applyEDLAdjustment:
                            if clipName in self._edlAdjustmentDict:
                                startTimeAdjusted = self.getTimeMinus(
                                    self._edlAdjustmentDict[clipName].split(":"),
                                    self._events[eventID]["src_start_time"].split(":"),
                                )
                                endTimeAdjusted = self.getTimeMinus(
                                    self._edlAdjustmentDict[clipName].split(":"),
                                    self._events[eventID]["src_end_time"].split(":"),
                                )
                                self._events[eventID]["src_start_time"] = startTimeAdjusted
                                self._events[eventID]["src_end_time"] = endTimeAdjusted

                                # Skipping events that do not have right offset
                                if startTimeAdjusted == "" or endTimeAdjusted == "":
                                    print(
                                        clipName + " : " + startTimeAdjusted,
                                        " start time incorrect; event " + str(eventID) + " ignored",
                                    )
                                    del self._events[eventID]
                                    continue
                            else:
                                # Skipping events that do not have right offset
                                print(
                                    "Warning: EDL adjustment not found for "
                                    + clipName
                                    + "; event "
                                    + str(eventID)
                                    + " ignored"
                                )
                                del self._events[eventID]
                                continue

                        self._events[eventID]["clipName"] = parsedClipName
                        # We don't do audio (only .wav or .mp3) for now
                        if parsedClipName.endswith(".wav") or parsedClipName.endswith(".mp3"):
                            continue
                        else:
                            parsedClipName = (" ").join(parsedClipName.split(".")[:-1])
                            # print(parsedClipName)
                        if parsedClipName in self._videoUrlDict:
                            # we assume one src_url from one FROM CLIP NAME for now
                            self._events[eventID]["src_url"] = (
                                "https://www.youtube.com/watch?v=" + self._videoUrlDict[parsedClipName]
                            )
                            self._events[eventID]["ytPresent"] = "YES"
                            print("src_url is " + self._events[eventID]["src_url"])
                        else:
                            self._events[eventID]["ytPresent"] = "NO"
                            print("Warning: file not found in Youtube channel: " + clipName)
                    else:
                        if "payload" not in self._events[eventID]:
                            self._events[eventID]["payload"] = [line]
                        else:
                            self._events[eventID]["payload"].append(line)

    @asyncio.coroutine
    def startPublishing(self):
        if len(self._events) == 0:
            return
        elif not self._running:
            self._memoryContentCache.registerPrefix(
                Name(self._namePrefixString), self.onRegisterFailed, self.onDataNotFound
            )
            startTime = time.time()

            latestEventTime = 0
            lastEventID = 0
            for event_id in sorted(self._events):
                timeStrs = self._events[event_id]["dst_start_time"].split(":")
                publishingTime = self.getScheduledTime(timeStrs, self._publishBeforeSeconds)
                translationTime = self.getScheduledTime(timeStrs, self._translateBeforeSeconds)
                if publishingTime > latestEventTime:
                    latestEventTime = publishingTime
                self._loop.call_later(translationTime, self.translateUrl, event_id)
                self._loop.call_later(publishingTime, self.publishData, event_id)
                lastEventID = event_id

            # append arbitrary 'end' data
            lastEventID = lastEventID + 1
            self._events[lastEventID] = json.loads(
                '{ \
        "event_id": "%s", \
        "src_url": "%s", \
        "translated": "%s" \
      }'
                % (str(lastEventID), "end", "not-required")
            )
            startTime = self.getScheduledTime(self._events[lastEventID - 1]["src_start_time"].split(":"), 0)
            endTime = self.getScheduledTime(self._events[lastEventID - 1]["src_end_time"].split(":"), 0)
            print("scheduled end " + str(endTime - startTime) + " sec from now")
            self._loop.call_later(latestEventTime + 1, self.publishData, lastEventID)
            self._loop.call_later(latestEventTime + 2 + (endTime - startTime), self._loop.stop)

            self._running = True

    def translateUrl(self, idx):
        queryUrl = self._translationServiceUrl
        timeStrs = self._events[idx]["src_start_time"].split(":")

        # we don't have the video from Youtube
        if self._events[idx]["src_url"] == "none":
            # print("no video from Youtube")
            # we still publish the data even if src_url is "none", to maintain consecutive sequence numbers
            self._events[idx]["translated"] = "non-existent"
            return

        serviceUrl = self._events[idx]["src_url"]  # + "&t=" + str(self.timeToSeconds(timeStrs)) + "s"

        values = {"url": serviceUrl, "fetchIfNotExist": "true"}

        data = urllibparse.urlencode(values)
        req = urllib.Request(queryUrl, data)
        # This synchronous request might block the execution of publishData; should be changed later
        response = urllib.urlopen(req)
        videoUrl = response.read()

        self._events[idx]["ori_url"] = serviceUrl
        self._events[idx]["src_url"] = videoUrl

        if self._events[idx]["translated"] == "publish":
            # We already missed the scheduled publishing time; should publish as soon as translation finishes
            self.publishData(idx)
        else:
            self._events[idx]["translated"] = "translated"
        return

    def publishData(self, idx):
        # Translation of the video URL has finished by the time of the publishData call;
        # if not, we set translated to "publish"; this is data race free since translateUrl and publishData are scheduled in the same thread
        if self._events[idx]["translated"] != "none":
            # Order published events sequence numbers by start times in destination
            data = Data(Name(self._namePrefixString + str(self._currentIdx)))

            data.setContent(json.dumps(self._events[idx]))
            data.getMetaInfo().setFreshnessPeriod(self._dataLifetime)
            self._keyChain.sign(data, self._certificateName)
            self._memoryContentCache.add(data)
            self._currentIdx += 1
            if __debug__:
                eventId = str(self._events[idx]["event_id"])
                channel = str(self._events[idx]["channel"])
                srcUrl = str(self._events[idx]["src_url"])
                clipName = str(self._events[idx]["clipName"])
                ytPresent = str(self._events[idx]["ytPresent"])
                clipStartTime = str(self._events[idx]["dst_start_time"])
                clipEndTime = str(self._events[idx]["dst_end_time"])
                print(
                    str(time.time())
                    + " Added event ["
                    + eventId
                    + "-"
                    + channel
                    + "|"
                    + clipName
                    + " YT:"
                    + ytPresent
                    + " "
                    + srcUrl[0:30]
                    + "... "
                    + clipStartTime
                    + "-"
                    + clipEndTime
                    + "] ("
                    + data.getName().toUri()
                    + ")"
                )
        else:
            self._events[idx]["translated"] = "publish"

    def timeToSeconds(self, timeStrs):
        seconds = int(timeStrs[2])
        minutes = int(timeStrs[1])
        hours = int(timeStrs[0])
        ret = hours * 3600 + minutes * 60 + seconds
        return ret

    def getTimeMinus(self, timeStrs1, timeStrs2):
        frameNumber = int(timeStrs1[3])
        seconds = int(timeStrs1[2])
        minutes = int(timeStrs1[1])
        hours = int(timeStrs1[0])

        frameNumber2 = int(timeStrs2[3]) - frameNumber
        seconds2 = int(timeStrs2[2]) - seconds
        minutes2 = int(timeStrs2[1]) - minutes
        hours2 = int(timeStrs2[0]) - hours

        if frameNumber2 < 0:
            # frame rate assumption
            frameNumber2 = 30 + frameNumber2
            seconds2 = seconds2 - 1

        if seconds2 < 0:
            seconds2 = 60 + seconds2
            minutes2 = minutes2 - 1

        if minutes2 < 0:
            minutes2 = 60 + minutes2
            hours2 = hours2 - 1

        if hours2 < 0:
            print("Warning: time minus smaller than 0")
            return ""

        # Arbitrary guard of start times that are off
        if hours2 > 1 or minutes2 > 1:
            return ""

        return ":".join([str(hours2), str(minutes2), str(seconds2), str(frameNumber2)])

    def getScheduledTime(self, timeStrs, beforeSeconds):
        frameNumber = int(timeStrs[3])
        seconds = int(timeStrs[2])
        minutes = int(timeStrs[1])
        hours = int(timeStrs[0])
        ret = hours * 3600 + minutes * 60 + seconds - beforeSeconds
        return 0 if ret < 0 else ret

    def onRegisterFailed(self, prefix):
        raise RuntimeError("Register failed for prefix", prefix.toUri())

    def onDataNotFound(self, prefix, interest, face, interestFilterId, filter):
        # print('Data not found for interest: ' + interest.getName().toUri())
        return

    #############################
    # 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

    ############################
    def loadEDLAdjustment(self, csvFile):
        with open(csvFile, "rb") as csvfile:
            reader = csv.reader(csvfile, delimiter=",", quotechar="|")
            for row in reader:
                self._edlAdjustmentDict[row[3]] = row[1]
Exemplo n.º 14
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)
Exemplo n.º 15
0
def startFileSync():
    global EXIT
    screenName = promptAndInput("Enter your name: ")

    defaultHubPrefix = "ndn/no/ntnu"
    hubPrefix = promptAndInput("Enter your hub prefix [" + defaultHubPrefix + "]: ")
    if hubPrefix == "":
        hubPrefix = defaultHubPrefix

    defaultpkList = "pklist"
    pkListName = promptAndInput("Sync with public key list [" + defaultpkList + "]: ")
    if pkListName == "":
        pkListName = defaultpkList

    host = "localhost" 
    # host = "129.241.208.115"
    logging.info("Connecting to " + host + ", public Key List: " + pkListName + ", Name: " + screenName)

    # Set up the key chain.
    face = Face(host)

    identityStorage = MemoryIdentityStorage()
    privateKeyStorage = MemoryPrivateKeyStorage()
    # privateKeyStorage = OSXPrivateKeyStorage()
    identityManager = IdentityManager(identityStorage, privateKeyStorage)
    # identityManager.createIdentity(Name("/name/"))
    keyChain = KeyChain(identityManager, NoVerifyPolicyManager())
    keyChain.setFace(face)
    
    keyName = Name("/testname/DSK-123")
    certificateName = keyName.getSubName(0, keyName.size() - 1).append("KEY").append(keyName[-1]).append("ID-CERT").append("0")
    identityStorage.addKey(keyName, KeyType.RSA, Blob(DEFAULT_RSA_PUBLIC_KEY_DER))
    privateKeyStorage.setKeyPairForKeyName(keyName, KeyType.RSA, DEFAULT_RSA_PUBLIC_KEY_DER, DEFAULT_RSA_PRIVATE_KEY_DER)
    face.setCommandSigningInfo(keyChain, certificateName)

    # keyName = Name("/ndn/no/ntnu/stud/haakonmo/ksk-1426537450856")
    # certificateName = Name("/ndn/no/ntnu/KEY/stud/haakonmo/ksk-1426537450856/ID-CERT/%FD%00%00%01L%26%D9E%92")

    # publicKey = privateKeyStorage.getPublicKey(keyName)
    # identityStorage.addKey(keyName, publicKey.getKeyType(), publicKey.getKeyDer())
    # face.setCommandSigningInfo(keyChain, certificateName)

    # print(identityStorage.getCertificate(certificateName))
    # print(identityStorage.getKey(keyName))

    path = './files/'
    fileSyncer = FileSync(screenName, pkListName, Name(hubPrefix), face, keyChain, certificateName, path)
    fileSyncer.initial()    

    fileWatcher = FileWatch(fileSyncer, path)
    # TODO:
    #    1. Generate new public key or use existing?
    #    2. Watch new public key
    #    3. sendUpdatedPublicKey if key is changed
    #    4. Download and store other keys
    #    5. Verify data packet
    while not EXIT:
        isReady, _, _ = select.select([sys.stdin], [], [], 0)
        if len(isReady) != 0:
            input = promptAndInput("")
            if input == "leave" or input == "exit":
                EXIT = True
                break
            #fileSyncer.onFileUpdate(input)

        fileSyncer.face.processEvents()
        # We need to sleep for a few milliseconds so we don't use 100% of the CPU.
        time.sleep(0.01)

    fileSyncer.unsubscribe()
    startTime = FileSync.getNowMilliseconds()
    while True:
        if FileSync.getNowMilliseconds() - startTime >= 1000.0:
            break

        face.processEvents()
        time.sleep(0.01)

    # Shutdown all services
    fileSyncer.face.shutdown()
    fileWatcher.stopFileWatch()
Exemplo n.º 16
0
class BaseNode(object):
    """
    This class contains methods/attributes common to both node and controller.
    """
    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 = BasicIdentityStorage()

        self._identityManager = IdentityManager(self._identityStorage, FilePrivateKeyStorage())
        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


##
# 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 as e:
            # zhehao: in the case of producer's /localhop prefixes, the default key is not defined in ndnsec-public-info.db
            certName = self._keyChain.createIdentityAndCertificate(self._policyManager.getDeviceIdentity())
            #certName = self._keyChain.getDefaultCertificateName()
            #print(certName.toUri())
        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()
        
        if (self.faceTransport == None or self.faceTransport == ''):
            self.face = ThreadsafeFace(self.loop)
        else:
            self.face = ThreadsafeFace(self.loop, self.faceTransport, self.faceConn)
        
        self.face.setCommandSigningInfo(self._keyChain, self.getDefaultCertificateName())
        
        self._keyChain.setFace(self.face)

        self._isStopped = False
        self.beforeLoopStart()
        
        try:
            self.loop.run_forever()
        except Exception as e:
            self.log.exception(exc_info=True)
        finally:
            self.stop()

    def stop(self):
        """
        Stops the node, taking it off the network
        """
        self.log.info("Shutting down")
        self._isStopped = True 
        self.loop.stop()
        
###
# 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, sign=True):
        """
        Reply to an interest with a data packet, optionally signing it.
        :param pyndn.Data data: The response data packet
        :param boolean sign: (optional, default=True) Whether the response must be signed. 
        """
        if sign:
            self.signData(data)
        self.face.putData(data)

###
# 
# 
##
    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.faceTransport != None and self.faceConn != None:
            self.log.warn("Explicit face transport and connectionInfo: Could not register {}; expect a manual or autoreg on the other side.".format(prefix.toUri()))
        elif 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.info("Prefix registration failed")
            self.stop()

    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 getSerial():
        """
        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
        """
        try:
            if PLATFORM == "raspberry pi":
                with open('/proc/cpuinfo') as f:
                    for line in f:
                        if line.startswith('Serial'):
                            return line.split(':')[1].strip()
            else:
                return "todo"
        except NameError:
            return "todo"
Exemplo n.º 17
0
class Bootstrap(object):
    """
    Create a Bootstrap object. Bootstrap object provides interface for setting up KeyChain, default certificate name;
    (as a producer) requesting publishing authorization from controller; and (as a consumer) keeping track of changes

    :param face: the face for communicating with a local / remote forwarder
    :type face: ThreadsafeFace

    TODO: support Face as well as ThreadsafeFace
    """
    def __init__(self, face):
        self._defaultIdentity = None
        self._defaultCertificateName = None
        
        self._controllerName = None
        self._controllerCertificate = None

        self._applicationName = ""

        self._identityManager = IdentityManager(BasicIdentityStorage(), FilePrivateKeyStorage())

        self._policyManager = ConfigPolicyManager()
        self._policyManager.config.read("validator \n \
            {                                      \n \
              rule                                 \n \
              {                                    \n \
                id \"initial rule\"                \n \
                for data                           \n \
                checker                            \n \
                {                                  \n \
                  type hierarchical                \n \
                }                                  \n \
              }                                    \n \
            }", "initial-schema")
        # keyChain is what we return to the application after successful setup
        # TODO: should we separate keyChain from internal KeyChain used to verify trust schemas?
        self._keyChain = KeyChain(self._identityManager, self._policyManager)

        self._face = face
        # setFace for keyChain or else it won't be able to express interests for certs
        self._keyChain.setFace(self._face)
        self._certificateContentCache = MemoryContentCache(face)
        
        self._trustSchemas = dict()

###############################################
# Initial keyChain and defaultCertificate setup
###############################################
    def setupDefaultIdentityAndRoot(self, defaultIdentityOrFileName, signerName = None, onSetupComplete = None, onSetupFailed = None):
        """
        Sets up the keyChain, default key name and certificate name according to given 
        configuration. If successful, this KeyChain and default certificate name will be 
        returned to the application, which can be passed to instances like Consumer, Discovery, etc

        :param defaultIdentityOrFileName: if str, the name of the configuration file; if Name, 
          the default identity name of this IoT node. The node will use the default keys and 
          certificate of that identity name.
        :type defaultIdentityOrFileName: Name or str
        :param signerName: (optional) the expected signing identity of the certificate
        :type signerName: Name
        :param onSetupComplete: (optional) onSetupComplete(Name, KeyChain) will be called if 
          set up's successful
        :type onSetupComplete: function object
        :param onSetupFailed: (optional) onSetupFailed(msg) will be called if setup fails
        :type onSetupFailed: function object
        """
        def helper(identityName, signerName):
            try:
                self._defaultIdentity = identityName
                self._defaultCertificateName = self._identityManager.getDefaultCertificateNameForIdentity(self._defaultIdentity)
                self._defaultKeyName = self._identityManager.getDefaultKeyNameForIdentity(identityName)
            except SecurityException:
                msg = "Identity " + identityName.toUri() + " in configuration does not exist. Please configure the device with this identity first."
                if onSetupFailed:
                    onSetupFailed(msg)
                return

            if not self._defaultCertificateName:
                msg = "Unable to get default certificate name for identity " + identityName.toUri() + ". Please configure the device with this identity first."
                if onSetupFailed:
                    onSetupFailed(msg)
                return

            if not self._defaultKeyName:
                msg = "Unable to get default key name for identity " + identityName.toUri() + ". Please configure the device with this identity first."
                if onSetupFailed:
                    onSetupFailed(msg)
                return
            
            # Note we'll not be able to issue face commands before this point
            self._face.setCommandSigningInfo(self._keyChain, self._defaultCertificateName)
            # Serve our own certificate
            self._certificateContentCache.registerPrefix(Name(self._defaultCertificateName).getPrefix(-1), self.onRegisterFailed)
            self._certificateContentCache.add(self._keyChain.getCertificate(self._defaultCertificateName))

            actualSignerName = self._keyChain.getCertificate(self._defaultCertificateName).getSignature().getKeyLocator().getKeyName()    
            if not signerName:
                print "Deriving from " + actualSignerName.toUri() + " for controller name"
            else:
                if signerName and actualSignerName.toUri() != signerName.toUri():
                    msg = "Configuration signer names mismatch: expected " + signerName.toUri() + "; got " + actualSignerName.toUri()
                    print msg
                    if onSetupFailed:
                        onSetupFailed(msg)

            self._controllerName = self.getIdentityNameFromCertName(actualSignerName)
            print "Controller name: " + self._controllerName.toUri()

            try:
                self._controllerCertificate = self._keyChain.getCertificate(self._identityManager.getDefaultCertificateNameForIdentity(self._controllerName))
                
                # TODO: this does not seem a good approach, implementation-wise and security implication
                self._policyManager._certificateCache.insertCertificate(self._controllerCertificate)
                if onSetupComplete:
                    onSetupComplete(Name(self._defaultCertificateName), self._keyChain)
            except SecurityException as e:
                print "don't have controller certificate " + actualSignerName.toUri() + " yet"
                controllerCertInterest = Interest(Name(actualSignerName))
                controllerCertInterest.setInterestLifetimeMilliseconds(4000)
                
                controllerCertRetries = 3

                self._face.expressInterest(controllerCertInterest, 
                  lambda interest, data: self.onControllerCertData(interest, data, onSetupComplete, onSetupFailed), 
                  lambda interest: self.onControllerCertTimeout(interest, onSetupComplete, onSetupFailed, controllerCertRetries))
            return

        if isinstance(defaultIdentityOrFileName, basestring):
            confObj = self.processConfiguration(defaultIdentityOrFileName)
            if "identity" in confObj:
                if confObj["identity"] == "default":
                    # TODO: handling the case where no default identity is present
                    defaultIdentity = self._keyChain.getDefaultIdentity()
                else:
                    defaultIdentity = Name(confObj["identity"])
            else:
                defaultIdentity = self._keyChain.getDefaultIdentity()

            # TODO: handling signature with direct bits instead of keylocator keyname
            if "signer" in confObj:    
                if confObj["signer"] == "default":
                    signerName = None
                else:
                    signerName = Name(confObj["signer"])
            else:
                signerName = None
                print "Deriving from " + signerName.toUri() + " for controller name"

            helper(defaultIdentity, signerName)
        else:
            if isinstance(defaultIdentityOrFileName, Name):
                helper(defaultIdentityOrFileName, signerName)
            else:
                raise RuntimeError("Please call setupDefaultIdentityAndRoot with identity name and root key name")
        return

    def onControllerCertData(self, interest, data, onSetupComplete, onSetupFailed):
        # TODO: verification rule for received self-signed cert. 
        # So, if a controller comes masquerading in at this point with the right name, it is problematic. Similar with ndn-pi's implementation
        self._controllerCertificate = IdentityCertificate(data)
        # insert root certificate so that we could verify initial trust schemas
        # TODO: this does not seem a good approach, implementation-wise and security implication
        self._keyChain.getPolicyManager()._certificateCache.insertCertificate(self._controllerCertificate)
        try:
            self._identityManager.addCertificate(self._controllerCertificate)
        except SecurityException as e:
            print str(e)
        for schema in self._trustSchemas:
            # TODO: remove the concept of pending-schema
            if "pending-schema" in self._trustSchemas[schema]:
                self._keyChain.verifyData(self._trustSchemas[schema]["pending-schema"], self.onSchemaVerified, self.onSchemaVerificationFailed)
        if onSetupComplete:
            onSetupComplete(Name(self._defaultCertificateName), self._keyChain)
        return

    def onControllerCertTimeout(self, interest, onSetupComplete, onSetupFailed, controllerCertRetries):
        print "Controller certificate interest times out"
        newInterest = Interest(interest)
        newInterest.refreshNonce()
        if controllerCertRetries == 0:
            if onSetupFailed:
                onSetupFailed("Controller certificate interest times out")
            else:
                print "Set up failed: controller certificate interest times out"
        else:
            self._face.expressInterest(newInterest, 
              lambda interest, data: self.onControllerCertData(interest, data, onSetupComplete, onSetupFailed), 
              lambda interest: self.onControllerCertTimeout(interest, onSetupComplete, onSetupFailed, controllerCertRetries - 1))
        return

#########################################################
# Handling application consumption (trust schema updates)
#########################################################
    # TODO: if trust schema gets over packet size limit, segmentation
    def startTrustSchemaUpdate(self, appPrefix, onUpdateSuccess = None, onUpdateFailed = None):
        """
        Starts trust schema update for under an application prefix: initial 
        interest asks for the rightMostChild, and later interests are sent 
        with previous version excluded. Each verified trust schema will trigger
        onUpdateSuccess and update the ConfigPolicyManager for the keyChain
        in this instance, and unverified ones will trigger onUpdateFailed.

        The keyChain and trust anchor should be set up using setupDefaultIdentityAndRoot
        before calling this method. 

        :param appPrefix: the prefix to ask trust schema for. (interest name: /<prefix>/_schema)
        :type appPrefix: Name
        :param onUpdateSuccess: (optional) onUpdateSuccess(trustSchemaStr, isInitial) is 
          called when update succeeds
        :type onUpdateSuccess: function object
        :param onUpdateFailed: (optional) onUpdateFailed(msg) is called when update fails
        :type onUpdateFailed: function object
        """
        namespace = appPrefix.toUri()
        if namespace in self._trustSchemas:
            if self._trustSchemas[namespace]["following"] == True:
                print "Already following trust schema under this namespace!"
                return
            self._trustSchemas[namespace]["following"] = True
        else:
            self._trustSchemas[namespace] = {"following": True, "version": 0, "is-initial": True}

        initialInterest = Interest(Name(namespace).append("_schema"))
        initialInterest.setChildSelector(1)
        self._face.expressInterest(initialInterest, 
          lambda interest, data: self.onTrustSchemaData(interest, data, onUpdateSuccess, onUpdateFailed), 
          lambda interest: self.onTrustSchemaTimeout(interest, onUpdateSuccess, onUpdateFailed))
        return

    def stopTrustSchemaUpdate(self):
        print "stopTrustSchemaUpdate not implemented"
        return

    def onSchemaVerified(self, data, onUpdateSuccess, onUpdateFailed):
        print "trust schema verified: " + data.getName().toUri()
        version = data.getName().get(-1)
        namespace = data.getName().getPrefix(-2).toUri()
        if not (namespace in self._trustSchemas):
            print "unexpected: received trust schema for application namespace that's not being followed; malformed data name?"
            return

        if version.toVersion() <= self._trustSchemas[namespace]["version"]:
            msg = "Got out-of-date trust schema"
            print msg
            if onUpdateFailed:
                onUpdateFailed(msg)
            return

        self._trustSchemas[namespace]["version"] = version.toVersion()
        
        if "pending-schema" in self._trustSchemas[namespace] and self._trustSchemas[namespace]["pending-schema"].getName().toUri() == data.getName().toUri():
            # we verified a pending trust schema, don't need to keep that any more
            del self._trustSchemas[namespace]["pending-schema"]

        self._trustSchemas[namespace]["trust-schema"] = data.getContent().toRawStr()
        print self._trustSchemas[namespace]["trust-schema"]

        # TODO: what about trust schema for discovery, is discovery its own application?
        newInterest = Interest(Name(data.getName()).getPrefix(-1))
        newInterest.setChildSelector(1)
        exclude = Exclude()
        exclude.appendAny()
        exclude.appendComponent(version)
        newInterest.setExclude(exclude)
        self._face.expressInterest(newInterest, 
          lambda interest, data: self.onTrustSchemaData(interest, data, onUpdateSuccess, onUpdateFailed), 
          lambda interest: self.onTrustSchemaTimeout(interest, onUpdateSuccess, onUpdateFailed))

        # Note: this changes the verification rules for root cert, future trust schemas as well; ideally from the outside this doesn't have an impact, but do we want to avoid this?
        # Per reset function in ConfigPolicyManager; For now we don't call reset as we still want root cert in our certCache, instead of asking for it again (when we want to verify) each time we update the trust schema
        self._policyManager.config = BoostInfoParser()
        self._policyManager.config.read(self._trustSchemas[namespace]["trust-schema"], "updated-schema")
        
        if onUpdateSuccess:
            onUpdateSuccess(data.getContent().toRawStr(), self._trustSchemas[namespace]["is-initial"])
        self._trustSchemas[namespace]["is-initial"] = False
        return

    def onSchemaVerificationFailed(self, data, reason, onUpdateSuccess, onUpdateFailed):
        print "trust schema verification failed: " + reason
        namespace = data.getName().getPrefix(-2).toUri()
        if not (namespace in self._trustSchemas):
            print "unexpected: received trust schema for application namespace that's not being followed; malformed data name?"
            return
        
        newInterest = Interest(Name(data.getName()).getPrefix(-1))
        newInterest.setChildSelector(1)
        exclude = Exclude()
        exclude.appendAny()
        exclude.appendComponent(Name.Component.fromVersion(self._trustSchemas[namespace]["version"]))
        newInterest.setExclude(exclude)
        # Don't immediately ask for potentially the same content again if verification fails
        self._face.callLater(4000, lambda : 
          self._face.expressInterest(newInterest, 
            lambda interest, data: self.onTrustSchemaData(interest, data, onUpdateSuccess, onUpdateFailed), 
            lambda interest: self.onTrustSchemaTimeout(interest, onUpdateSuccess, onUpdateFailed)))
        return

    def onTrustSchemaData(self, interest, data, onUpdateSuccess, onUpdateFailed):
        print("Trust schema received: " + data.getName().toUri())
        namespace = data.getName().getPrefix(-2).toUri()
        # Process newly received trust schema
        if not self._controllerCertificate:
            # we don't yet have the root certificate fetched, so we store this cert for now
            print "Controller certificate not yet present, verify once it's in place"
            self._trustSchemas[namespace]["pending-schema"] = data
        else:
            # we veriy the received trust schema, should we use an internal KeyChain instead?
            self._keyChain.verifyData(data, 
              lambda data: self.onSchemaVerified(data, onUpdateSuccess, onUpdateFailed), 
              lambda data, reason: self.onSchemaVerificationFailed(data, reason, onUpdateSuccess, onUpdateFailed))

        return

    def onTrustSchemaTimeout(self, interest, onUpdateSuccess, onUpdateFailed):
        print("Trust schema interest times out: " + interest.getName().toUri())
        newInterest = Interest(interest)
        newInterest.refreshNonce()
        self._face.expressInterest(newInterest, 
          lambda interest, data: self.onTrustSchemaData(interest, data, onUpdateSuccess, onUpdateFailed), 
          lambda interest: self.onTrustSchemaTimeout(interest, onUpdateSuccess, onUpdateFailed))        
        return

###############################################
# Handling application producing authorizations
###############################################
    # Wrapper for sendAppRequest, fills in already configured defaultCertificateName
    def requestProducerAuthorization(self, dataPrefix, appName, onRequestSuccess = None, onRequestFailed = None):
        """
        Requests producing authorization for a data prefix: commandInterest is sent out 
        to the controller, using /<controller identity>/requests/<encoded-application-parameters>/<signed-interest-suffix>
        where encoded-application-parameters is a ProtobufTlv encoding of 
        {appPrefix, certificateName, appName}

        The keyChain, trust anchor and controller name should be set up using 
        setupDefaultIdentityAndRoot before calling this method.

        :param dataPrefix: the prefix to request publishing for
        :type dataPrefix: Name
        :param appName: the application name to request publishing for
        :type appName: str
        :param onRequestSuccess: (optional) onRequestSuccess() is called when a valid response
          if received for the request
        :type onRequestSuccess: function object
        :param onRequestFailed: (optional) onRequestFailed(msg) is called when request fails
        :type onRequestFailed: function object
        """
        # TODO: update logic on this part, should the presence of default certificate name be mandatory? 
        # And allow application developer to send app request to a configured root/controller?
        if not self._defaultCertificateName:
            raise RuntimeError("Default certificate is missing! Try setupDefaultIdentityAndRoot first?")
            return
        self.sendAppRequest(self._defaultCertificateName, dataPrefix, appName, onRequestSuccess, onRequestFailed)

    def sendAppRequest(self, certificateName, dataPrefix, applicationName, onRequestSuccess, onRequestFailed):
        message = AppRequestMessage()

        for component in range(certificateName.size()):
            message.command.idName.components.append(certificateName.get(component).toEscapedString())
        for component in range(dataPrefix.size()):
            message.command.dataPrefix.components.append(dataPrefix.get(component).toEscapedString())
        message.command.appName = applicationName

        paramComponent = ProtobufTlv.encode(message)

        requestInterest = Interest(Name(self._controllerName).append("requests").append(paramComponent))

        requestInterest.setInterestLifetimeMilliseconds(4000)
        self._face.makeCommandInterest(requestInterest)
        
        appRequestTimeoutCnt = 3

        self._face.expressInterest(requestInterest, 
          lambda interest, data : self.onAppRequestData(interest, data, onRequestSuccess, onRequestFailed), 
          lambda interest : self.onAppRequestTimeout(interest, onRequestSuccess, onRequestFailed, appRequestTimeoutCnt))
        print "Application publish request sent: " + requestInterest.getName().toUri()
        return

    def onAppRequestData(self, interest, data, onRequestSuccess, onRequestFailed):
        print "Got application publishing request data"
        def onVerified(data):
            responseObj = json.loads(data.getContent().toRawStr())
            if responseObj["status"] == "200":
                if onRequestSuccess:
                    onRequestSuccess()
                else:
                    print "onSetupComplete"
            else:
                print "Verified content: " + data.getContent().toRawStr()
                if onRequestFailed:
                    onRequestFailed(data.getContent().toRawStr())
        def onVerifyFailed(data, reason):
            msg = "Application request response verification failed: " + reason
            print msg
            if onRequestFailed:
                onRequestFailed(msg)

        self._keyChain.verifyData(data, onVerified, onVerifyFailed)
        return

    def onAppRequestTimeout(self, interest, onSetupComplete, onSetupFailed, appRequestTimeoutCnt):
        print "Application publishing request times out"
        newInterest = Interest(interest)
        newInterest.refreshNonce()

        if appRequestTimeoutCnt == 0:
            if onSetupFailed:
                onSetupFailed("Application publishing request times out")
            else:
                print "Setup failed: application publishing request times out"
        else:
            self._face.expressInterest(newInterest,
              lambda interest, data : self.onAppRequestData(interest, data, onSetupComplete, onSetupFailed), 
              lambda interest : self.onAppRequestTimeout(interest, onSetupComplete, onSetupFailed, appRequestTimeoutCnt - 1))
        return

###############################################
# Helper functions
###############################################
    def onRegisterFailed(self, prefix):
        print("register failed for prefix " + prefix.getName().toUri())
        return

    def processConfiguration(self, confFile):
        config = BoostInfoParser()
        config.read(confFile)

        # TODO: handle missing configuration, refactor dict representation
        confObj = dict()
        try:
            confObj["identity"] = config["application/identity"][0].value
            confObj["signer"] = config["application/signer"][0].value
        except KeyError as e:
            msg = "Missing key in configuration: " + str(e)
            print msg
            return None
        return confObj

    def getIdentityNameFromCertName(self, certName):
        i = certName.size() - 1

        idString = "KEY"
        while i >= 0:
            if certName.get(i).toEscapedString() == idString:
                break
            i -= 1

        if i < 0:
            print "Error: unexpected certName " + certName.toUri()
            return None

        return Name(certName.getPrefix(i))

#################################
# Getters and setters
#################################
    def getKeyChain(self):
        return self._keyChain