Ejemplo n.º 1
0
    async def send_response(self, transaction: Transaction) -> Transaction:
        """
        Handles the Blocks option in a outgoing response.

        :type transaction: Transaction
        :param transaction: the transaction that owns the response
        :rtype : Transaction
        :return: the edited transaction
        """
        try:
            host, port = transaction.request.source
        except AttributeError:  # pragma: no cover
            raise errors.CoAPException("Request Source cannot be computed")

        key_token = utils.str_append_hash(host, port,
                                          transaction.request.token)

        if (key_token in self._block2_receive and transaction.response.payload is not None) or \
                (transaction.response.payload is not None and len(transaction.response.payload) > defines.MAX_PAYLOAD):
            if key_token in self._block2_receive:

                byte = self._block2_receive[key_token].byte
                size = self._block2_receive[key_token].size
                num = self._block2_receive[key_token].num

            else:
                byte = 0
                num = 0
                size = defines.MAX_PAYLOAD
                m = 1

                self._block2_receive[key_token] = BlockItem(byte, num, m, size)

            if num != 0:
                del transaction.response.observe
            m = 0
            if len(transaction.response.payload) > (byte + size):
                m = 1

            transaction.response.payload = transaction.response.payload[
                byte:byte + size]
            transaction.response.block2 = (num, m, size)

            self._block2_receive[key_token].byte += size
            self._block2_receive[key_token].num += 1
            if m == 0:
                del self._block2_receive[key_token]
        elif key_token in self._block1_receive:
            num = self._block1_receive[key_token].num
            size = self._block1_receive[key_token].size
            m = self._block1_receive[key_token].m
            transaction.response.block1 = (num - 1, m, size)
            if m == 1:
                transaction.response.code = defines.Code.CONTINUE
            else:
                del self._block1_receive[key_token]

        return transaction
Ejemplo n.º 2
0
    async def receive_response(self, response: Response):
        """
        Pair responses with requests.

        :type response: Response
        :param response: the received response
        :rtype : Transaction
        :return: the transaction to which the response belongs to
        """
        logger.debug("receive_response - {0}".format(response))
        try:
            host, port = response.source
        except TypeError or AttributeError:  # pragma: no cover
            raise errors.CoAPException("Response Source cannot be computed")

        key_mid = utils.str_append_hash(host, port, response.mid)
        key_mid_multicast = utils.str_append_hash(defines.ALL_COAP_NODES, port, response.mid)
        key_token = utils.str_append_hash(host, port, response.token)
        key_token_multicast = utils.str_append_hash(defines.ALL_COAP_NODES, port, response.token)
        if key_mid in list(self._transactions.keys()):
            transaction = self._transactions[key_mid]
            if response.token != transaction.request.token:
                logger.warning(f"Tokens does not match -  response message {host}:{port}")
                raise errors.CoAPException(msg=f"Tokens does not match -  response message {host}:{port}")
        elif key_token in self._transactions_token:
            transaction = self._transactions_token[key_token]
        elif key_mid_multicast in list(self._transactions.keys()):
            transaction = self._transactions[key_mid_multicast]
        elif key_token_multicast in self._transactions_token:
            transaction = self._transactions_token[key_token_multicast]
            if response.token != transaction.request.token:
                logger.warning(f"Tokens does not match -  response message {host}:{port}")
                raise errors.CoAPException(msg=f"Tokens does not match -  response message {host}:{port}")
        else:
            raise errors.CoAPException("Un-Matched incoming response message " + str(host) + ":" + str(port))

        transaction.request.acknowledged = True
        transaction.response = response
        if response.type != defines.Type.CON:
            transaction.response.acknowledged = True
        transaction.retransmit_stop = True
        if transaction.retransmit_task is not None:
            transaction.retransmit_task.cancel()
        return transaction
Ejemplo n.º 3
0
    def version(self, v: int):
        """
        Sets the CoAP version

        :param v: the version
        :raise AttributeError: if value is not 1
        """
        if not isinstance(v, int) or v != 1:  # pragma: no cover
            raise errors.CoAPException("Only CoAP version 1 is supported")
        self._version = v
Ejemplo n.º 4
0
    def destination(self,
                    value: Optional[Tuple[Union[str, ipaddress.IPv4Address,
                                                ipaddress.IPv6Address], int]]):
        """
        Set the destination of the message.

        :type value: tuple
        :param value: (ip, port)
        :raise AttributeError: if value is not a ip and a port.
        """
        if not isinstance(value, tuple) or len(value) != 2:  # pragma: no cover
            raise errors.CoAPException("Invalid destination")

        host, port = value
        try:
            host = ipaddress.ip_address(host)
        except ipaddress.AddressValueError:  # pragma: no cover
            raise errors.CoAPException("Invalid destination")
        self._destination = host, port
