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
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
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
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
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
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
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
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")
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.")
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")
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
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")
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")
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")
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
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")
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)
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
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)
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
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
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
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
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
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
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
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
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
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
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")