Esempio n. 1
0
class CoAP(DatagramProtocol):
    def __init__(self, server, forward):
        self._forward = forward
        self.received = {}
        self.sent = {}
        self.sent_token = {}
        self.received_token = {}
        self.call_id = {}
        self.relation = {}
        self._currentMID = 1
        import socket
        try:
            socket.inet_aton(server[0])
            self.server = server
            # legal
        except socket.error:
            # Not legal
            data = socket.gethostbyname(server[0])
            self.server = (data, server[1])

        # defer = reactor.resolve('coap.me')
        # defer.addCallback(self.start)
        # self.server = (None, 5683)

        root = Resource('root', visible=False, observable=False, allow_children=True)
        root.path = '/'
        self.root = Tree(root)
        self.operations = []
        self.l = None
        self.transport = None

    @property
    def current_mid(self):
        return self._currentMID

    @current_mid.setter
    def current_mid(self, c):
        self._currentMID = c

    def set_operations(self, operations):
        for op in operations:
            function, args, kwargs, client_callback = op
            self.operations.append((function, args, kwargs, client_callback))

    def startProtocol(self):
        if self.server is None:
            log.err("Server address for the client is not initialized")
            exit()
        host, port = self.server
        if host is not None:
            self.start(host)
        self.l = task.LoopingCall(self.purge_mids)
        self.l.start(defines.EXCHANGE_LIFETIME)

    def stopProtocol(self):
        self.l.stop()

    def purge_mids(self):
        log.msg("Purge mids")
        now = time.time()
        sent_key_to_delete = []
        for key in self.sent.keys():
            message, timestamp, callback, client_callback = self.sent.get(key)
            if timestamp + defines.EXCHANGE_LIFETIME <= now:
                sent_key_to_delete.append(key)
        for key in sent_key_to_delete:
            message, timestamp, callback, client_callback = self.sent.get(key)
            key_token = hash(str(self.server[0]) + str(self.server[1]) + str(message.token))
            try:
                del self.sent[key]
            except KeyError:
                pass
            try:
                del self.received[key]
            except KeyError:
                pass
            try:
                del self.sent_token[key_token]
            except KeyError:
                pass
            try:
                del self.received_token[key_token]
            except KeyError:
                pass

    def start(self, host):
        # self.transport.connect(host, self.server[1])
        function, args, kwargs, client_callback = self.get_operation()
        function(client_callback, *args, **kwargs)

    def start_test(self, transport):
        self.transport = transport
        function, args, kwargs, client_callback = self.get_operation()
        function(client_callback, *args, **kwargs)

    def get_operation(self):
        try:
            to_exec = self.operations.pop(0)
            args = []
            kwargs = {}
            if len(to_exec) == 4:
                function, args, kwargs, client_callback = to_exec
            elif len(to_exec) == 3:
                function, args, client_callback = to_exec
            elif len(to_exec) == 2:
                function, client_callback = to_exec[0]
            else:
                return None, None, None, None
            return function, args, kwargs, client_callback
        except IndexError:
            return None, None, None, None

    def send(self, message):
        serializer = Serializer()
        message.destination = self.server
        host, port = message.destination
        print "Message sent to " + host + ":" + str(port)
        print "----------------------------------------"
        print message
        print "----------------------------------------"
        datagram = serializer.serialize(message)
        log.msg("Send datagram")
        self.transport.write(datagram, self.server)

    def send_callback(self, req, callback, client_callback):
        self._currentMID += 1
        req.mid = self._currentMID
        key = hash(str(self.server[0]) + str(self.server[1]) + str(req.mid))
        key_token = hash(str(self.server[0]) + str(self.server[1]) + str(req.token))
        self.sent[key] = (req, time.time(), callback, client_callback)
        self.sent_token[key_token] = (req, time.time(), callback, client_callback)
        if isinstance(client_callback, tuple) and len(client_callback) > 1:
            client_callback, err_callback = client_callback
        else:
            err_callback = None
        self.schedule_retrasmission(req, err_callback)
        self.send(req)

    def datagramReceived(self, datagram, host):
        serializer = Serializer()
        host, port = host
        message = serializer.deserialize(datagram, host, port)
        print "Message received from " + host + ":" + str(port)
        print "----------------------------------------"
        print message
        print "----------------------------------------"
        if isinstance(message, Response):
            self.handle_response(message)
        elif isinstance(message, Request):
            log.err("Received request")
        else:
            self.handle_message(message)

        key = hash(str(host) + str(port) + str(message.mid))
        if message.type == defines.inv_types["ACK"] and message.code == defines.inv_codes["EMPTY"] \
           and key in self.sent.keys():
            # Separate Response
            print "Separate Response"
        else:
            function, args, kwargs, client_callback = self.get_operation()
            key = hash(str(host) + str(port) + str(message.token))
            if function is None and len(self.relation) == 0:
                if not self._forward:
                    reactor.stop()
            elif key in self.relation:
                response, timestamp, client_callback = self.relation.get(key)
                self.handle_notification(message, client_callback)
            else:
                function(client_callback, *args, **kwargs)

    def handle_message(self, message):
        key = hash(str(self.server[0]) + str(self.server[1]) + str(message.mid))
        if message.type == defines.inv_types["ACK"] and message.code == defines.inv_codes["EMPTY"] \
           and key in self.sent.keys():
            return None
        if key in self.sent.keys():
            self.received[key] = message
            if message.type == defines.inv_types["RST"]:
                print message
            else:
                req, timestamp, callback, client_callback = self.sent[key]
                callback(message.mid, client_callback)

    def handle_response(self, response):
        if response.type == defines.inv_types["CON"]:
            ack = Message.new_ack(response)
            self.send(ack)
        key_token = hash(str(self.server[0]) + str(self.server[1]) + str(response.token))
        if key_token in self.sent_token.keys():
            self.received_token[key_token] = response
            req, timestamp, callback, client_callback = self.sent_token[key_token]
            key = hash(str(self.server[0]) + str(self.server[1]) + str(req.mid))
            self.received[key] = response
            callback(req.mid, client_callback)

    def discover(self, client_callback, *args, **kwargs):
        req = Request()
        if "Token" in kwargs.keys():
            req.token = kwargs.get("Token")
        req.code = defines.inv_codes['GET']
        req.uri_path = ".well-known/core"
        req.type = defines.inv_types["CON"]
        self.send_callback(req, self.discover_results, client_callback)

    def discover_results(self, mid, client_callback):
        key = hash(str(self.server[0]) + str(self.server[1]) + str(mid))
        response = self.received.get(key)
        if key in self.call_id.keys():
            handler, retransmit_count = self.call_id.get(key)
            if handler is not None:
                try:
                    log.msg("Cancel retrasmission")
                    handler.cancel()
                except AlreadyCancelled:
                    pass
        err_callback = None
        if isinstance(client_callback, tuple) and len(client_callback) > 1:
            client_callback, err_callback = client_callback
        elif isinstance(client_callback, tuple):
            client_callback = client_callback[0]
        if response is not None:
            # self.parse_core_link_format(response.payload)
            client_callback(response)
        elif err_callback is not None:
            err_callback(mid, self.server[0], self.server[1])

    def parse_core_link_format(self, link_format):
        while len(link_format) > 0:
            pattern = "<([^>]*)>;"
            result = re.match(pattern, link_format)
            path = result.group(1)
            path = path.split("/")
            path = path[1:]
            link_format = link_format[result.end(1) + 2:]
            pattern = "([^<,])*"
            result = re.match(pattern, link_format)
            attributes = result.group(0)
            dict_att = {}
            if len(attributes) > 0:
                attributes = attributes.split(";")
                for att in attributes:
                    a = att.split("=")
                    # TODO check correctness
                    dict_att[a[0]] = a[1]
                link_format = link_format[result.end(0) + 1:]

            while True:
                last, p = self.root.find_complete_last(path)
                if p is not None:
                    resource = Resource("/".join(path))
                    resource.path = p
                    if p == "".join(path):
                        resource.attributes = dict_att
                    last.add_child(resource)
                else:
                    break
        log.msg(self.root.dump())

    def get(self, client_callback, *args, **kwargs):
        path = args[0]
        req = Request()
        if "Token" in kwargs.keys():
            req.token = kwargs.get("Token")
            del kwargs["Token"]
        for key in kwargs:
            o = Option()
            o.number = defines.inv_options[key]
            o.value = kwargs[key]
            req.add_option(o)

        req.code = defines.inv_codes['GET']
        req.uri_path = path
        req.type = defines.inv_types["CON"]
        self.send_callback(req, self.get_results, client_callback)

    def get_results(self, mid, client_callback):
        key = hash(str(self.server[0]) + str(self.server[1]) + str(mid))
        response = self.received.get(key)
        if key in self.call_id.keys():
            handler, retransmit_count = self.call_id.get(key)
            if handler is not None:
                try:
                    log.msg("Cancel retrasmission")
                    handler.cancel()
                except AlreadyCancelled:
                    pass

        err_callback = None
        if isinstance(client_callback, tuple) and len(client_callback) > 1:
            client_callback, err_callback = client_callback
        elif isinstance(client_callback, tuple):
            client_callback = client_callback[0]
        if response is not None:
            # self.parse_core_link_format(response.payload)
            client_callback(response)
        elif err_callback is not None:
            err_callback(mid, self.server[0], self.server[1])

    def observe(self, client_callback, *args, **kwargs):
        path = args[0]
        req = Request()
        if "Token" in kwargs.keys():
            req.token = kwargs.get("Token")
            del kwargs["Token"]
        for key in kwargs:
            o = Option()
            o.number = defines.inv_options[key]
            o.value = kwargs[key]
            req.add_option(o)

        req.code = defines.inv_codes['GET']
        req.uri_path = path
        req.observe = 0
        req.type = defines.inv_types["CON"]
        self.send_callback(req, self.observe_results, client_callback)

    def observe_results(self, mid, client_callback):
        key = hash(str(self.server[0]) + str(self.server[1]) + str(mid))
        response = self.received.get(key)
        if key in self.call_id.keys():
            handler, retransmit_count = self.call_id.get(key)
            if handler is not None:
                try:
                    log.msg("Cancel retrasmission")
                    handler.cancel()
                except AlreadyCancelled:
                    pass

        err_callback = None
        if isinstance(client_callback, tuple) and len(client_callback) > 1:
            client_callback, err_callback = client_callback
        elif isinstance(client_callback, tuple):
            client_callback = client_callback[0]
        if response is not None:
            if response.observe != 0:
                # TODO add observing results
                host, port = response.source
                key = hash(str(host) + str(port) + str(response.token))
                self.relation[key] = (response, time.time(), client_callback)
            client_callback(response)
        elif err_callback is not None:
            err_callback(mid, self.server[0], self.server[1])

    def handle_notification(self, response, client_callback):
        host, port = response.source
        key = hash(str(host) + str(port) + str(response.token))
        self.relation[key] = (response, time.time(), client_callback)
        if response.type == defines.inv_types["CON"]:
            ack = Message.new_ack(response)
            self.send(ack)

    def cancel_observing(self, response, send_rst):
        host, port = response.source
        key = hash(str(host) + str(port) + str(response.token))
        del self.relation[key]
        if send_rst:
            rst = Message.new_rst(response)
            self.send(rst)

    def post(self, client_callback, *args, **kwargs):
        path, payload = args
        req = Request()
        if "Token" in kwargs.keys():
            req.token = kwargs.get("Token")
            del kwargs["Token"]
        for key in kwargs:
            o = Option()
            o.number = defines.inv_options[key]
            o.value = kwargs[key]
            req.add_option(o)
        req.code = defines.inv_codes['POST']
        req.uri_path = path
        req.type = defines.inv_types["CON"]
        req.payload = payload
        self.send_callback(req, self.post_results, client_callback)

    def post_results(self, mid, client_callback):
        key = hash(str(self.server[0]) + str(self.server[1]) + str(mid))
        response = self.received.get(key)
        if key in self.call_id.keys():
            handler, retransmit_count = self.call_id.get(key)
            if handler is not None:
                try:
                    log.msg("Cancel retrasmission")
                    handler.cancel()
                except AlreadyCancelled:
                    pass
        err_callback = None
        if isinstance(client_callback, tuple) and len(client_callback) > 1:
            client_callback, err_callback = client_callback
        elif isinstance(client_callback, tuple):
            client_callback = client_callback[0]
        if response is not None:
            # self.parse_core_link_format(response.payload)
            client_callback(response)
        elif err_callback is not None:
            err_callback(mid, self.server[0], self.server[1])

    def put(self, client_callback, *args, **kwargs):
        path, payload = args
        req = Request()
        if "Token" in kwargs.keys():
            req.token = kwargs.get("Token")
            del kwargs["Token"]
        for key in kwargs:
            o = Option()
            o.number = defines.inv_options[key]
            o.value = kwargs[key]
            req.add_option(o)
        req.code = defines.inv_codes['PUT']
        req.uri_path = path
        req.type = defines.inv_types["CON"]
        req.payload = payload
        self.send_callback(req, self.put_results, client_callback)

    def put_results(self, mid, client_callback):
        key = hash(str(self.server[0]) + str(self.server[1]) + str(mid))
        response = self.received.get(key)
        if key in self.call_id.keys():
            handler, retransmit_count = self.call_id.get(key)
            if handler is not None:
                try:
                    log.msg("Cancel retrasmission")
                    handler.cancel()
                except AlreadyCancelled:
                    pass
        err_callback = None
        if isinstance(client_callback, tuple) and len(client_callback) > 1:
            client_callback, err_callback = client_callback
        elif isinstance(client_callback, tuple):
            client_callback = client_callback[0]
        if response is not None:
            # self.parse_core_link_format(response.payload)
            client_callback(response)
        elif err_callback is not None:
            err_callback(mid, self.server[0], self.server[1])

    def delete(self, client_callback, *args, **kwargs):
        path = args[0]
        req = Request()
        if "Token" in kwargs.keys():
            req.token = kwargs.get("Token")
            del kwargs["Token"]
        for key in kwargs:
            o = Option()
            o.number = defines.inv_options[key]
            o.value = kwargs[key]
            req.add_option(o)
        req.code = defines.inv_codes['DELETE']
        req.uri_path = path
        req.type = defines.inv_types["CON"]
        self.send_callback(req, self.delete_results, client_callback)

    def delete_results(self, mid, client_callback):
        key = hash(str(self.server[0]) + str(self.server[1]) + str(mid))
        response = self.received.get(key)
        if key in self.call_id.keys():
            handler, retransmit_count = self.call_id.get(key)
            if handler is not None:
                try:
                    log.msg("Cancel retrasmission")
                    handler.cancel()
                except AlreadyCancelled:
                    pass
        err_callback = None
        if isinstance(client_callback, tuple) and len(client_callback) > 1:
            client_callback, err_callback = client_callback
        elif isinstance(client_callback, tuple):
            client_callback = client_callback[0]
        if response is not None:
            # self.parse_core_link_format(response.payload)
            client_callback(response)
        elif err_callback is not None:
            err_callback(mid, self.server[0], self.server[1])

    def schedule_retrasmission(self, request, err_callback):
        host, port = self.server
        if request.type == defines.inv_types['CON']:
            future_time = random.uniform(defines.ACK_TIMEOUT, (defines.ACK_TIMEOUT * defines.ACK_RANDOM_FACTOR))
            key = hash(str(host) + str(port) + str(request.mid))
            self.call_id[key] = (reactor.callLater(future_time, self.retransmit,
                                                   (request, host, port, future_time, err_callback)), 0)

    def retransmit(self, t):
        log.msg("Retransmit")
        request, host, port, future_time, err_callback = t
        key = hash(str(host) + str(port) + str(request.mid))
        call_id, retransmit_count = self.call_id[key]
        if retransmit_count < defines.MAX_RETRANSMIT and (not request.acknowledged and not request.rejected):
            retransmit_count += 1
            req, timestamp, callback, client_callback = self.sent[key]
            self.sent[key] = (request, time.time(), callback, client_callback)
            self.send(request)
            future_time *= 2
            self.call_id[key] = (reactor.callLater(future_time, self.retransmit,
                                                   (request, host, port, future_time, err_callback)), retransmit_count)

        elif request.acknowledged or request.rejected:
            request.timeouted = False
            del self.call_id[key]
        else:
            request.timeouted = True
            log.err("Request timeouted")
            del self.call_id[key]
            if err_callback is not None:
                err_callback(request.mid, host, port)