async def receive_request(self, request): """ Handle duplicates and store received messages. :type request: Request :param request: the incoming request :rtype : Transaction :return: the edited transaction """ logger.info("receive_request - " + str(request)) try: host, port = request.source except AttributeError: return if request.multicast: key_mid = request.mid key_token = request.token # skip duplicated from net interfaces if key_token in list(self._transactions_token.keys()): # Duplicated multicast request self._transactions_token[key_token].request.duplicated = True return self._transactions_token[key_token] else: key_mid = utils.str_append_hash(host, port, request.mid) key_token = utils.str_append_hash(host, port, request.token) if key_mid in list(self._transactions.keys()): # Duplicated self._transactions[key_mid].request.duplicated = True return self._transactions[key_mid] request.timestamp = time.time() transaction = Transaction(request=request, timestamp=request.timestamp) async with transaction.lock: self._transactions[key_mid] = transaction self._transactions_token[key_token] = transaction 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: # Observe request host, port = transaction.request.source key_token = utils.str_append_hash(host, port, transaction.request.token) 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) elif transaction.request.observe == 1: host, port = transaction.request.source key_token = utils.str_append_hash(host, port, transaction.request.token) logger.info("Remove Subscriber") try: del self._relations[key_token] except KeyError: pass return transaction
def send_request(self, request): """ Handles the Blocks option in a outgoing request. :type request: Request :param request: the outgoing request :return: the edited request """ assert isinstance(request, Request) if request.block1 or (request.payload is not None and len(request.payload) > defines.MAX_PAYLOAD): host, port = request.destination 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 # correct m m = 0 if ((num * size) + size) > len(request.payload) else 1 del request.size1 request.size1 = len(request.payload) self._block1_sent[key_token] = BlockItem(size, num, m, size, request.payload, request.content_type) request.payload = request.payload[0:size] del request.block1 request.block1 = (num, m, size) elif request.block2: host, port = request.destination key_token = utils.str_append_hash(host, port, request.token) num, m, size = request.block2 item = BlockItem(size, num, m, size, "", None) self._block2_sent[key_token] = item return request return request
def receive_empty(self, message): """ Pair ACKs with requests. :type message: Message :param message: the received message :rtype : Transaction :return: the transaction to which the message belongs to """ logger.info("receive_empty - " + str(message)) try: host, port = message.source except AttributeError: return all_coap_nodes = defines.ALL_COAP_NODES_IPV6 if socket.getaddrinfo(host, None)[0][0] == socket.AF_INET6 else defines.ALL_COAP_NODES key_mid = utils.str_append_hash(host, port, message.mid) key_mid_multicast = utils.str_append_hash(all_coap_nodes, port, message.mid) key_token = utils.str_append_hash(host, port, message.token) key_token_multicast = utils.str_append_hash(all_coap_nodes, port, message.token) if key_mid in list(self._transactions.keys()): transaction = self._transactions[key_mid] 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] else: logger.warning("Un-Matched incoming empty message " + str(host) + ":" + str(port)) return None if message.type == defines.Types["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.Types["RST"]: if not transaction.request.acknowledged: transaction.request.rejected = True elif not transaction.response.acknowledged: transaction.response.rejected = True elif message.type == defines.Types["CON"]: #implicit ACK (might have been lost) logger.debug("Implicit ACK on received CON for waiting transaction") transaction.request.acknowledged = True else: logger.warning("Unhandled message type...") if transaction.retransmit_stop is not None: transaction.retransmit_stop.set() return transaction
def send_response(self, 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 """ logger.info("send_response - " + str(transaction.response)) if transaction.response.type is None: if transaction.request.type == defines.Types["CON"] and not transaction.request.acknowledged: transaction.response.type = defines.Types["ACK"] transaction.response.mid = transaction.request.mid transaction.response.acknowledged = True transaction.completed = True elif transaction.request.type == defines.Types["NON"]: transaction.response.type = defines.Types["NON"] else: transaction.response.type = defines.Types["CON"] transaction.response.token = transaction.request.token if transaction.response.mid is None: transaction.response.mid = self.fetch_mid() try: host, port = transaction.response.destination except AttributeError: return key_mid = utils.str_append_hash(host, port, transaction.response.mid) self._transactions[key_mid] = transaction transaction.request.acknowledged = True return transaction
def receive_response(self, 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.info("receive_response - " + str(response)) try: host, port = response.source except AttributeError: return # all_coap_nodes = defines.ALL_COAP_NODES_IPV6 if socket.getaddrinfo(host, None)[0][0] == socket.AF_INET6 else defines.ALL_COAP_NODES key_mid = utils.str_append_hash(host, port, response.mid) # key_mid_multicast = utils.str_append_hash(all_coap_nodes, port, response.mid) key_token = utils.str_append_hash(host, port, response.token) key_token_multicast = utils.str_append_hash(response.destination[0], response.destination[1], response.token) if key_mid in list(self._transactions.keys()): transaction = self._transactions[key_mid] if response.token != transaction.request.token: logger.warning("Tokens does not match - response message " + str(host) + ":" + str(port)) return None, False 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("Tokens does not match - response message " + str(host) + ":" + str(port)) return None, False else: logger.warning("Un-Matched incoming response message " + str(host) + ":" + str(port)) return None, False send_ack = False if response.type == defines.Types["CON"]: send_ack = True transaction.request.acknowledged = True transaction.completed = True transaction.response = response if transaction.retransmit_stop is not None: transaction.retransmit_stop.set() return transaction, send_ack
def send_request(self, 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 """ logger.info("send_request - " + str(request)) assert isinstance(request, Request) try: host, port = request.destination except AttributeError: return request.timestamp = time.time() transaction = Transaction(request=request, timestamp=request.timestamp) if transaction.request.type is None: transaction.request.type = defines.Types["CON"] if transaction.request.mid is None: transaction.request.mid = self.fetch_mid() if transaction.request.token is None: transaction.request.token = self.fetch_token() if request.multicast: key_token = utils.str_append_hash(request.source[0], request.source[1], request.token) self._transactions_token[key_token] = transaction return self._transactions_token[key_token] else: 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 return self._transactions[key_mid]
def remove_subscriber(self, message): """ Remove a subscriber based on token. :param message: the message """ logger.info("Remove Subcriber") host, port = message.destination key_token = utils.str_append_hash(host, port, message.token) try: self._relations[key_token].transaction.completed = True del self._relations[key_token] except KeyError: logger.warning("No Subscriber")
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 """ host, port = message.destination key_token = utils.str_append_hash(host, port, message.token) if key_token in self._relations and message.type == defines.Types[ "RST"]: del self._relations[key_token] return message
def receive_response(self, transaction): """ Sets notification's parameters. :type transaction: Transaction :param transaction: the transaction :rtype : Transaction :return: the modified transaction """ host, port = transaction.response.source key_token = utils.str_append_hash(host, port, transaction.response.token) if key_token in self._relations and transaction.response.type == defines.Types[ "CON"]: transaction.notification = True return transaction
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 host, port = request.destination key_token = utils.str_append_hash(host, port, request.token) self._relations[key_token] = ObserveItem(time.time(), None, True, None) return request
def send_response(self, 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 """ host, port = transaction.request.source 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) # correct m m = 0 if ((num * size) + size) > len(transaction.response.payload) else 1 # add size2 if requested or if payload is bigger than one datagram del transaction.response.size2 if (transaction.request.size2 is not None and transaction.request.size2 == 0) or \ (transaction.response.payload is not None and len(transaction.response.payload) > defines.MAX_PAYLOAD): transaction.response.size2 = len(transaction.response.payload) transaction.response.payload = transaction.response.payload[byte:byte + size] del transaction.response.block2 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] return transaction
def receive_empty(self, empty, transaction): """ Manage the observe feature to remove a client in case of a RST message receveide 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.Types["RST"]: host, port = transaction.request.source key_token = utils.str_append_hash(host, port, transaction.request.token) logger.info("Remove Subscriber") try: del self._relations[key_token] except KeyError: pass transaction.completed = True return transaction
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 """ host, port = transaction.request.source key_token = utils.str_append_hash(host, port, transaction.request.token) if key_token in self._relations: if transaction.response.code == defines.Codes.CONTENT.number: if transaction.resource is not None and transaction.resource.observable: 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() else: del self._relations[key_token] elif transaction.response.code >= defines.Codes.ERROR_LOWER_BOUND: del self._relations[key_token] return transaction
def receive_request(self, 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 """ if transaction.request.block2 is not None: host, port = transaction.request.source key_token = utils.str_append_hash(host, port, transaction.request.token) 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 del transaction.request.block2 else: # early negotiation byte = num * size self._block2_receive[key_token] = BlockItem(byte, num, m, size) del transaction.request.block2 elif transaction.request.block1 is not None: # POST or PUT host, port = transaction.request.source key_token = utils.str_append_hash(host, port, transaction.request.token) num, m, size = transaction.request.block1 if transaction.request.size1 is not None: # What to do if the size1 is larger than the maximum resource size or the maxium server buffer pass if key_token in self._block1_receive: # n-th block content_type = transaction.request.content_type if num != self._block1_receive[key_token].num \ or content_type != self._block1_receive[key_token].content_type: # Error Incomplete return self.incomplete(transaction) self._block1_receive[key_token].payload += transaction.request.payload else: # first block if num != 0: # Error Incomplete return self.incomplete(transaction) content_type = transaction.request.content_type self._block1_receive[key_token] = BlockItem(size, num, m, size, transaction.request.payload, content_type) if m == 0: transaction.request.payload = self._block1_receive[key_token].payload # end of blockwise del transaction.request.block1 transaction.block_transfer = False del self._block1_receive[key_token] return transaction else: # Continue transaction.block_transfer = True transaction.response = Response() transaction.response.destination = transaction.request.source transaction.response.token = transaction.request.token transaction.response.code = defines.Codes.CONTINUE.number transaction.response.block1 = (num, m, size) 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 return transaction
def send_empty(self, transaction, related, message): """ Manage ACK or RST related to a transaction. Sets if the transaction has been acknowledged or rejected. :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 :type message: Message :param message: the ACK or RST message to send """ logger.info("send_empty - " + str(message)) if transaction is None: try: host, port = message.destination except AttributeError: return key_mid = utils.str_append_hash(host, port, message.mid) key_token = utils.str_append_hash(host, port, message.token) if key_mid in self._transactions: transaction = self._transactions[key_mid] related = transaction.response elif key_token in self._transactions_token: transaction = self._transactions_token[key_token] related = transaction.response else: return message if message.type == defines.Types["ACK"]: if transaction.request == related: transaction.request.acknowledged = True transaction.completed = True message.mid = transaction.request.mid message.code = 0 message.destination = transaction.request.source elif transaction.response == related: transaction.response.acknowledged = True transaction.completed = True message.mid = transaction.response.mid message.code = 0 message.token = transaction.response.token message.destination = transaction.response.source elif message.type == defines.Types["RST"]: if transaction.request == related: transaction.request.rejected = True message._mid = transaction.request.mid if message.mid is None: message.mid = self.fetch_mid() message.code = 0 message.token = transaction.request.token message.destination = transaction.request.source elif transaction.response == related: transaction.response.rejected = True transaction.completed = True message._mid = transaction.response.mid if message.mid is None: message.mid = self.fetch_mid() message.code = 0 message.token = transaction.response.token message.destination = transaction.response.source return message
def receive_response(self, 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 """ host, port = transaction.response.source 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] transaction.block_transfer = True if item.m == 0: transaction.block_transfer = False del transaction.request.block1 return transaction n_num, n_m, n_size = transaction.response.block1 if n_num != item.num: # pragma: no cover logger.warning("Blockwise num acknowledged error, expected " + str(item.num) + " received " + str(n_num)) return None if n_size < item.size: logger.debug("Scale down size, was " + str(item.size) + " become " + str(n_size)) item.size = n_size request = transaction.request del request.mid del request.block1 request.payload = item.payload[item.byte: item.byte+item.size] item.num += 1 item.byte += item.size if len(item.payload) <= item.byte: item.m = 0 else: item.m = 1 request.block1 = (item.num, item.m, item.size) # The original request already has this option set # request.size1 = len(item.payload) elif transaction.response.block2 is not None: num, m, size = transaction.response.block2 logger.error(f"response block2 num:{num} m:{m} token:{key_token}") if m == 1: transaction.block_transfer = True if key_token in self._block2_sent: item = self._block2_sent[key_token] if num != item.num: # pragma: no cover logger.error("Receive unwanted block") return self.error(transaction, defines.Codes.REQUEST_ENTITY_INCOMPLETE.number) if item.content_type is None: item.content_type = transaction.response.content_type if item.content_type != transaction.response.content_type: # pragma: no cover logger.error("Content-type Error") return self.error(transaction, defines.Codes.UNSUPPORTED_CONTENT_FORMAT.number) 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 request = transaction.request del request.mid del request.block2 request.block2 = (item.num, 0, item.size) else: transaction.block_transfer = False if key_token in self._block2_sent: if self._block2_sent[key_token].content_type != transaction.response.content_type: # pragma: no cover logger.error("Content-type Error") return self.error(transaction, defines.Codes.UNSUPPORTED_CONTENT_FORMAT.number) transaction.response.payload = self._block2_sent[key_token].payload + transaction.response.payload del self._block2_sent[key_token] else: transaction.block_transfer = False return transaction