Ejemplo n.º 5
0
    def mid(self, value: int):
        """
        Sets the MID of the message.

        :type value: Integer
        :param value: the MID
        :raise AttributeError: if value is not int or cannot be represented on 16 bits.
        """
        if not isinstance(value, int) or value > 65536:  # pragma: no cover
            raise errors.CoAPException("MID must be between 0 and 65536")
        self._mid = value
Ejemplo n.º 6
0
    async def send_request(self, request: Request):
        """
        Handles the Blocks option in a outgoing request.

        :type request: Request
        :param request: the outgoing request
        :return: the edited request
        """
        if request.block1 or (request.payload is not None
                              and len(request.payload) > defines.MAX_PAYLOAD):
            try:
                host, port = request.destination
            except AttributeError:  # pragma: no cover
                raise errors.CoAPException(
                    "Request destination cannot be computed")
            key_token = utils.str_append_hash(host, port, request.token)
            if request.block1:
                num, m, size = request.block1
            else:
                num = 0
                m = 1
                size = defines.MAX_PAYLOAD
                request.block1 = num, m, size
            self._block1_sent[key_token] = BlockItem(size, num, m, size,
                                                     request.payload,
                                                     request.content_type)
            request.payload = request.payload[0:size]

        elif request.block2:
            try:
                host, port = request.destination
            except AttributeError:  # pragma: no cover
                raise errors.CoAPException(
                    "Request destination cannot be computed")
            key_token = utils.str_append_hash(host, port, request.token)
            num, m, size = request.block2
            item = BlockItem(size, num, m, size)
            self._block2_sent[key_token] = item
            return request
        return request
Ejemplo n.º 7
0
    def _deserialize_options(cls, data: bytes) -> Tuple[bytes, List[Option]]:
        ret = []
        option_number = 0
        while data:
            if data[0] == defines.PAYLOAD_MARKER:
                return data, ret
            field = data[0]
            delta = (field & 0xF0) >> 4
            length = (field & 0x0F)
            data = data[1:]
            data, delta = Serializer._read_extended_value(delta, data)
            data, length = Serializer._read_extended_value(length, data)
            option_number += delta
            if len(data) < length:  # pragma: no cover
                raise errors.CoAPException("Option value is not present")
            try:
                option_item = OptionRegistry(option_number)
            except KeyError or ValueError:
                (opt_critical, _,
                 _) = OptionRegistry.get_option_flags(option_number)
                if opt_critical:  # pragma: no cover
                    raise errors.CoAPException(
                        "Critical option {0} unknown".format(option_number))
                else:
                    # If the non-critical option is unknown
                    # (vendor-specific, proprietary) - just skip it
                    pass
            else:
                if length == 0:
                    value = bytes()
                else:
                    value = data[:length]

                option = Option(option_item)
                option.raw_value = value

                ret.append(option)
            finally:
                data = data[length:]
        return data, ret
Ejemplo n.º 8
0
    def options(self, value: Optional[List[Option]]):
        """
        Set the options of the CoAP message.

        :type value: list
        :param value: list of options
        """
        if value is None:
            value = []
        if isinstance(value, list):
            self._options = value
        else:  # pragma: no cover
            raise errors.CoAPException("Invalid option list")
Ejemplo n.º 9
0
 def _write_extended_value(cls, value: int) -> Tuple[int, bytes, str]:
     """Used to encode large values of option delta and option length
        into raw binary form.
        In CoAP option delta and length can be represented by a variable
        number of bytes depending on the value."""
     if 0 <= value < 13:
         return value, bytes(), ""
     elif 13 <= value < 269:
         return 13, (value - 13).to_bytes(1, 'big'), "c"
     elif 269 <= value < 65804:
         return 14, (value - 269).to_bytes(2, 'big'), "cc"
     else:  # pragma: no cover
         raise errors.CoAPException("Delta or Length value out of range.")
Ejemplo n.º 10
0
    def accept(self, value: int):
        """
        Add an Accept option to a request.

        :param value: the Accept value
        """
        del self.accept
        try:
            value = defines.ContentType(value)
            option = Option(defines.OptionRegistry.ACCEPT)
            option.value = value
            self.add_option(option)
        except ValueError:  # pragma: no cover
            raise errors.CoAPException("Unknown Accept value")
Ejemplo n.º 11
0
    def content_type(self) -> defines.ContentType:
        """
        Get the Content-Type option of a response.

        :return: the Content-Type value or 0 if not specified by the response
        """
        value = defines.ContentType.TEXT_PLAIN
        for option in self.options:
            if option.number == defines.OptionRegistry.CONTENT_TYPE.value:
                try:
                    value = defines.ContentType(option.value)
                except ValueError:  # pragma: no cover
                    raise errors.CoAPException("Unknown Content Type")
        return value
