async def send_request(self, request: Request): """ Create the transaction and fill it with the outgoing request. :type request: Request :param request: the request to send :rtype : Transaction :return: the created transaction """ try: host, port = request.destination except TypeError or AttributeError: # pragma: no cover raise errors.CoAPException("Request destination cannot be computed") request.timestamp = time.time() transaction = Transaction(request=request, timestamp=request.timestamp) if transaction.request.type is None: # pragma: no cover raise errors.CoAPException("Request type is not set") if transaction.request.mid is None: transaction.request.mid = self.fetch_mid() key_mid = utils.str_append_hash(host, port, request.mid) self._transactions[key_mid] = transaction key_token = utils.str_append_hash(host, port, request.token) self._transactions_token[key_token] = transaction logger.debug("send_request - {0}".format(request)) return transaction
async def receive_request(self, request: Request) -> Transaction: """ Handle duplicates and store received messages. :type request: Request :param request: the incoming request :rtype : Transaction :return: the edited transaction """ logger.debug("receive_request - {0}".format(request)) try: host, port = request.source except TypeError or AttributeError: # pragma: no cover raise errors.CoAPException("Request Source cannot be computed") key_mid = utils.str_append_hash(host, port, request.mid) key_token = utils.str_append_hash(host, port, request.token) transaction = self._transactions.get(key_mid, None) if transaction is not None: from_token = self._transactions_token.get(key_token, None) if from_token is None: logger.warning("Duplicated message with different Token") raise errors.ProtocolError(msg="Tokens does not match", mid=transaction.request.mid) transaction.request.duplicated = True else: request.timestamp = time.time() transaction = Transaction(request=request, timestamp=request.timestamp) self._transactions[key_mid] = transaction self._transactions_token[key_token] = transaction return transaction
async def receive_empty(self, empty, transaction): """ Manage the observe feature to remove a client in case of a RST message received in reply to a notification. :type empty: Message :param empty: the received message :type transaction: Transaction :param transaction: the transaction that owns the notification message :rtype : Transaction :return: the modified transaction """ if empty.type == defines.Type.RST: 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) logger.info("Remove Subscriber") try: del self._relations[key_token] except KeyError: # pragma: no cover logger.exception("Subscriber was not registered") transaction.completed = True return transaction
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 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
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
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")
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
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_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 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