async def _retransmit(self, transaction: Transaction, message: Message, future_time: float, retransmit_count: int): try: if message.type == defines.Type.CON: while retransmit_count < defines.MAX_RETRANSMIT and \ (not message.acknowledged and not message.rejected) \ and not transaction.retransmit_stop: await asyncio.sleep(future_time) if not message.acknowledged and not message.rejected: retransmit_count += 1 future_time *= 2 logger.error(f"Retransmit message #{retransmit_count}, next attempt in {future_time}") await self._send_datagram(message) if message.acknowledged or message.rejected: message.timeouts = False else: logger.error("Give up on message {message}".format(message=message.line_print)) message.timeouts = True if message.observe is not None: await self._observeLayer.remove_subscriber(message) transaction.retransmit_stop = False except asyncio.CancelledError: logger.debug("_retransmit cancelled")
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 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 test_td_coap_obs_13(self): client, server = await self.start_client_server() print("TD_COAP_OBS_13") path = "/obs-large" token = utils.generate_random_hex(2) req = Request() req.code = defines.Code.GET req.uri_path = path req.type = defines.Type.CON req.mid = random.randint(1, 1000) req.destination = self.server_address req.token = token req.observe = 0 expected = Response() expected.type = defines.Type.ACK expected.mid = req.mid expected.code = defines.Code.CONTENT expected.token = token expected.observe = 2 expected.source = "127.0.0.1", 5683 expected.payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sollicitudin fermentum ornare. " \ "Cras accumsan tellus quis dui lacinia eleifend. Proin ultrices rutrum orci vitae luctus. " \ "Nullam malesuada pretium elit, at aliquam odio vehicula in. Etiam nec maximus elit. " \ "Etiam at erat ac ex ornare feugiat. Curabitur sed malesuada orci, id aliquet nunc. Phasellus " \ "nec leo luctus, blandit lorem sit amet, interdum metus. Duis efficitur volutpat magna, ac " \ "ultricies nibh aliquet sit amet. Etiam tempor egestas augue in hendrerit. Nunc eget augue " \ "ultricies, dignissim lacus et, vulputate dolor. Nulla eros odio, fringilla vel massa ut, " \ "facilisis cursus quam. Fusce faucibus lobortis congue. Fusce consectetur porta neque, id " \ "sollicitudin velit maximus eu. Sed pharetra leo quam, vel finibus turpis cursus ac. " \ "Aenean ac nisi massa. Cras commodo arcu nec ante tristique ullamcorper. Quisque eu hendrerit" \ " urna. Cras fringilla eros ut nunc maximus, non porta nisl mollis. Aliquam in rutrum massa." \ " Praesent tristique turpis dui, at ultri" expected.block2 = (0, 1, 1024) transaction = await client.send_request(req) ret = await client.receive_response(transaction, 10) if ret != expected: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) self.assertEqual(ret, expected) req = Request() req.code = defines.Code.GET req.uri_path = path req.type = defines.Type.CON req.mid = random.randint(1, 1000) req.destination = self.server_address req.token = token req.block2 = (1, 0, 1024) expected = Response() expected.type = defines.Type.ACK expected.mid = req.mid expected.code = defines.Code.CONTENT expected.payload = "cies lorem fermentum at. Vivamus sit amet ornare neque, " \ "a imperdiet nisl. Quisque a iaculis libero, id tempus lacus. " \ "Aenean convallis est non justo consectetur, a hendrerit enim consequat. In accumsan ante " \ "a egestas luctus. Etiam quis neque nec eros vestibulum faucibus. Nunc viverra ipsum " \ "lectus, vel scelerisque dui dictum a. Ut orci enim, ultrices a ultrices nec, pharetra " \ "in quam. Donec accumsan sit amet eros eget fermentum." \ "Vivamus ut odio ac odio malesuada accumsan. Aenean vehicula diam at tempus ornare. " \ "Phasellus dictum mauris a mi consequat, vitae mattis nulla fringilla. Ut laoreet " \ "tellus in nisl efficitur, a luctus justo tempus. Fusce finibus libero eget velit " \ "finibus iaculis. Morbi rhoncus purus vel vestibulum ullamcorper. Sed ac metus in urna " \ "fermentum feugiat. Nulla nunc diam, sodales aliquam mi id, varius porta nisl. Praesent " \ "vel nibh ac turpis rutrum laoreet at non odio. Phasellus ut posuere mi. Suspendisse " \ "malesuada velit nec mauris convallis porta. Vivamus sed ultrices sapien, at cras amet." expected.token = token expected.block2 = (1, 0, 1024) expected.source = "127.0.0.1", 5683 transaction = await client.send_request(req) ret = await client.receive_response(transaction, 10) if ret != expected: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) self.assertEqual(ret, expected) expected = Response() expected.type = defines.Type.CON expected.mid = self.server_mid + 2 expected.code = defines.Code.CONTENT expected.token = token expected.observe = 3 expected.source = "127.0.0.1", 5683 expected.payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sollicitudin fermentum ornare. " \ "Cras accumsan tellus quis dui lacinia eleifend. Proin ultrices rutrum orci vitae luctus. " \ "Nullam malesuada pretium elit, at aliquam odio vehicula in. Etiam nec maximus elit. " \ "Etiam at erat ac ex ornare feugiat. Curabitur sed malesuada orci, id aliquet nunc. Phasellus " \ "nec leo luctus, blandit lorem sit amet, interdum metus. Duis efficitur volutpat magna, ac " \ "ultricies nibh aliquet sit amet. Etiam tempor egestas augue in hendrerit. Nunc eget augue " \ "ultricies, dignissim lacus et, vulputate dolor. Nulla eros odio, fringilla vel massa ut, " \ "facilisis cursus quam. Fusce faucibus lobortis congue. Fusce consectetur porta neque, id " \ "sollicitudin velit maximus eu. Sed pharetra leo quam, vel finibus turpis cursus ac. " \ "Aenean ac nisi massa. Cras commodo arcu nec ante tristique ullamcorper. Quisque eu hendrerit" \ " urna. Cras fringilla eros ut nunc maximus, non porta nisl mollis. Aliquam in rutrum massa." \ " Praesent tristique turpis dui, at ultri" expected.block2 = (0, 1, 1024) transaction.response = None ret = await client.receive_response(transaction, 10) req = Message() req.code = defines.Code.EMPTY req.type = defines.Type.ACK req.mid = self.server_mid + 2 req.destination = self.server_address req.token = token transaction = await client.send_request(req) if ret == expected: print("PASS") else: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) print(ret.pretty_print()) self.assertEqual(ret, expected) self.stop_client_server(client, server)
async def test_td_coap_obs_12(self): client, server = await self.start_client_server() print("TD_COAP_OBS_12") path = "/obs" token = utils.generate_random_hex(2) req = Request() req.code = defines.Code.GET req.uri_path = path req.type = defines.Type.CON req.mid = random.randint(1, 1000) req.destination = self.server_address req.token = token req.observe = 0 expected = Response() expected.type = defines.Type.ACK expected.mid = req.mid expected.code = defines.Code.CONTENT expected.payload = "5" expected.token = token expected.observe = 2 expected.source = "127.0.0.1", 5683 transaction = await client.send_request(req) ret = await client.receive_response(transaction, 10) if ret != expected: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) self.assertEqual(ret, expected) expected = Response() expected.type = defines.Type.CON expected.mid = self.server_mid + 1 expected.code = defines.Code.CONTENT expected.payload = "6" expected.token = token expected.observe = 3 expected.source = "127.0.0.1", 5683 transaction.response = None ret = await client.receive_response(transaction, 10) if ret != expected: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) self.assertEqual(ret, expected) req = Message() req.code = defines.Code.EMPTY req.type = defines.Type.ACK req.mid = self.server_mid req.destination = self.server_address req.token = token transaction = await client.send_request(req) req = Request() req.code = defines.Code.GET req.uri_path = path req.type = defines.Type.CON req.mid = random.randint(1, 1000) req.destination = self.server_address req.observe = 1 req.token = token expected = Response() expected.type = defines.Type.ACK expected.mid = req.mid expected.code = defines.Code.CONTENT expected.token = token expected.source = self.server_address expected.payload = "6" transaction = await client.send_request(req) ret = await client.receive_response(transaction, 10) if ret == expected: print("PASS") else: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) print(ret.pretty_print()) self.assertEqual(ret, expected) self.stop_client_server(client, server)
async def test_td_coap_obs_08(self): client, server = await self.start_client_server() print("TD_COAP_OBS_08") path = "/obs" token = utils.generate_random_hex(2) req = Request() req.code = defines.Code.GET req.uri_path = path req.type = defines.Type.CON req.mid = random.randint(1, 1000) req.destination = self.server_address req.token = token req.observe = 0 expected = Response() expected.type = defines.Type.ACK expected.mid = req.mid expected.code = defines.Code.CONTENT expected.payload = "5" expected.token = token expected.observe = 2 expected.source = "127.0.0.1", 5683 transaction = await client.send_request(req) ret = await client.receive_response(transaction, 10) if ret != expected: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) self.assertEqual(ret, expected) else: print("PASS-EXCHANGE-01") expected = Response() expected.type = defines.Type.CON expected.mid = self.server_mid + 1 expected.code = defines.Code.CONTENT expected.payload = "6" expected.token = token expected.observe = 3 expected.source = "127.0.0.1", 5683 transaction.response = None ret = await client.receive_response(transaction, 10) if ret != expected: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) self.assertEqual(ret, expected) else: print("PASS-EXCHANGE-02") req = Message() req.code = defines.Code.EMPTY req.type = defines.Type.ACK req.mid = self.server_mid + 1 req.destination = self.server_address req.token = token transaction = await client.send_request(req) token2 = utils.generate_random_hex(2) req = Request() req.code = defines.Code.PUT req.uri_path = path req.type = defines.Type.CON req.mid = random.randint(1, 1000) req.destination = self.server_address req.token = token2 req.payload = "{ \"value\": 100}" req.content_type = defines.ContentType.application_json expected = Response() expected.type = defines.Type.ACK expected.mid = req.mid expected.code = defines.Code.CHANGED expected.token = token2 expected.source = self.server_address transaction2 = await client.send_request(req) ret = await client.receive_response(transaction2, 10) if ret != expected: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) self.assertEqual(ret, expected) else: print("PASS-EXCHANGE-03") expected = Response() expected.type = defines.Type.CON expected.mid = self.server_mid + 4 expected.code = defines.Code.NOT_ACCEPTABLE expected.token = token expected.source = self.server_address expected.payload = "Content-Type changed" transaction.response = None ret = await client.receive_response(transaction, 10) if ret == expected: print("PASS") else: print("Received: {0}".format(ret)) print("Expected: {0}".format(expected)) print(ret.pretty_print()) self.assertEqual(ret, expected) self.stop_client_server(client, server)
async def _handler(self, data, addr): try: transaction, msg_type = await self._handle_datagram(data, addr) await self.handle_message(transaction, msg_type) except errors.PongException as e: if e.message is not None: # check if it is ping if e.message.type == defines.Type.CON: rst = Message() rst.destination = addr rst.type = defines.Type.RST rst.mid = e.message.mid await self._send_datagram(rst) except errors.ProtocolError as e: ''' From RFC 7252, Section 4.2 reject the message if the recipient lacks context to process the message properly, including situations where the message is Empty, uses a code with a reserved class (1, 6, or 7), or has a message format error. Rejecting a Confirmable message is effected by sending a matching Reset message and otherwise ignoring it. From RFC 7252, Section 4.3 A recipient MUST reject the message if it lacks context to process the message properly, including the case where the message is Empty, uses a code with a reserved class (1, 6, or 7), or has a message format error. Rejecting a Non- confirmable message MAY involve sending a matching Reset message ''' rst = Message() rst.destination = addr rst.type = defines.Type.RST rst.mid = e.mid rst.payload = e.msg await self._send_datagram(rst) except errors.InternalError as e: if e.transaction.separate_task is not None: e.transaction.separate_task.cancel() del e.transaction.separate_task e.transaction.response = Response() e.transaction.response.destination = addr e.transaction.response.code = e.response_code e.transaction.response.payload = e.msg transaction = await self._messageLayer.send_response(e.transaction) await self._send_datagram(transaction.response) logger.error(e.msg) except errors.ObserveError as e: if e.transaction is not None: if e.transaction.separate_task is not None: e.transaction.separate_task.cancel() del e.transaction.separate_task e.transaction.response.payload = e.msg e.transaction.response.clear_options() e.transaction.response.type = defines.Type.CON e.transaction.response.code = e.response_code e.transaction = await self._messageLayer.send_response(e.transaction) await self._send_datagram(e.transaction.response) logger.error("Observe Error") except errors.CoAPException as e: logger.error(e.msg) except Exception as e: logger.exception(e)