Ejemplo n.º 12
0
    def type(self, value: Union[defines.Type, int]):
        """
        Sets the type of the message.

        :type value: Types
        :param value: the type
        :raise AttributeError: if value is not a valid type
        """
        if isinstance(value, defines.Type):
            self._type = value
        else:
            try:
                self._type = defines.Type(value)
            except ValueError:  # pragma: no cover
                raise errors.CoAPException("Unsupported message type")
Ejemplo n.º 13
0
    def payload(self, value: Union[bytes, str, utils.CoAPPayload, None]):
        """
        Sets the payload of the message and eventually the Content-Type

        :param value: the payload
        """
        if value is None:
            self._payload.payload = None
        elif isinstance(value, bytes):
            self._payload.payload = value
        elif isinstance(value, str):
            self._payload.payload = value.encode("utf-8")
        elif isinstance(value, utils.CoAPPayload):
            self._payload.payload = value.payload
        else:  # pragma: no cover
            raise errors.CoAPException("Payload must be bytes, str or None")
Ejemplo n.º 14
0
    def token(self, value: Union[bytes, str, None]):
        """
        Set the Token of the message.

        :type value: String
        :param value: the Token
        :raise AttributeError: if value is longer than 256
        """
        if value is None:
            self._token = value
        elif isinstance(value, str) and len(value) < 256:
            self._token = value.encode("utf-8")
        elif isinstance(value, bytes) and len(value) < 256:
            self._token = value
        else:  # pragma: no cover
            raise errors.CoAPException("Invalid token")
Ejemplo n.º 15
0
    async def send_response(self, transaction: Transaction) -> Transaction:
        """
        Set the type, the token and eventually the MID for the outgoing response

        :type transaction: Transaction
        :param transaction: the transaction that owns the response
        :rtype : Transaction
        :return: the edited transaction
        """
        if transaction.response.type is None:
            if transaction.request.type == defines.Type.CON and not transaction.request.acknowledged:
                transaction.response.type = defines.Type.ACK
                transaction.response.mid = transaction.request.mid
                transaction.response.acknowledged = True
                # transaction.completed = True
            elif transaction.request.type == defines.Type.NON:
                transaction.response.type = defines.Type.NON
                transaction.response.acknowledged = True
            else:
                transaction.response.type = defines.Type.CON

        transaction.response.token = transaction.request.token
        transaction.response.timestamp = time.time()

        if transaction.response.mid is None:
            transaction.response.mid = self.fetch_mid()
        try:
            host, port = transaction.response.destination
        except TypeError or AttributeError:  # pragma: no cover
            raise errors.CoAPException("Response destination cannot be computed")

        logger.debug("send_response - {0}".format(transaction.response))

        key_mid = utils.str_append_hash(host, port, transaction.response.mid)
        key_token = utils.str_append_hash(host, port, transaction.response.token)
        self._transactions[key_mid] = transaction
        self._transactions_token[key_token] = transaction
        request_host, request_port = transaction.request.source
        if request_host.is_multicast:
            key_mid_multicast = utils.str_append_hash(request_host, request_port, transaction.response.mid)
            key_token_multicast = utils.str_append_hash(request_host, request_port, transaction.response.token)
            self._transactions[key_mid_multicast] = transaction
            self._transactions_token[key_token_multicast] = transaction

        transaction.request.acknowledged = True
        return transaction
Ejemplo n.º 16
0
    async def remove_subscriber(self, message):
        """
        Remove a subscriber based on token.

        :param message: the message
        """
        logger.debug("Remove Subcriber")
        try:
            host, port = message.destination
        except AttributeError:  # pragma: no cover
            raise errors.CoAPException(
                "Message destination cannot be computed")
        key_token = utils.str_append_hash(host, port, message.token)
        try:
            del self._relations[key_token]
        except KeyError:  # pragma: no cover
            logger.exception("Subscriber was not registered")
Ejemplo n.º 17
0
    def etag(self, etag: List[Union[str, bytes]]):
        """
        Add an ETag option to the message.

        :param etag: the etag
        """
        if not isinstance(etag, list):
            etag = [etag]
        for e in etag:
            option = Option(defines.OptionRegistry.ETAG)
            if isinstance(e, str):
                e = e.encode("utf-8")
            if isinstance(e, bytes):
                option.value = e
            else:  # pragma: no cover
                raise errors.CoAPException("ETAG must be Opaque")
            self.add_option(option)
Ejemplo n.º 18
0
    async def send_empty(self, message):
        """
        Eventually remove from the observer list in case of a RST message.

        :type message: Message
        :param message: the message
        :return: the message unmodified
        """

        try:
            host, port = message.destination
        except AttributeError as e:  # pragma: no cover
            raise errors.CoAPException(
                "Message destination cannot be computed")
        key_token = utils.str_append_hash(host, port, message.token)
        if key_token in self._relations and message.type == defines.Type.RST:
            del self._relations[key_token]
        return message
