diff --git a/coapthon/client/coap.py b/coapthon/client/coap.py index f199b2c..5a3747c 100644 --- a/coapthon/client/coap.py +++ b/coapthon/client/coap.py @@ -65,6 +65,13 @@ class CoAP(object): self._receiver_thread = None + def purge_transactions(self, timeout_time=defines.EXCHANGE_LIFETIME): + """ + Clean old transactions + + """ + self._messageLayer.purge(timeout_time) + def close(self): """ Stop the client. @@ -96,16 +103,21 @@ class CoAP(object): assert isinstance(c, int) self._currentMID = c - def send_message(self, message): + def send_message(self, message, no_response=False): """ Prepare a message to send on the UDP socket. Eventually set retransmissions. :param message: the message to send + :param no_response: whether to await a response from the request """ if isinstance(message, Request): request = self._requestLayer.send_request(message) request = self._observeLayer.send_request(request) request = self._blockLayer.send_request(request) + if no_response: + # don't add the send message to the message layer transactions + self.send_datagram(request) + return transaction = self._messageLayer.send_request(request) self.send_datagram(transaction.request) if transaction.request.type == defines.Types["CON"]: @@ -238,7 +250,7 @@ class CoAP(object): while not self.stopped.isSet(): self._socket.settimeout(0.1) try: - datagram, addr = self._socket.recvfrom(1152) + datagram, addr = self._socket.recvfrom(1500) except socket.timeout: # pragma: no cover continue except Exception as e: # pragma: no cover diff --git a/coapthon/client/helperclient.py b/coapthon/client/helperclient.py index d204b68..eb79191 100644 --- a/coapthon/client/helperclient.py +++ b/coapthon/client/helperclient.py @@ -223,17 +223,26 @@ class HelperClient(object): :param request: the request to send :param callback: the callback function to invoke upon response :param timeout: the timeout of the request + :param no_response: whether to await a response from the request :return: the response """ if callback is not None: thread = threading.Thread(target=self._thread_body, args=(request, callback)) thread.start() else: - self.protocol.send_message(request) + self.protocol.send_message(request, no_response=no_response) if no_response: return try: - response = self.queue.get(block=True, timeout=timeout) + while True: + response = self.queue.get(block=True, timeout=timeout) + if response is not None: + if response.mid == request.mid: + return response + if response.type == defines.Types["NON"]: + return response + else: + return response except Empty: #if timeout is set response = None diff --git a/coapthon/defines.py b/coapthon/defines.py index 09fff43..172d058 100644 --- a/coapthon/defines.py +++ b/coapthon/defines.py @@ -125,7 +125,7 @@ class OptionRegistry(object): LOCATION_QUERY = OptionItem(20,"Location-Query",STRING, True, None) BLOCK2 = OptionItem(23, "Block2", INTEGER, False, None) BLOCK1 = OptionItem(27, "Block1", INTEGER, False, None) - SIZE2 = OptionItem(28, "Size2", INTEGER, False, None) + SIZE2 = OptionItem(28, "Size2", INTEGER, False, 0) PROXY_URI = OptionItem(35, "Proxy-Uri", STRING, False, None) PROXY_SCHEME = OptionItem(39, "Proxy-Schema", STRING, False, None) SIZE1 = OptionItem(60, "Size1", INTEGER, False, None) diff --git a/coapthon/layers/blocklayer.py b/coapthon/layers/blocklayer.py index dd96142..cc7bec1 100644 --- a/coapthon/layers/blocklayer.py +++ b/coapthon/layers/blocklayer.py @@ -60,7 +60,7 @@ class BlockLayer(object): del transaction.request.block2 else: # early negotiation - byte = 0 + byte = num * size self._block2_receive[key_token] = BlockItem(byte, num, m, size) del transaction.request.block2 @@ -233,6 +233,7 @@ class BlockLayer(object): 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 @@ -240,6 +241,7 @@ class BlockLayer(object): 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) diff --git a/coapthon/layers/forwardLayer.py b/coapthon/layers/forwardLayer.py index 22f521a..b7363f2 100644 --- a/coapthon/layers/forwardLayer.py +++ b/coapthon/layers/forwardLayer.py @@ -53,11 +53,12 @@ class ForwardLayer(object): :rtype : Transaction :return: the edited transaction """ + wkc_resource_is_defined = defines.DISCOVERY_URL in self._server.root path = str("/" + transaction.request.uri_path) transaction.response = Response() transaction.response.destination = transaction.request.source transaction.response.token = transaction.request.token - if path == defines.DISCOVERY_URL: + if path == defines.DISCOVERY_URL and not wkc_resource_is_defined: transaction = self._server.resourceLayer.discover(transaction) else: new = False diff --git a/coapthon/layers/messagelayer.py b/coapthon/layers/messagelayer.py index c46ea96..966a5f7 100644 --- a/coapthon/layers/messagelayer.py +++ b/coapthon/layers/messagelayer.py @@ -42,17 +42,17 @@ class MessageLayer(object): self._current_mid %= 65535 return current_mid - def purge(self): + def purge(self, timeout_time=defines.EXCHANGE_LIFETIME): for k in list(self._transactions.keys()): now = time.time() transaction = self._transactions[k] - if transaction.timestamp + defines.EXCHANGE_LIFETIME < now: + if transaction.timestamp + timeout_time < now: logger.debug("Delete transaction") del self._transactions[k] for k in list(self._transactions_token.keys()): now = time.time() transaction = self._transactions_token[k] - if transaction.timestamp + defines.EXCHANGE_LIFETIME < now: + if transaction.timestamp + timeout_time < now: logger.debug("Delete transaction") del self._transactions_token[k] diff --git a/coapthon/layers/requestlayer.py b/coapthon/layers/requestlayer.py index 0286b4c..e864436 100644 --- a/coapthon/layers/requestlayer.py +++ b/coapthon/layers/requestlayer.py @@ -52,11 +52,12 @@ class RequestLayer(object): :rtype : Transaction :return: the edited transaction with the response to the request """ + wkc_resource_is_defined = defines.DISCOVERY_URL in self._server.root path = str("/" + transaction.request.uri_path) transaction.response = Response() transaction.response.destination = transaction.request.source transaction.response.token = transaction.request.token - if path == defines.DISCOVERY_URL: + if path == defines.DISCOVERY_URL and not wkc_resource_is_defined: transaction = self._server.resourceLayer.discover(transaction) else: try: diff --git a/coapthon/messages/message.py b/coapthon/messages/message.py index 6364bf8..98e4a76 100644 --- a/coapthon/messages/message.py +++ b/coapthon/messages/message.py @@ -119,17 +119,16 @@ class Message(object): """ Set the Token of the message. - :type value: String + :type value: Bytes :param value: the Token :raise AttributeError: if value is longer than 256 """ if value is None: self._token = value return - if isinstance(value, int): - value = bytes([value]) if not isinstance(value, bytes): - value = bytes(value, "utf-8") + value = bytes(value) + if len(value) > 256: raise AttributeError self._token = value @@ -646,8 +645,7 @@ class Message(object): Delete the Block2 option. """ self.del_option_by_number(defines.OptionRegistry.BLOCK2.number) - - @property + def size1(self): value = None for option in self.options: @@ -668,14 +666,24 @@ class Message(object): @property def size2(self): + """ + Get the Size2 option. + + :return: the Size2 value + """ value = None for option in self.options: if option.number == defines.OptionRegistry.SIZE2.number: - value = option.value if option.value is not None else 0 + value = option.value return value @size2.setter def size2(self, value): + """ + Set the Size2 option. + + :param value: the Block2 value + """ option = Option() option.number = defines.OptionRegistry.SIZE2.number option.value = value @@ -683,6 +691,9 @@ class Message(object): @size2.deleter def size2(self): + """ + Delete the Size2 option. + """ self.del_option_by_number(defines.OptionRegistry.SIZE2.number) @property diff --git a/coapthon/serializer.py b/coapthon/serializer.py index a92726f..2f4683f 100644 --- a/coapthon/serializer.py +++ b/coapthon/serializer.py @@ -129,6 +129,9 @@ class Serializer(object): return defines.Codes.BAD_REQUEST.number except struct.error: return defines.Codes.BAD_REQUEST.number + except UnicodeDecodeError as e: + logger.debug(e) + return defines.Codes.BAD_REQUEST.number @staticmethod def serialize(message): @@ -154,10 +157,8 @@ class Serializer(object): values = [tmp, message.code, message.mid] if message.token is not None and tkl > 0: - - for b in message.token: - fmt += "B" - values.append(b) + fmt += "%ss" % tkl + values.append(message.token) options = Serializer.as_sorted_list(message.options) # already sorted lastoptionnumber = 0 diff --git a/coapthon/utils.py b/coapthon/utils.py index dd99122..20948e4 100644 --- a/coapthon/utils.py +++ b/coapthon/utils.py @@ -201,3 +201,6 @@ class Tree(object): def __delitem__(self, key): del self.tree[key] + + def __contains__(self, item): + return item in self.tree diff --git a/exampleresources.py b/exampleresources.py index 0330829..d98a89b 100644 --- a/exampleresources.py +++ b/exampleresources.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import time from coapthon import defines