Ejemplo n.º 19
0
    def add_option(self, option: Option):
        """
        Add an option to the message.

        :type option: Option
        :param option: the option
        :raise TypeError: if the option is not repeatable and such option is already present in the message
        """
        assert isinstance(option, Option)
        if not option.type.repeatable:
            ret = self._already_in(option)
            if ret:  # pragma: no cover
                raise errors.CoAPException(
                    "Option {0} is not repeatable".format(option.name))
            else:
                self._options.append(option)
        else:
            self._options.append(option)
Ejemplo n.º 20
0
    async def receive_response(self, transaction):
        """
        Sets notification's parameters.

        :type transaction: Transaction
        :param transaction: the transaction
        :rtype : Transaction
        :return: the modified transaction
        """
        try:
            host, port = transaction.response.source
        except AttributeError as e:  # pragma: no cover
            raise errors.CoAPException("Message source cannot be computed")
        key_token = utils.str_append_hash(host, port,
                                          transaction.response.token)

        if key_token in self._relations and transaction.response.type == defines.Type.CON:
            transaction.notification = True
        return transaction
Ejemplo n.º 21
0
    async def send_request(self, request):
        """
        Add itself to the observing list

        :param request: the request
        :return: the request unmodified
        """
        if request.observe == 0:
            # Observe request
            try:
                host, port = request.destination
            except AttributeError as e:  # pragma: no cover
                raise errors.CoAPException(
                    "Request destination cannot be computed")

            key_token = utils.str_append_hash(host, port, request.token)

            self._relations[key_token] = ObserveItem(time.time(), 0, True,
                                                     None, None)

        return request
Ejemplo n.º 22
0
    async def send_response(self, transaction):
        """
        Finalize to add the client to the list of observer.

        :type transaction: Transaction
        :param transaction: the transaction that owns the response
        :return: the transaction unmodified
        """
        try:
            host, port = transaction.request.source
        except AttributeError as e:  # pragma: no cover
            raise errors.CoAPException("Request source cannot be computed")

        key_token = utils.str_append_hash(host, port,
                                          transaction.request.token)
        if key_token in self._relations:
            if transaction.response.code == defines.Code.CONTENT:
                if transaction.resource is not None and transaction.resource.observable:
                    if transaction.resource.content_type == self._relations[key_token].content_type or \
                                    self._relations[key_token].content_type == -1:
                        transaction.response.observe = transaction.resource.observe_count
                        self._relations[key_token].allowed = True
                        self._relations[key_token].transaction = transaction
                        self._relations[key_token].timestamp = time.time()
                        self._relations[
                            key_token].content_type = transaction.resource.content_type
                        del transaction.request.observe
                        if transaction.response.max_age is not None:
                            self._relations[
                                key_token].pmin = transaction.response.max_age
                    else:
                        del self._relations[key_token]
                        raise errors.ObserveError("Content-Type changed",
                                                  defines.Code.NOT_ACCEPTABLE,
                                                  transaction=transaction)
                else:
                    del self._relations[key_token]
            elif transaction.response.code.is_error():
                del self._relations[key_token]
        return transaction
Ejemplo n.º 23
0
    async def receive_request(self, transaction):
        """
        Manage the observe option in the request end eventually initialize the client for adding to
        the list of observers or remove from the list.

        :type transaction: Transaction
        :param transaction: the transaction that owns the request
        :rtype : Transaction
        :return: the modified transaction
        """

        if transaction.request.observe == 0 or transaction.request.observe == 1:
            # Observe request
            try:
                host, port = transaction.request.source
            except AttributeError as e:  # pragma: no cover
                raise errors.CoAPException("Request Source cannot be computed")

            key_token = utils.str_append_hash(host, port,
                                              transaction.request.token)

            if transaction.request.observe == 0:
                non_counter = 0
                if key_token in self._relations:
                    # Renew registration
                    allowed = True
                else:
                    allowed = False
                self._relations[key_token] = ObserveItem(
                    time.time(), non_counter, allowed, transaction, -1)
            elif transaction.request.observe == 1:
                logger.info("Remove Subscriber")
                try:
                    del self._relations[key_token]
                except KeyError:  # pragma: no cover
                    logger.exception("Subscriber was not registered")

        return transaction
Ejemplo n.º 24
0
    async def receive_response(self, transaction: Transaction):
        """
        Handles the Blocks option in a incoming response.

        :type transaction: Transaction
        :param transaction: the transaction that owns the response
        :rtype : Transaction
        :return: the edited transaction
        """
        try:
            host, port = transaction.response.source
        except AttributeError:  # pragma: no cover
            raise errors.CoAPException("Response source cannot be computed")
        key_token = utils.str_append_hash(host, port,
                                          transaction.response.token)

        if key_token in self._block1_sent and transaction.response.block1 is not None:
            item = self._block1_sent[key_token]
            n_num, n_m, n_size = transaction.response.block1
            if n_num != item.num:  # pragma: no cover
                if transaction.response.type == defines.Type.CON or transaction.response.type == defines.Type.NON:
                    raise errors.ProtocolError(
                        msg=f"Block num acknowledged error, expected {item.num} "
                        f"received {n_num}",
                        mid=transaction.response.mid)
                else:
                    raise errors.CoAPException(
                        msg=f"Block num acknowledged error, expected {item.num} "
                        f"received {n_num}")
            if n_size < item.size:
                logger.debug("Scale down size, was " + str(item.size) +
                             " become " + str(n_size))
                item.size = n_size

        elif transaction.response.block2 is not None:
            num, m, size = transaction.response.block2
            if m == 1:
                if key_token in self._block2_sent:
                    item = self._block2_sent[key_token]
                    if num != item.num:  # pragma: no cover
                        if transaction.response.type == defines.Type.CON or transaction.response.type == defines.Type.NON:
                            raise errors.ProtocolError(
                                msg=
                                f"Receive unwanted block, expected {item.num} "
                                f"received {num}",
                                mid=transaction.response.mid)
                        else:
                            raise errors.CoAPException(
                                msg=
                                f"Receive unwanted block, expected {item.num} "
                                f"received {num}")

                    if item.content_type is None:
                        item.content_type = transaction.response.content_type
                    if item.content_type != transaction.response.content_type:
                        if transaction.response.type == defines.Type.CON or transaction.response.type == defines.Type.NON:
                            raise errors.ProtocolError(
                                msg=f"Content-type Error",
                                mid=transaction.response.mid)
                        else:
                            raise errors.CoAPException(
                                msg=f"Content-type Error")
                    item.byte += size
                    item.num = num + 1
                    item.size = size
                    item.m = m
                    item.payload += transaction.response.payload
                else:
                    item = BlockItem(size, num + 1, m, size,
                                     transaction.response.payload,
                                     transaction.response.content_type)
                    self._block2_sent[key_token] = item

            else:
                if key_token in self._block2_sent:
                    if self._block2_sent[key_token].content_type is None:
                        self._block2_sent[
                            key_token].content_type = transaction.response.content_type
                    if self._block2_sent[
                            key_token].content_type != transaction.response.content_type:  # pragma: no cover
                        if transaction.response.type == defines.Type.CON or transaction.response.type == defines.Type.NON:
                            raise errors.ProtocolError(
                                msg=f"Content-type Error",
                                mid=transaction.response.mid)
                        else:
                            raise errors.CoAPException(
                                msg=f"Content-type Error")
                    del self._block2_sent[key_token]

        return transaction
Ejemplo n.º 25
0
    async def send_empty(self, transaction: Optional[Transaction] = None,
                         related: Optional[defines.MessageRelated] = None,
                         message: Message = None) -> Tuple[Transaction, Message]:
        """
        Manage ACK or RST related to a transaction. Sets if the transaction has been acknowledged or rejected.

        :param message: 
        :param msg_type:
        :param transaction: the transaction
        :param related: if the ACK/RST message is related to the request or the response. Must be equal to
        transaction.request or to transaction.response or None
        """
        if message is None:
            message = Message()
        if related == defines.MessageRelated.REQUEST:
            if transaction.request.type == defines.Type.CON:
                transaction.request.acknowledged = True
                # transaction.completed = True
                message.type = defines.Type.ACK
                message.mid = transaction.request.mid
                message.code = defines.Code.EMPTY
                message.destination = transaction.request.source
            else:  # pragma: no cover
                raise errors.CoAPException("NON messages cannot be replied with ACKs")

            try:
                host, port = transaction.request.source
            except TypeError or AttributeError:  # pragma: no cover
                raise errors.CoAPException("Response destination cannot be computed")
            key_mid = utils.str_append_hash(host, port, transaction.request.mid)
            key_token = utils.str_append_hash(host, port, transaction.request.token)
            self._transactions[key_mid] = transaction
            self._transactions_token[key_token] = transaction

        elif related == defines.MessageRelated.RESPONSE:
            if transaction.response.type == defines.Type.CON:
                transaction.response.acknowledged = True
                # transaction.completed = True
                message.type = defines.Type.ACK
                message.mid = transaction.response.mid
                message.code = defines.Code.EMPTY
                message.destination = transaction.response.source
            else:  # pragma: no cover
                raise errors.CoAPException("NON messages cannot be replied with ACKs")

            try:
                host, port = transaction.response.source
            except TypeError or AttributeError:  # pragma: no cover
                raise errors.CoAPException("Response destination cannot be computed")
            key_mid = utils.str_append_hash(host, port, transaction.response.mid)
            key_token = utils.str_append_hash(host, port, transaction.response.token)
            self._transactions[key_mid] = transaction
            self._transactions_token[key_token] = transaction
            request_host, request_port = transaction.request.destination
            if request_host.is_multicast:
                key_mid_multicast = utils.str_append_hash(request_host, request_port, transaction.response.mid)
                key_token_multicast = utils.str_append_hash(request_host, request_port, transaction.response.token)
                self._transactions[key_mid_multicast] = transaction
                self._transactions_token[key_token_multicast] = transaction
        else:
            # for clients
            try:
                host, port = message.destination
            except TypeError or AttributeError:  # pragma: no cover
                raise errors.CoAPException("Message destination cannot be computed")

            key_mid = utils.str_append_hash(host, port, message.mid)
            key_token = utils.str_append_hash(host, port, message.token)

            message.timestamp = time.time()
            transaction = Transaction(request=message, timestamp=message.timestamp)
            self._transactions[key_mid] = transaction
            self._transactions_token[key_token] = transaction
        logger.debug("send_empty -  {0}".format(message))
        return transaction, message
Ejemplo n.º 26
0
    async def receive_empty(self, message: Message) -> Transaction:
        """
        Pair ACKs with requests.

        :type message: Message
        :param message: the received message
        :rtype : Transaction
        :return: the transaction to which the message belongs to
        """
        logger.debug("receive_empty - {0}".format(message))
        try:
            host, port = message.source
        except TypeError or AttributeError:  # pragma: no cover
            raise errors.CoAPException("Request Source cannot be computed")

        key_mid = utils.str_append_hash(host, port, message.mid)
        key_mid_multicast = utils.str_append_hash(defines.ALL_COAP_NODES, port, message.mid)
        key_token = utils.str_append_hash(host, port, message.token)
        key_token_multicast = utils.str_append_hash(defines.ALL_COAP_NODES, port, message.token)
        transaction = self._transactions.get(key_mid, None)
        in_memory = [(transaction, key_mid)]
        transaction = self._transactions_token.get(key_token, None)
        in_memory.append((transaction, key_token))
        transaction = self._transactions.get(key_mid_multicast, None)
        in_memory.append((transaction, key_mid_multicast))
        transaction = self._transactions_token.get(key_token_multicast, None)
        in_memory.append((transaction, key_token_multicast))
        valid = list(filter(lambda x: x[0] is not None, in_memory))
        if len(valid) == 0:  # pragma: no cover
            logger.warning("Un-Matched incoming empty message fom {0}:{1} with MID {2}".format(host, port,
                                                                                               message.mid))
            raise errors.PongException("Un-Matched incoming empty message fom {0}:{1} with MID {2}"
                                       .format(host, port, message.mid), message=message)
        else:
            transaction, key = valid[0]

        if message.type == defines.Type.ACK:
            if not transaction.request.acknowledged:
                transaction.request.acknowledged = True
            elif (transaction.response is not None) and (not transaction.response.acknowledged):
                transaction.response.acknowledged = True
        elif message.type == defines.Type.RST:
            if not transaction.request.acknowledged:
                transaction.request.rejected = True
            elif not transaction.response.acknowledged:
                transaction.response.rejected = True
        elif message.type == defines.Type.CON:  # pragma: no cover
            # implicit ACK (might have been lost)
            logger.debug("Implicit ACK on received CON for waiting transaction")
            transaction.request.acknowledged = True
        else:  # pragma: no cover
            logger.warning("Unhandled message type...")
            raise errors.CoAPException("Unhandled message type...")

        transaction.retransmit_stop = True
        if transaction.retransmit_task is not None:
            transaction.retransmit_task.cancel()

        for t, k in valid:

            if k == key_mid:
                self._transactions[key_mid] = transaction
            elif k == key_token:
                self._transactions_token[key_token] = transaction
            elif k == key_mid_multicast:
                self._transactions[key_mid_multicast] = transaction
            elif k == key_token_multicast:
                self._transactions_token[key_token_multicast] = transaction

        return transaction
Ejemplo n.º 27
0
    async def serialize(
            cls,
            message: Union[Request, Response, Message],
            source: Optional[Tuple[str, int]] = None,
            destination: Optional[Tuple[str, int]] = None) -> ctypes.Array:
        """
        Serialize a message to a udp packet

        :param destination:
        :param source:
        :type message: Message
        :param message: the message to be serialized
        :rtype: stream of byte
        :return: the message serialized
        """
        if message.source is None:
            message.source = source
        if message.destination is None:
            message.destination = destination
        if message.code is None or message.type is None or message.mid is None:  # pragma: no cover
            raise errors.CoAPException(
                "Code, Message Type and Message ID must not be None.")
        fmt = "!BBH"

        if message.token is None:
            tkl = 0
        else:
            tkl = len(message.token)
        tmp = (defines.VERSION << 2)
        tmp |= message.type
        tmp <<= 4
        tmp |= tkl

        data = [tmp, message.code.value, message.mid]

        if tkl > 0:
            fmt += "{0}s".format(tkl)
            data.append(message.token)

        optiondata, option_fmt = cls._serialize_options(message.options)
        data.extend(optiondata)
        fmt += option_fmt
        payload = message.payload.raw

        if payload is not None and len(payload) > 0:
            # if payload is present and of non-zero length, it is prefixed by
            # an one-byte Payload Marker (0xFF) which indicates the end of
            # options and the start of the payload

            fmt += "B"
            data.append(defines.PAYLOAD_MARKER)

            fmt += "{0}s".format(len(payload))
            data.append(payload)

        try:
            s = struct.Struct(fmt)
            datagram = ctypes.create_string_buffer(s.size)
            s.pack_into(datagram, 0, *data)
        except struct.error:  # pragma: no cover
            print("fmt: {0}, {1}".format(fmt, data))
            raise errors.CoAPException("Message cannot be serialized.")
        return datagram
Ejemplo n.º 28
0
    async def receive_request(self, transaction: Transaction) -> Transaction:
        """
        Handles the Blocks option in a incoming request.

        :type transaction: Transaction
        :param transaction: the transaction that owns the request
        :rtype : Transaction
        :return: the edited transaction
        """
        try:
            host, port = transaction.request.source
        except AttributeError:  # pragma: no cover
            raise errors.CoAPException("Request Source cannot be computed")

        key_token = utils.str_append_hash(host, port,
                                          transaction.request.token)

        if transaction.request.block2 is not None:

            num, m, size = transaction.request.block2
            if key_token in self._block2_receive:
                self._block2_receive[key_token].num = num
                self._block2_receive[key_token].size = size
                self._block2_receive[key_token].m = m
            else:
                # early negotiation
                byte = size * num
                self._block2_receive[key_token] = BlockItem(byte, num, m, size)

        elif transaction.request.block1 is not None or len(
                transaction.request.payload) > defines.MAX_PAYLOAD:
            # POST or PUT
            if len(transaction.request.payload) > defines.MAX_PAYLOAD:
                num, m, size = 0, 1, defines.MAX_PAYLOAD
                transaction.request.payload = transaction.request.payload[
                    0:size]
            else:
                num, m, size = transaction.request.block1
            if key_token in self._block1_receive:
                content_type = transaction.request.content_type
                if num != self._block1_receive[key_token].num \
                        or content_type != self._block1_receive[key_token].content_type \
                        or transaction.request.payload is None:
                    # Error Incomplete
                    raise errors.InternalError(
                        msg="Entity incomplete",
                        response_code=defines.Code.REQUEST_ENTITY_INCOMPLETE,
                        transaction=transaction)
                self._block1_receive[
                    key_token].payload += transaction.request.payload
            else:
                # first block
                if num != 0:
                    # Error Incomplete
                    raise errors.InternalError(
                        msg="Entity incomplete",
                        response_code=defines.Code.REQUEST_ENTITY_INCOMPLETE,
                        transaction=transaction)
                content_type = transaction.request.content_type
                self._block1_receive[key_token] = BlockItem(
                    size, num, m, size, transaction.request.payload,
                    content_type)
            num += 1
            byte = size
            self._block1_receive[key_token].byte = byte
            self._block1_receive[key_token].num = num
            self._block1_receive[key_token].size = size
            self._block1_receive[key_token].m = m

            if m == 0:
                transaction.request.payload = self._block1_receive[
                    key_token].payload
                # end of blockwise
                transaction.block_transfer = False
                #
                return transaction
            else:
                # Continue
                transaction.block_transfer = True
                transaction.response = Response()
                transaction.response.destination = transaction.request.source
                transaction.response.token = transaction.request.token

        return transaction
Ejemplo n.º 29
0
    async def deserialize(
        cls,
        datagram: bytes,
        source: Optional[Tuple[str, int]] = None,
        destination: Optional[Tuple[str, int]] = None
    ) -> Union[Message, Request, Response]:
        """
        De-serialize a stream of byte to a message.

        :param destination:
        :param datagram: the incoming udp message
        :param source: the source address and port (ip, port)
        :return: the message
        :rtype: Message
        """

        try:
            (vttkl, code, mid) = struct.unpack('!BBH', datagram[:4])
        except struct.error:  # pragma: no cover
            raise errors.CoAPException("Message too short for CoAP")

        datagram = datagram[4:]
        version = int((vttkl & 0xC0) >> 6)
        message_type = (vttkl & 0x30) >> 4
        tkl = int(vttkl & 0x0F)

        if version != defines.VERSION:  # pragma: no cover
            raise errors.ProtocolError("Unsupported protocol version", mid)

        if 9 <= tkl <= 15:  # pragma: no cover
            raise errors.ProtocolError("Token Length 9-15 are reserved", mid)

        code_class = (code & 0b11100000) >> 5
        code_details = (code & 0b00011111)
        try:
            cl = MessageCodeClass(code_class)
        except ValueError:  # pragma: no cover
            raise errors.ProtocolError(
                "Unknown code class {0}".format(code_class), mid)
        try:
            if cl == MessageCodeClass.RESPONSE or cl == MessageCodeClass.CLIENT_ERROR or \
                    cl == MessageCodeClass.SERVER_ERROR:
                message = Response()
                message.code = code
            elif cl == MessageCodeClass.REQUEST and code_details != 0:
                message = Request()
                message.code = code
            else:  # Empty message
                message = Message()
                message.code = defines.Code.EMPTY
        except ValueError:  # pragma: no cover
            raise errors.ProtocolError("Unknown code {0}".format(code), mid)

        if source is not None:
            message.source = source
        if destination is not None:
            message.destination = destination
        message.version = version
        message.type = message_type
        message.mid = mid

        if tkl > 0:
            message.token = datagram[:tkl]
        else:
            message.token = None
        try:
            datagram, options = cls._deserialize_options(datagram[tkl:])
        except errors.CoAPException as e:
            raise errors.ProtocolError(e.msg, mid)
        message.add_options(options)
        if len(datagram) == 1:  # pragma: no cover
            raise errors.ProtocolError("Payload Marker with no payload", mid)
        elif len(datagram) == 0:
            message.payload = None
        else:
            message.payload = datagram[1:]
        return message
Ejemplo n.º 30
0
    async def handle_message(self, transaction, message):
        logger.debug(f"handle_message: {message}")
        if isinstance(message, Response):
            if transaction.retransmit_task is not None:
                transaction.retransmit_stop = True
                transaction.retransmit_task.cancel()
            if transaction.response.type == defines.Type.CON:
                transaction.response.acknowledged = True
                transaction, message = await self._messageLayer.send_empty(transaction, defines.MessageRelated.RESPONSE)
                await self._send_datagram(message)
            transaction = await self._blockLayer.receive_response(transaction)
            transaction = await self._observeLayer.receive_response(transaction)

            async with transaction.response_wait:
                transaction.response_wait.notify()

        elif isinstance(message, Request):
            if transaction.request.duplicated:
                logger.warning("Duplicate request")
                if transaction.response.completed is False:
                    transaction.send_separate.set()
                else:
                    if transaction.separate_task is not None:
                        transaction.separate_task.cancel()
                    transaction = await self._messageLayer.send_response(transaction)
                    await self._send_datagram(transaction.response)
                return

            transaction.separate_task = self._loop.create_task(self._send_ack(transaction))
            transaction.automatic_separate_task = self._loop.call_later(defines.SEPARATE_TIMEOUT,
                                                                        functools.partial(self._send_automatic_ack,
                                                                                          transaction))

            transaction = await self._blockLayer.receive_request(transaction)
            if transaction.block_transfer:
                transaction.separate_task.cancel()
                transaction = await self._blockLayer.send_response(transaction)
                transaction = await self._messageLayer.send_response(transaction)
                await self._send_datagram(transaction.response)
                return
            transaction = await self._observeLayer.receive_request(transaction)

            transaction = await self._requestLayer.receive_request(transaction)
            transaction.response.source = self._address

            transaction = await self._observeLayer.send_response(transaction)
            transaction = await self._blockLayer.send_response(transaction)

            transaction.separate_task.cancel()

            transaction = await self._messageLayer.send_response(transaction)

            if transaction.response is not None:
                if transaction.response.type == defines.Type.CON:
                    future_time = random.uniform(defines.ACK_TIMEOUT,
                                                 (defines.ACK_TIMEOUT * defines.ACK_RANDOM_FACTOR))
                    transaction.retransmit_task = self._loop.create_task(
                        self._retransmit(transaction, transaction.response, future_time, 0))

                await self._send_datagram(transaction.response)
            if transaction.resource is not None and transaction.resource.notify_queue is not None \
                    and transaction.resource.changed:
                await transaction.resource.notify_queue.put(transaction.resource)

        elif isinstance(message, Message):
            if transaction is not None:
                if not transaction.request.rejected:
                    # async with transaction.lock:
                    transaction = await self._observeLayer.receive_empty(message, transaction)
                    if transaction.retransmit_task is not None:
                        transaction.retransmit_stop = True
                        transaction.retransmit_task.cancel()
                transaction.response = message
            async with transaction.response_wait:
                transaction.response_wait.notify()
        else:  # pragma: no cover
            raise errors.CoAPException("Unknown Message type")