diff --git a/.gitignore b/.gitignore index 7bbc71c..daea7c4 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ wheels/ *.egg-info/ .installed.cfg *.egg +.idea/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..e35629c --- /dev/null +++ b/MANIFEST @@ -0,0 +1,42 @@ +# file GENERATED by distutils, do NOT edit +coapclient.py +coapforwardproxy.py +coapreverseproxy.py +coapserver.py +exampleresources.py +setup.cfg +setup.py +coapthon\__init__.py +coapthon\defines.py +coapthon\serializer.py +coapthon\transaction.py +coapthon\utils.py +coapthon\caching\__init__.py +coapthon\caching\cache.py +coapthon\caching\coapcache.py +coapthon\caching\coaplrucache.py +coapthon\client\__init__.py +coapthon\client\coap.py +coapthon\client\helperclient.py +coapthon\forward_proxy\__init__.py +coapthon\forward_proxy\coap.py +coapthon\layers\__init__.py +coapthon\layers\blocklayer.py +coapthon\layers\cachelayer.py +coapthon\layers\forwardLayer.py +coapthon\layers\messagelayer.py +coapthon\layers\observelayer.py +coapthon\layers\requestlayer.py +coapthon\layers\resourcelayer.py +coapthon\messages\__init__.py +coapthon\messages\message.py +coapthon\messages\option.py +coapthon\messages\request.py +coapthon\messages\response.py +coapthon\resources\__init__.py +coapthon\resources\remoteResource.py +coapthon\resources\resource.py +coapthon\reverse_proxy\__init__.py +coapthon\reverse_proxy\coap.py +coapthon\server\__init__.py +coapthon\server\coap.py diff --git a/cache_test.py b/cache_test.py index 42d8cf5..e105f91 100644 --- a/cache_test.py +++ b/cache_test.py @@ -1,8 +1,9 @@ from queue import Queue import random -import socket import threading import unittest +import time + from coapclient import HelperClient from coapforwardproxy import CoAPForwardProxy from coapserver import CoAPServer @@ -10,8 +11,6 @@ from coapthon import defines from coapthon.messages.option import Option from coapthon.messages.request import Request from coapthon.messages.response import Response -from coapthon.serializer import Serializer -import time __author__ = 'Emilio Vallati' __version__ = "1.0" @@ -24,10 +23,10 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServer("127.0.0.1", 5684) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.proxy = CoAPForwardProxy("127.0.0.1", 5683, cache=True) - self.proxy_thread = threading.Thread(target=self.proxy.listen, args=(10,)) + self.proxy_thread = threading.Thread(target=self.proxy.listen, args=(1,)) self.proxy_thread.start() self.queue = Queue() @@ -459,7 +458,6 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.etag = str(1) - expected.location_path = "etag" exchange3 = (req3, expected) diff --git a/coapclient.py b/coapclient.py index e4d4d9a..bffe958 100644 --- a/coapclient.py +++ b/coapclient.py @@ -59,7 +59,7 @@ def main(): # pragma: no cover "payload_file="]) except getopt.GetoptError as err: # print help information and exit: - print((str(err))) # will print something like "option -a not recognized" + print(str(err)) # will print something like "option -a not recognized" usage() sys.exit(2) for o, a in opts: @@ -107,7 +107,7 @@ def main(): # pragma: no cover usage() sys.exit(2) response = client.get(path) - print((response.pretty_print())) + print(response.pretty_print()) client.stop() elif op == "OBSERVE": if path is None: @@ -122,7 +122,7 @@ def main(): # pragma: no cover usage() sys.exit(2) response = client.delete(path) - print((response.pretty_print())) + print(response.pretty_print()) client.stop() elif op == "POST": if path is None: @@ -134,7 +134,7 @@ def main(): # pragma: no cover usage() sys.exit(2) response = client.post(path, payload) - print((response.pretty_print())) + print(response.pretty_print()) client.stop() elif op == "PUT": if path is None: @@ -146,11 +146,11 @@ def main(): # pragma: no cover usage() sys.exit(2) response = client.put(path, payload) - print((response.pretty_print())) + print(response.pretty_print()) client.stop() elif op == "DISCOVER": response = client.discover() - print((response.pretty_print())) + print(response.pretty_print()) client.stop() else: print("Operation not recognized") diff --git a/coapforwardproxy.py b/coapforwardproxy.py index 9a6e1d5..36eec0a 100644 --- a/coapforwardproxy.py +++ b/coapforwardproxy.py @@ -11,7 +11,7 @@ class CoAPForwardProxy(CoAP): def __init__(self, host, port, multicast=False, cache=False): CoAP.__init__(self, (host, port), multicast=multicast, cache=cache) - print(("CoAP Proxy start on " + host + ":" + str(port))) + print("CoAP Proxy start on " + host + ":" + str(port)) def usage(): # pragma: no cover diff --git a/coapping.py b/coapping.py index 8ba6787..945dd38 100644 --- a/coapping.py +++ b/coapping.py @@ -51,7 +51,7 @@ if __name__ == '__main__': ping_cnt = 0 # global ping cnt print('COAP ping script') - print(('COAP ping to: %s:%s...' % (host, port))) + print('COAP ping to: %s:%s...' % (host, port)) try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -69,8 +69,8 @@ if __name__ == '__main__': msg += struct.pack("B", ping_no) try : - print(('[0x%08X] Send ping:' % (ping_cnt), [hex(ord(c)) for c in msg])) - #Set the whole string + print('[0x%08X] Send ping:' % (ping_cnt), [hex(ord(c)) for c in msg]) + # Set the whole string s.sendto(msg, (host, port)) s.settimeout(2 + sleep_sec) @@ -81,11 +81,10 @@ if __name__ == '__main__': # We need to check if ping peyload counter is the same in reply status = bytes(msg)[3] == bytes(reply)[3] - print(('[0x%08X] Recv ping:' % (ping_cnt), [hex(ord(c)) for c in reply], 'ok' if status else 'fail')) + print('[0x%08X] Recv ping:' % (ping_cnt), [hex(ord(c)) for c in reply], 'ok' if status else 'fail') except socket.error as e: - print(('Error: socket.error: ', str(e))) - #print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1] + print('Error: socket.error: ', str(e)) sleep(3) # Waiting to recover ;) except socket.timeout: print("Error: closing socket") diff --git a/coapreverseproxy.py b/coapreverseproxy.py index b93fb44..943e5dc 100644 --- a/coapreverseproxy.py +++ b/coapreverseproxy.py @@ -12,7 +12,7 @@ class CoAPReverseProxy(CoAP): CoAP.__init__(self, (host, port), xml_file=xml_file, multicast=multicast, starting_mid=starting_mid, cache=cache) - print(("CoAP Proxy start on " + host + ":" + str(port))) + print("CoAP Proxy start on " + host + ":" + str(port)) def usage(): # pragma: no cover diff --git a/coapserver.py b/coapserver.py index aef8b9c..a5ebf57 100644 --- a/coapserver.py +++ b/coapserver.py @@ -26,8 +26,8 @@ class CoAPServer(CoAP): self.add_resource('advanced/', AdvancedResource()) self.add_resource('advancedSeparate/', AdvancedResourceSeparate()) - print(("CoAP Server start on " + host + ":" + str(port))) - print((self.root.dump())) + print("CoAP Server start on " + host + ":" + str(port)) + print(self.root.dump()) def usage(): # pragma: no cover diff --git a/coapthon/caching/cache.py.bak b/coapthon/caching/cache.py.bak deleted file mode 100644 index 0e2391a..0000000 --- a/coapthon/caching/cache.py.bak +++ /dev/null @@ -1,246 +0,0 @@ -import time - -import logging - -from coaplrucache import CoapLRUCache -from coapthon import utils -from coapthon.messages.request import * - -__author__ = 'Emilio Vallati' - -logger = logging.getLogger(__name__) - - -class Cache(object): - - def __init__(self, mode, max_dim): - """ - - :param max_dim: max number of elements in the cache - :param mode: used to differentiate between a cache used in a forward-proxy or in a reverse-proxy - """ - - self.max_dimension = max_dim - self.mode = mode - self.cache = CoapLRUCache(max_dim) - - def cache_add(self, request, response): - """ - checks for full cache and valid code before updating the cache - - :param request: - :param response: - :return: - """ - logger.debug("adding response to the cache") - - """ - checking for valid code -- """ - code = response.code - try: - utils.check_code(code) - except utils.InvalidResponseCode: # pragma no cover - logger.error("Invalid response code") - return - - """ - return if max_age is 0 - """ - if response.max_age == 0: - return - - """ - Initialising new cache element based on the mode and updating the cache - """ - - if self.mode == defines.FORWARD_PROXY: - new_key = CacheKey(request) - else: - new_key = ReverseCacheKey(request) - - logger.debug("MaxAge = {maxage}".format(maxage=response.max_age)) - new_element = CacheElement(new_key, response, request, response.max_age) - - self.cache.update(new_key, new_element) - logger.debug("Cache Size = {size}".format(size=self.cache.debug_print())) - - def search_related(self, request): - logger.debug("Cache Search Request") - if self.cache.is_empty() is True: - logger.debug("Empty Cache") - return None - - """ - extracting everything from the cache - """ - result = [] - items = self.cache.cache.items() - - for key, item in items: - element = self.cache.get(item.key) - logger.debug("Element : {elm}".format(elm=str(element))) - - if request.proxy_uri == element.uri: - result.append(item) - - return result - - def search_response(self, request): - """ - creates a key from the request and searches the cache with it - - :param request: - :return CacheElement: returns None if there's a cache miss - """ - logger.debug("Cache Search Response") - - if self.cache.is_empty() is True: - logger.debug("Empty Cache") - return None - - """ - create a new cache key from the request - """ - - if self.mode == defines.FORWARD_PROXY: - search_key = CacheKey(request) - else: - search_key = ReverseCacheKey(request) - - response = self.cache.get(search_key) - - return response - - def validate(self, request, response): - """ - refreshes a resource when a validation response is received - - :param request: - :param response: - :return: - """ - element = self.search_response(request) - if element is not None: - element.cached_response.options = response.options - element.freshness = True - element.max_age = response.max_age - element.creation_time = time.time() - element.uri = request.proxy_uri - - def mark(self, element): - """ - marks the requested resource in the cache as not fresh - :param element: - :return: - """ - logger.debug("Element : {elm}".format(elm=str(element))) - if element is not None: - logger.debug("Mark as not fresh") - element.freshness = False - logger.debug(str(self.cache)) - -""" -class for the element contained in the cache -""" - - -class CacheElement(object): - def __init__(self, cache_key, response, request, max_age=60): - """ - - :param cache_key: the key used to search for the element in the cache - :param response: the server response to store - :param max_age: maximum number of seconds that the resource is considered fresh - """ - self.freshness = True - self.key = cache_key - self.cached_response = response - self.max_age = max_age - self.creation_time = time.time() - self.uri = request.proxy_uri - - def __str__(self): - return "Key: {key}, Fresh: {fresh}, URI: {uri}, MaxAge: {maxage}, CreationTime: {ct}, Response: {resp}"\ - .format(key=str(self.key), fresh=self.freshness, uri=self.uri, maxage=self.max_age, ct=self.creation_time, - resp=str(self.cached_response)) - -""" -class for the key used to search elements in the cache (forward-proxy only) -""" - - -class CacheKey(object): - def __init__(self, request): - """ - - :param request: - """ - logger.debug("Creating Key") - self._payload = request.payload - self._method = request.code - - """ - making a list of the options that do not have a nocachekey option number and are not uri-path, uri-host, uri-port, uri-query - """ - - self._options = [] - for option in request.options: - if (utils.check_nocachekey(option) is False) and (utils.is_uri_option(option.number) is False): - self._options.append(option) - - """ - creating a usable key for the cache structure - """ - - option_str = ', '.join(map(str, self._options)) - self._list = [self._payload, self._method, option_str] - - self.hashkey = ', '.join(map(str, self._list)) - logger.debug(self) - - def __str__(self): - msg = "" - for opt in self._options: - msg += "{name}: {value}, ".format(name=opt.name, value=opt.value) - return "Payload: {p}, Method: {m}, Options: [{o}]".format(p=self._payload, m=self._method, o=msg) - -""" -class for the key used to search elements in the cache (reverse-proxy only) -""" - - -class ReverseCacheKey(object): - def __init__(self, request): - """ - - :param request: - """ - self._payload = request.payload - self._method = request.code - - """ - making a list of the options that do not have a nocachekey option number - """ - - self._options = [] - for option in request.options: - if utils.check_nocachekey(option) is False: - self._options.append(option) - - """ - creating a usable key for the cache structure - """ - - option_str = ', '.join(map(str, self._options)) - self.hashkey = (self._payload, self._method, option_str) - - def __str__(self): - msg = "" - for opt in self._options: - msg += "{name}: {value}, ".format(name=opt.name, value=opt.value) - return "Payload: {p}, Method: {m}, Options: [{o}]".format(p=self._payload, m=self._method, o=msg) - - - - diff --git a/coapthon/caching/coaplrucache.py b/coapthon/caching/coaplrucache.py index 3a536ef..545e8f2 100644 --- a/coapthon/caching/coaplrucache.py +++ b/coapthon/caching/coaplrucache.py @@ -23,8 +23,7 @@ class CoapLRUCache(CoapCache): :param element: :return: """ - logger.debug("updating cache, key: %s, element: %s", \ - key.hashkey, element) + logger.debug("updating cache, key: %s, element: %s", key.hashkey, element) self.cache.update([(key.hashkey, element)]) def get(self, key): diff --git a/coapthon/caching/coaplrucache.py.bak b/coapthon/caching/coaplrucache.py.bak deleted file mode 100644 index dcdd745..0000000 --- a/coapthon/caching/coaplrucache.py.bak +++ /dev/null @@ -1,83 +0,0 @@ -import logging - -from cachetools import LRUCache -from coapthon.caching.coapcache import CoapCache - -__author__ = 'Emilio Vallati' - -logger = logging.getLogger(__name__) - - -class CoapLRUCache(CoapCache): - def __init__(self, max_dim): - """ - - :param max_dim: - """ - self.cache = LRUCache(maxsize=max_dim) - - def update(self, key, element): - """ - - :param key: - :param element: - :return: - """ - logger.debug("updating cache, key: %s, element: %s", \ - key.hashkey, element) - self.cache.update([(key.hashkey, element)]) - - def get(self, key): - """ - - :param key: - :return: CacheElement - """ - try: - response = self.cache[key.hashkey] - except KeyError: - logger.debug("problem here", exc_info=1) - response = None - return response - - def is_full(self): - """ - :return: - """ - if self.cache.currsize == self.cache.maxsize: - return True - return False - - def is_empty(self): - """ - - :return: - """ - - if self.cache.currsize == 0: - return True - return False - - def __str__(self): - msg = [] - for e in self.cache.values(): - msg.append(str(e)) - return "Cache Size: {sz}\n" + "\n".join(msg) - - def debug_print(self): - """ - - :return: - """ - return ("size = %s\n%s" % ( - self.cache.currsize, - '\n'.join([ - ( "element.max age %s\n"\ - "element.uri %s\n"\ - "element.freshness %s" ) % ( - element.max_age, - element.uri, - element.freshness ) - for key, element - in list(self.cache.items()) - ]))) diff --git a/coapthon/client/coap.py b/coapthon/client/coap.py index 3a77e1a..5a3747c 100644 --- a/coapthon/client/coap.py +++ b/coapthon/client/coap.py @@ -1,9 +1,9 @@ -import logging.config -import os +import logging import random import socket import threading import time +import collections from coapthon import defines from coapthon.layers.blocklayer import BlockLayer @@ -14,7 +14,6 @@ from coapthon.messages.message import Message from coapthon.messages.request import Request from coapthon.messages.response import Response from coapthon.serializer import Serializer -import collections __author__ = 'Giacomo Tanganelli' @@ -66,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. @@ -76,7 +82,7 @@ class CoAP(object): event.set() if self._receiver_thread is not None: self._receiver_thread.join() - self._socket.close() + # self._socket.close() @property def current_mid(self): @@ -97,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"]: @@ -149,7 +160,7 @@ class CoAP(object): :param message: the message to send """ host, port = message.destination - logger.debug("send_datagram - " + str(message)) + logger.info("send_datagram - " + str(message)) serializer = Serializer() raw_message = serializer.serialize(message) @@ -264,7 +275,7 @@ class CoAP(object): message = serializer.deserialize(datagram, source) if isinstance(message, Response): - logger.debug("receive_datagram - " + str(message)) + logger.info("receive_datagram - " + str(message)) transaction, send_ack = self._messageLayer.receive_response(message) if transaction is None: # pragma: no cover continue @@ -291,6 +302,7 @@ class CoAP(object): self._messageLayer.receive_empty(message) logger.debug("Exiting receiver Thread due to request") + self._socket.close() def _send_ack(self, transaction): """ diff --git a/coapthon/client/coap.py.bak b/coapthon/client/coap.py.bak deleted file mode 100644 index 3d2a5ba..0000000 --- a/coapthon/client/coap.py.bak +++ /dev/null @@ -1,317 +0,0 @@ -import logging.config -import os -import random -import socket -import threading -import time - -from coapthon import defines -from coapthon.layers.blocklayer import BlockLayer -from coapthon.layers.messagelayer import MessageLayer -from coapthon.layers.observelayer import ObserveLayer -from coapthon.layers.requestlayer import RequestLayer -from coapthon.messages.message import Message -from coapthon.messages.request import Request -from coapthon.messages.response import Response -from coapthon.serializer import Serializer -from coapthon.utils import create_logging - - -__author__ = 'Giacomo Tanganelli' - - -if not os.path.isfile("logging.conf"): - create_logging() - -logger = logging.getLogger(__name__) -logging.config.fileConfig("logging.conf", disable_existing_loggers=False) - - -class CoAP(object): - """ - Client class to perform requests to remote servers. - """ - def __init__(self, server, starting_mid, callback, sock=None, cb_ignore_read_exception=None, cb_ignore_write_exception=None): - """ - Initialize the client. - - :param server: Server address for incoming connections - :param callback:the callback function to be invoked when a response is received - :param starting_mid: used for testing purposes - :param sock: if a socket has been created externally, it can be used directly - :param cb_ignore_read_exception: Callback function to handle exception raised during the socket read operation - :param cb_ignore_write_exception: Callback function to handle exception raised during the socket write operation - """ - self._currentMID = starting_mid - self._server = server - self._callback = callback - self._cb_ignore_read_exception = cb_ignore_read_exception - self._cb_ignore_write_exception = cb_ignore_write_exception - self.stopped = threading.Event() - self.to_be_stopped = [] - - self._messageLayer = MessageLayer(self._currentMID) - self._blockLayer = BlockLayer() - self._observeLayer = ObserveLayer() - self._requestLayer = RequestLayer(self) - - addrinfo = socket.getaddrinfo(self._server[0], None)[0] - - if sock is not None: - self._socket = sock - - elif addrinfo[0] == socket.AF_INET: - self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - - else: - self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - - self._receiver_thread = None - - def close(self): - """ - Stop the client. - - """ - self.stopped.set() - for event in self.to_be_stopped: - event.set() - if self._receiver_thread is not None: - self._receiver_thread.join() - self._socket.close() - - @property - def current_mid(self): - """ - Return the current MID. - - :return: the current mid - """ - return self._currentMID - - @current_mid.setter - def current_mid(self, c): - """ - Set the current MID. - - :param c: the mid to set - """ - assert isinstance(c, int) - self._currentMID = c - - def send_message(self, message): - """ - Prepare a message to send on the UDP socket. Eventually set retransmissions. - - :param message: the message to send - """ - if isinstance(message, Request): - request = self._requestLayer.send_request(message) - request = self._observeLayer.send_request(request) - request = self._blockLayer.send_request(request) - transaction = self._messageLayer.send_request(request) - self.send_datagram(transaction.request) - if transaction.request.type == defines.Types["CON"]: - self._start_retransmission(transaction, transaction.request) - elif isinstance(message, Message): - message = self._observeLayer.send_empty(message) - message = self._messageLayer.send_empty(None, None, message) - self.send_datagram(message) - - @staticmethod - def _wait_for_retransmit_thread(transaction): - """ - Only one retransmit thread at a time, wait for other to finish - - """ - if hasattr(transaction, 'retransmit_thread'): - while transaction.retransmit_thread is not None: - logger.debug("Waiting for retransmit thread to finish ...") - time.sleep(0.01) - continue - - def _send_block_request(self, transaction): - """ - A former request resulted in a block wise transfer. With this method, the block wise transfer - will be continued, including triggering of the retry mechanism. - - :param transaction: The former transaction including the request which should be continued. - """ - transaction = self._messageLayer.send_request(transaction.request) - # ... but don't forget to reset the acknowledge flag - transaction.request.acknowledged = False - self.send_datagram(transaction.request) - if transaction.request.type == defines.Types["CON"]: - self._start_retransmission(transaction, transaction.request) - - def send_datagram(self, message): - """ - Send a message over the UDP socket. - - :param message: the message to send - """ - host, port = message.destination - logger.debug("send_datagram - " + str(message)) - serializer = Serializer() - raw_message = serializer.serialize(message) - - try: - self._socket.sendto(raw_message, (host, port)) - except Exception as e: - if self._cb_ignore_write_exception is not None and callable(self._cb_ignore_write_exception): - if not self._cb_ignore_write_exception(e, self): - raise - - if self._receiver_thread is None or not self._receiver_thread.isAlive(): - self._receiver_thread = threading.Thread(target=self.receive_datagram) - self._receiver_thread.start() - - def _start_retransmission(self, transaction, message): - """ - Start the retransmission task. - - :type transaction: Transaction - :param transaction: the transaction that owns the message that needs retransmission - :type message: Message - :param message: the message that needs the retransmission task - """ - with transaction: - if message.type == defines.Types['CON']: - future_time = random.uniform(defines.ACK_TIMEOUT, (defines.ACK_TIMEOUT * defines.ACK_RANDOM_FACTOR)) - transaction.retransmit_stop = threading.Event() - self.to_be_stopped.append(transaction.retransmit_stop) - transaction.retransmit_thread = threading.Thread(target=self._retransmit, - name=str('%s-Retry-%d' % (threading.current_thread().name, message.mid)), - args=(transaction, message, future_time, 0)) - transaction.retransmit_thread.start() - - def _retransmit(self, transaction, message, future_time, retransmit_count): - """ - Thread function to retransmit the message in the future - - :param transaction: the transaction that owns the message that needs retransmission - :param message: the message that needs the retransmission task - :param future_time: the amount of time to wait before a new attempt - :param retransmit_count: the number of retransmissions - """ - with transaction: - logger.debug("retransmit loop ... enter") - while retransmit_count <= defines.MAX_RETRANSMIT \ - and (not message.acknowledged and not message.rejected) \ - and not transaction.retransmit_stop.isSet(): - transaction.retransmit_stop.wait(timeout=future_time) - if not message.acknowledged and not message.rejected and not transaction.retransmit_stop.isSet(): - retransmit_count += 1 - future_time *= 2 - if retransmit_count < defines.MAX_RETRANSMIT: - logger.debug("retransmit loop ... retransmit Request") - self.send_datagram(message) - - if message.acknowledged or message.rejected: - message.timeouted = False - else: - logger.warning("Give up on message {message}".format(message=message.line_print)) - message.timeouted = True - - # Inform the user, that nothing was received - self._callback(None) - - try: - self.to_be_stopped.remove(transaction.retransmit_stop) - except ValueError: - pass - transaction.retransmit_stop = None - transaction.retransmit_thread = None - - logger.debug("retransmit loop ... exit") - - def receive_datagram(self): - """ - Receive datagram from the UDP socket and invoke the callback function. - """ - logger.debug("Start receiver Thread") - while not self.stopped.isSet(): - self._socket.settimeout(0.1) - try: - datagram, addr = self._socket.recvfrom(1152) - except socket.timeout: # pragma: no cover - continue - except Exception as e: # pragma: no cover - if self._cb_ignore_read_exception is not None and callable(self._cb_ignore_read_exception): - if self._cb_ignore_read_exception(e, self): - continue - return - else: # pragma: no cover - if len(datagram) == 0: - logger.debug("Exiting receiver Thread due to orderly shutdown on server end") - return - - serializer = Serializer() - - try: - host, port = addr - except ValueError: - host, port, tmp1, tmp2 = addr - - source = (host, port) - - message = serializer.deserialize(datagram, source) - - if isinstance(message, Response): - logger.debug("receive_datagram - " + str(message)) - transaction, send_ack = self._messageLayer.receive_response(message) - if transaction is None: # pragma: no cover - continue - self._wait_for_retransmit_thread(transaction) - if send_ack: - self._send_ack(transaction) - self._blockLayer.receive_response(transaction) - if transaction.block_transfer: - self._send_block_request(transaction) - continue - elif transaction is None: # pragma: no cover - self._send_rst(transaction) - return - self._observeLayer.receive_response(transaction) - if transaction.notification: # pragma: no cover - ack = Message() - ack.type = defines.Types['ACK'] - ack = self._messageLayer.send_empty(transaction, transaction.response, ack) - self.send_datagram(ack) - self._callback(transaction.response) - else: - self._callback(transaction.response) - elif isinstance(message, Message): - self._messageLayer.receive_empty(message) - - logger.debug("Exiting receiver Thread due to request") - - def _send_ack(self, transaction): - """ - Sends an ACK message for the response. - - :param transaction: transaction that holds the response - """ - - ack = Message() - ack.type = defines.Types['ACK'] - - if not transaction.response.acknowledged: - ack = self._messageLayer.send_empty(transaction, transaction.response, ack) - self.send_datagram(ack) - - def _send_rst(self, transaction): # pragma: no cover - """ - Sends an RST message for the response. - - :param transaction: transaction that holds the response - """ - - rst = Message() - rst.type = defines.Types['RST'] - - if not transaction.response.acknowledged: - rst = self._messageLayer.send_empty(transaction, transaction.response, rst) - self.send_datagram(rst) 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/client/helperclient.py.bak b/coapthon/client/helperclient.py.bak deleted file mode 100644 index edbaa13..0000000 --- a/coapthon/client/helperclient.py.bak +++ /dev/null @@ -1,236 +0,0 @@ -import random -from multiprocessing import Queue -from Queue import Empty -import threading -from coapthon.messages.message import Message -from coapthon import defines -from coapthon.client.coap import CoAP -from coapthon.messages.request import Request -from coapthon.utils import generate_random_token - -__author__ = 'Giacomo Tanganelli' - - -class HelperClient(object): - """ - Helper Client class to perform requests to remote servers in a simplified way. - """ - def __init__(self, server, sock=None, cb_ignore_read_exception=None, cb_ignore_write_exception=None): - """ - Initialize a client to perform request to a server. - - :param server: the remote CoAP server - :param sock: if a socket has been created externally, it can be used directly - :param cb_ignore_read_exception: Callback function to handle exception raised during the socket read operation - :param cb_ignore_write_exception: Callback function to handle exception raised during the socket write operation - """ - self.server = server - self.protocol = CoAP(self.server, random.randint(1, 65535), self._wait_response, sock=sock, - cb_ignore_read_exception=cb_ignore_read_exception, cb_ignore_write_exception=cb_ignore_write_exception) - self.queue = Queue() - - def _wait_response(self, message): - """ - Private function to get responses from the server. - - :param message: the received message - """ - if message is None or message.code != defines.Codes.CONTINUE.number: - self.queue.put(message) - - def stop(self): - """ - Stop the client. - """ - self.protocol.close() - self.queue.put(None) - - def close(self): - """ - Close the client. - """ - self.stop() - - def _thread_body(self, request, callback): - """ - Private function. Send a request, wait for response and call the callback function. - - :param request: the request to send - :param callback: the callback function - """ - self.protocol.send_message(request) - while not self.protocol.stopped.isSet(): - response = self.queue.get(block=True) - callback(response) - - def cancel_observing(self, response, send_rst): # pragma: no cover - """ - Delete observing on the remote server. - - :param response: the last received response - :param send_rst: if explicitly send RST message - :type send_rst: bool - """ - if send_rst: - message = Message() - message.destination = self.server - message.code = defines.Codes.EMPTY.number - message.type = defines.Types["RST"] - message.token = response.token - message.mid = response.mid - self.protocol.send_message(message) - self.stop() - - def get(self, path, callback=None, timeout=None, **kwargs): # pragma: no cover - """ - Perform a GET on a certain path. - - :param path: the path - :param callback: the callback function to invoke upon response - :param timeout: the timeout of the request - :return: the response - """ - request = self.mk_request(defines.Codes.GET, path) - request.token = generate_random_token(2) - - for k, v in kwargs.iteritems(): - if hasattr(request, k): - setattr(request, k, v) - - return self.send_request(request, callback, timeout) - - def observe(self, path, callback, timeout=None, **kwargs): # pragma: no cover - """ - Perform a GET with observe on a certain path. - - :param path: the path - :param callback: the callback function to invoke upon notifications - :param timeout: the timeout of the request - :return: the response to the observe request - """ - request = self.mk_request(defines.Codes.GET, path) - request.observe = 0 - - for k, v in kwargs.iteritems(): - if hasattr(request, k): - setattr(request, k, v) - - return self.send_request(request, callback, timeout) - - def delete(self, path, callback=None, timeout=None, **kwargs): # pragma: no cover - """ - Perform a DELETE on a certain path. - - :param path: the path - :param callback: the callback function to invoke upon response - :param timeout: the timeout of the request - :return: the response - """ - request = self.mk_request(defines.Codes.DELETE, path) - - for k, v in kwargs.iteritems(): - if hasattr(request, k): - setattr(request, k, v) - - return self.send_request(request, callback, timeout) - - def post(self, path, payload, callback=None, timeout=None, **kwargs): # pragma: no cover - """ - Perform a POST on a certain path. - - :param path: the path - :param payload: the request payload - :param callback: the callback function to invoke upon response - :param timeout: the timeout of the request - :return: the response - """ - request = self.mk_request(defines.Codes.POST, path) - request.token = generate_random_token(2) - request.payload = payload - - for k, v in kwargs.iteritems(): - if hasattr(request, k): - setattr(request, k, v) - - return self.send_request(request, callback, timeout) - - def put(self, path, payload, callback=None, timeout=None, **kwargs): # pragma: no cover - """ - Perform a PUT on a certain path. - - :param path: the path - :param payload: the request payload - :param callback: the callback function to invoke upon response - :param timeout: the timeout of the request - :return: the response - """ - request = self.mk_request(defines.Codes.PUT, path) - request.token = generate_random_token(2) - request.payload = payload - - for k, v in kwargs.iteritems(): - if hasattr(request, k): - setattr(request, k, v) - - return self.send_request(request, callback, timeout) - - def discover(self, callback=None, timeout=None, **kwargs): # pragma: no cover - """ - Perform a Discover request on the server. - - :param callback: the callback function to invoke upon response - :param timeout: the timeout of the request - :return: the response - """ - request = self.mk_request(defines.Codes.GET, defines.DISCOVERY_URL) - - for k, v in kwargs.iteritems(): - if hasattr(request, k): - setattr(request, k, v) - - return self.send_request(request, callback, timeout) - - def send_request(self, request, callback=None, timeout=None): # pragma: no cover - """ - Send a request to the remote server. - - :param request: the request to send - :param callback: the callback function to invoke upon response - :param timeout: the timeout of 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) - try: - response = self.queue.get(block=True, timeout=timeout) - except Empty: - #if timeout is set - response = None - return response - - def send_empty(self, empty): # pragma: no cover - """ - Send empty message. - - :param empty: the empty message - """ - self.protocol.send_message(empty) - - def mk_request(self, method, path): - """ - Create a request. - - :param method: the CoAP method - :param path: the path of the request - :return: the request - """ - request = Request() - request.destination = self.server - request.code = method.number - request.uri_path = path - return request - - diff --git a/coapthon/defines.py b/coapthon/defines.py index 9b081ea..09fff43 100644 --- a/coapthon/defines.py +++ b/coapthon/defines.py @@ -1,5 +1,6 @@ +# -*- coding: utf-8 -*- + import collections -import array import struct __author__ = 'Giacomo Tanganelli' @@ -124,6 +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) 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) @@ -147,6 +149,7 @@ class OptionRegistry(object): 20: LOCATION_QUERY, 23: BLOCK2, 27: BLOCK1, + 28: SIZE2, 35: PROXY_URI, 39: PROXY_SCHEME, 60: SIZE1, @@ -165,7 +168,7 @@ class OptionRegistry(object): :return: option flags :rtype: 3-tuple (critical, unsafe, no-cache) """ - opt_bytes = array.array('B', '\0\0') + opt_bytes = bytearray(2) if option_num < 256: s = struct.Struct("!B") s.pack_into(opt_bytes, 0, option_num) diff --git a/coapthon/forward_proxy/coap.py b/coapthon/forward_proxy/coap.py index b448cca..d974872 100644 --- a/coapthon/forward_proxy/coap.py +++ b/coapthon/forward_proxy/coap.py @@ -1,11 +1,9 @@ -import logging.config +import logging import random import socket import struct import threading -import os - from coapthon import defines from coapthon.layers.blocklayer import BlockLayer from coapthon.layers.cachelayer import CacheLayer @@ -88,27 +86,26 @@ class CoAP(object): # Allow multiple copies of this program on one machine # (not strictly needed) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._socket.bind((defines.ALL_COAP_NODES, self.server_address[1])) + self._socket.bind(('', self.server_address[1])) + mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY) self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) - self._unicast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._unicast_socket.bind(self.server_address) else: + # Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant + if not hasattr(socket, 'IPPROTO_IPV6'): + socket.IPPROTO_IPV6 = 41 + self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # Allow multiple copies of this program on one machine # (not strictly needed) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1])) + self._socket.bind(('', self.server_address[1])) addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0] group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0]) mreq = group_bin + struct.pack('@I', 0) self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) - self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._unicast_socket.bind(self.server_address) else: if addrinfo[0] == socket.AF_INET: # IPv4 self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -140,7 +137,7 @@ class CoAP(object): except socket.timeout: continue try: - #Start a new thread not to block other requests + # Start a new thread not to block other requests args = ((data, client_address), ) t = threading.Thread(target=self.receive_datagram, args=args) t.daemon = True @@ -159,7 +156,7 @@ class CoAP(object): self.stopped.set() for event in self.to_be_stopped: event.set() - self._socket.close() + # self._socket.close() def receive_datagram(self, args): """ @@ -189,20 +186,20 @@ class CoAP(object): rst.code = message self.send_datagram(rst) return - logger.debug("receive_datagram - " + str(message)) + logger.info("receive_datagram - " + str(message)) if isinstance(message, Request): transaction = self._messageLayer.receive_request(message) if transaction.request.duplicated and transaction.completed: - logger.debug("message duplicated,transaction completed") + logger.debug("message duplicated, transaction completed") transaction = self._observeLayer.send_response(transaction) transaction = self._blockLayer.send_response(transaction) transaction = self._messageLayer.send_response(transaction) self.send_datagram(transaction.response) return elif transaction.request.duplicated and not transaction.completed: - logger.debug("message duplicated,transaction NOT completed") + logger.debug("message duplicated, transaction NOT completed") self._send_ack(transaction) return @@ -269,7 +266,7 @@ class CoAP(object): """ if not self.stopped.isSet(): host, port = message.destination - logger.debug("send_datagram - " + str(message)) + logger.info("send_datagram - " + str(message)) serializer = Serializer() message = serializer.serialize(message) diff --git a/coapthon/http_proxy/http_coap_proxy.py b/coapthon/http_proxy/http_coap_proxy.py index 83b689c..ec21b03 100644 --- a/coapthon/http_proxy/http_coap_proxy.py +++ b/coapthon/http_proxy/http_coap_proxy.py @@ -175,7 +175,7 @@ class HCProxyHandler(BaseHTTPRequestHandler): logger.debug(payload) coap_response = self.client.put(self.coap_uri.path, payload) self.client.stop() - logger.debug("Server response: %s", coap_response.pretty_print()) + logger.info("Server response: %s", coap_response.pretty_print()) self.set_http_response(coap_response) def do_DELETE(self): @@ -185,7 +185,7 @@ class HCProxyHandler(BaseHTTPRequestHandler): self.do_initial_operations() coap_response = self.client.delete(self.coap_uri.path) self.client.stop() - logger.debug("Server response: %s", coap_response.pretty_print()) + logger.info("Server response: %s", coap_response.pretty_print()) self.set_http_response(coap_response) def do_CONNECT(self): diff --git a/coapthon/http_proxy/http_coap_proxy.py.bak b/coapthon/http_proxy/http_coap_proxy.py.bak deleted file mode 100644 index df07df6..0000000 --- a/coapthon/http_proxy/http_coap_proxy.py.bak +++ /dev/null @@ -1,283 +0,0 @@ -import argparse -import logging - -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -from coapthon.client.helperclient import HelperClient -from coapthon.utils import parse_uri -from coapthon.defines import Codes, DEFAULT_HC_PATH, HC_PROXY_DEFAULT_PORT, COAP_DEFAULT_PORT, LOCALHOST, BAD_REQUEST, \ - NOT_IMPLEMENTED, CoAP_HTTP -from coapthon.defines import COAP_PREFACE -from urlparse import urlparse - -__author__ = "Marco Ieni, Davide Foti" -__email__ = "marcoieni94@gmail.com, davidefoti.uni@gmail.com" - -logger = logging.getLogger(__name__) - -hc_path = DEFAULT_HC_PATH - -""" the class that realizes the HTTP-CoAP Proxy """ - - -class HCProxy: - """ - This program implements an HTTP-CoAP Proxy without using external libraries. - It is assumed that URI is formatted like this: - http://hc_proxy_ip:proxy_port/hc/coap://server_coap_ip:server_coap_port/resource - You can run this program passing the parameters from the command line or you can use the HCProxy class in your own - project. - """ - def __init__(self, path=DEFAULT_HC_PATH, hc_port=HC_PROXY_DEFAULT_PORT, ip=LOCALHOST, - coap_port=COAP_DEFAULT_PORT): - """ - Initialize the HC proxy. - - :param path: the path of the hc_proxy server - :param hc_port: the port of the hc_proxy server - :param ip: the ip of the hc_proxy server - :param coap_port: the coap server port you want to reach - """ - global hc_path - hc_path = HCProxy.get_formatted_path(path) - self.hc_port = hc_port - self.ip = ip - self.coap_port = coap_port - - def run(self): - """ - Start the proxy. - """ - server_address = (self.ip, self.hc_port) - hc_proxy = HTTPServer(server_address, HCProxyHandler) - logger.info('Starting HTTP-CoAP Proxy...') - hc_proxy.serve_forever() # the server listen to http://ip:hc_port/path - - @staticmethod - def get_formatted_path(path): - """ - Uniform the path string - - :param path: the path - :return: the uniform path - """ - if path[0] != '/': - path = '/' + path - if path[-1] != '/': - path = '{0}/'.format(path) - return path - - -class CoapUri: # this class takes the URI from the HTTP URI - """ Class that can manage and inbox the CoAP URI """ - def __init__(self, coap_uri): - self.uri = coap_uri - self.host, self.port, self.path = parse_uri(coap_uri) - - def get_uri_as_list(self): - """ - Split the uri into :///;?# - - :return: the split uri - """ - return urlparse(self.uri) - - def get_payload(self): - """ - Return the query string of the uri. - - :return: the query string as a list - """ - temp = self.get_uri_as_list() - query_string = temp[4] - if query_string == "": - return None # Bad request error code - query_string_as_list = str.split(query_string, "=") - return query_string_as_list[1] - - def __str__(self): - return self.uri - - -class HCProxyHandler(BaseHTTPRequestHandler): - """ It maps the requests from HTTP to CoAP """ - coap_uri = None - client = None - - def set_coap_uri(self): - """ - Create a CoAP Uri - """ - self.coap_uri = CoapUri(self.path[len(hc_path):]) - - def do_initial_operations(self): - """ - Setup the client for interact with remote server - """ - if not self.request_hc_path_corresponds(): - # the http URI of the request is not the same of the one specified by the admin for the hc proxy, - # so I do not answer - # For example the admin setup the http proxy URI like: "http://127.0.0.1:8080:/my_hc_path/" and the URI of - # the requests asks for "http://127.0.0.1:8080:/another_hc_path/" - return - self.set_coap_uri() - self.client = HelperClient(server=(self.coap_uri.host, self.coap_uri.port)) - - def do_GET(self): - """ - Perform a GET request - """ - self.do_initial_operations() - coap_response = self.client.get(self.coap_uri.path) - self.client.stop() - logger.info("Server response: %s", coap_response.pretty_print()) - self.set_http_response(coap_response) - - def do_HEAD(self): - """ - Perform a HEAD request - """ - self.do_initial_operations() - # the HEAD method is not present in CoAP, so we treat it - # like if it was a GET and then we exclude the body from the response - # with send_body=False we say that we do not need the body, because it is a HEAD request - coap_response = self.client.get(self.coap_uri.path) - self.client.stop() - logger.info("Server response: %s", coap_response.pretty_print()) - self.set_http_header(coap_response) - - def do_POST(self): - """ - Perform a POST request - """ - # Doesn't do anything with posted data - # print "uri: ", self.client_address, self.path - self.do_initial_operations() - payload = self.coap_uri.get_payload() - if payload is None: - logger.error("BAD POST REQUEST") - self.send_error(BAD_REQUEST) - return - coap_response = self.client.post(self.coap_uri.path, payload) - self.client.stop() - logger.info("Server response: %s", coap_response.pretty_print()) - self.set_http_response(coap_response) - - def do_PUT(self): - """ - Perform a PUT request - """ - self.do_initial_operations() - payload = self.coap_uri.get_payload() - if payload is None: - logger.error("BAD PUT REQUEST") - self.send_error(BAD_REQUEST) - return - logger.debug(payload) - coap_response = self.client.put(self.coap_uri.path, payload) - self.client.stop() - logger.debug("Server response: %s", coap_response.pretty_print()) - self.set_http_response(coap_response) - - def do_DELETE(self): - """ - Perform a DELETE request - """ - self.do_initial_operations() - coap_response = self.client.delete(self.coap_uri.path) - self.client.stop() - logger.debug("Server response: %s", coap_response.pretty_print()) - self.set_http_response(coap_response) - - def do_CONNECT(self): - """ - Perform a CONNECT request. Reply with error, not implemented in CoAP - """ - self.send_error(NOT_IMPLEMENTED) - - def do_OPTIONS(self): - """ - Perform a OPTIONS request. Reply with error, not implemented in CoAP - """ - self.send_error(NOT_IMPLEMENTED) - - def do_TRACE(self): - """ - Perform a TRACE request. Reply with error, not implemented in CoAP - """ - self.send_error(NOT_IMPLEMENTED) - - def request_hc_path_corresponds(self): - """ - Tells if the hc path of the request corresponds to that specified by the admin - - :return: a boolean that says if it corresponds or not - """ - uri_path = self.path.split(COAP_PREFACE) - request_hc_path = uri_path[0] - logger.debug("HCPATH: %s", hc_path) - # print HC_PATH - logger.debug("URI: %s", request_hc_path) - if hc_path != request_hc_path: - return False - else: - return True - - def set_http_header(self, coap_response): - """ - Sets http headers. - - :param coap_response: the coap response - """ - logger.debug( - ("Server: %s\n"\ - "codice risposta: %s\n"\ - "PROXED: %s\n"\ - "payload risposta: %s"), - coap_response.source, - coap_response.code, - CoAP_HTTP[Codes.LIST[coap_response.code].name], - coap_response.payload) - self.send_response(int(CoAP_HTTP[Codes.LIST[coap_response.code].name])) - self.send_header('Content-type', 'text/html') - self.end_headers() - - def set_http_body(self, coap_response): - """ - Set http body. - - :param coap_response: the coap response - """ - if coap_response.payload is not None: - body = "

", coap_response.payload, "

" - self.wfile.write("".join(body)) - else: - self.wfile.write("

None

") - - def set_http_response(self, coap_response): - """ - Set http response. - - :param coap_response: the coap response - """ - self.set_http_header(coap_response) - self.set_http_body(coap_response) - return - - -def get_command_line_args(): - parser = argparse.ArgumentParser(description='Run the HTTP-CoAP Proxy.') - parser.add_argument('-p', dest='path', default=DEFAULT_HC_PATH, - help='the path of the hc_proxy server') - parser.add_argument('-hp', dest='hc_port', default=HC_PROXY_DEFAULT_PORT, - help='the port of the hc_proxy server') - parser.add_argument('-ip', dest='ip', default=LOCALHOST, - help='the ip of the hc_proxy server') - parser.add_argument('-cp', dest='coap_port', default=COAP_DEFAULT_PORT, - help='the coap server port you want to reach') - return parser.parse_args() - - -if __name__ == "__main__": - args = get_command_line_args() - hc_proxy = HCProxy(args.path, int(args.hc_port), args.ip, args.coap_port) - hc_proxy.run() diff --git a/coapthon/layers/blocklayer.py b/coapthon/layers/blocklayer.py index 8f698a3..1980764 100644 --- a/coapthon/layers/blocklayer.py +++ b/coapthon/layers/blocklayer.py @@ -1,5 +1,7 @@ import logging + from coapthon import defines +from coapthon import utils from coapthon.messages.request import Request from coapthon.messages.response import Response @@ -33,10 +35,10 @@ class BlockLayer(object): Handle the Blockwise options. Hides all the exchange to both servers and clients. """ def __init__(self): - self._block1_sent = {} - self._block2_sent = {} - self._block1_receive = {} - self._block2_receive = {} + self._block1_sent = {} # type: dict[hash, BlockItem] + self._block2_sent = {} # type: dict[hash, BlockItem] + self._block1_receive = {} # type: dict[hash, BlockItem] + self._block2_receive = {} # type: dict[hash, BlockItem] def receive_request(self, transaction): """ @@ -49,7 +51,7 @@ class BlockLayer(object): """ if transaction.request.block2 is not None: host, port = transaction.request.source - key_token = hash(str(host) + str(port) + str(transaction.request.token)) + 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 @@ -58,16 +60,20 @@ 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 elif transaction.request.block1 is not None: # POST or PUT host, port = transaction.request.source - key_token = hash(str(host) + str(port) + str(transaction.request.token)) + 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: @@ -118,7 +124,7 @@ class BlockLayer(object): :return: the edited transaction """ host, port = transaction.response.source - key_token = hash(str(host) + str(port) + str(transaction.response.token)) + 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 @@ -145,6 +151,8 @@ class BlockLayer(object): 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 @@ -208,7 +216,7 @@ class BlockLayer(object): :return: the edited transaction """ host, port = transaction.request.source - key_token = hash(str(host) + str(port) + str(transaction.request.token)) + 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: @@ -225,10 +233,13 @@ class BlockLayer(object): self._block2_receive[key_token] = BlockItem(byte, num, m, size) - if len(transaction.response.payload) > (byte + size): - m = 1 - else: - m = 0 + # 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) @@ -251,21 +262,24 @@ class BlockLayer(object): 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 = hash(str(host) + str(port) + str(request.token)) + 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 = hash(str(host) + str(port) + str(request.token)) + 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 diff --git a/coapthon/layers/forwardLayer.py b/coapthon/layers/forwardLayer.py index e36f68d..b7363f2 100644 --- a/coapthon/layers/forwardLayer.py +++ b/coapthon/layers/forwardLayer.py @@ -1,4 +1,5 @@ import copy +import logging from coapthon.messages.request import Request from coapclient import HelperClient from coapthon.messages.response import Response @@ -8,6 +9,8 @@ from coapthon.utils import parse_uri __author__ = 'Giacomo Tanganelli' +logger = logging.getLogger(__name__) + class ForwardLayer(object): """ @@ -50,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 @@ -146,8 +150,10 @@ class ForwardLayer(object): request.destination = transaction.resource.remote_server request.payload = transaction.request.payload request.code = transaction.request.code + logger.info("forward_request - " + str(request)) response = client.send_request(request) client.stop() + logger.info("forward_response - " + str(response)) transaction.response.payload = response.payload transaction.response.code = response.code transaction.response.options = response.options diff --git a/coapthon/layers/messagelayer.py b/coapthon/layers/messagelayer.py index f4cba32..966a5f7 100644 --- a/coapthon/layers/messagelayer.py +++ b/coapthon/layers/messagelayer.py @@ -1,6 +1,9 @@ import logging import random import time +import socket + +from coapthon import utils from coapthon.messages.message import Message from coapthon import defines from coapthon.messages.request import Request @@ -11,15 +14,6 @@ __author__ = 'Giacomo Tanganelli' logger = logging.getLogger(__name__) -def str_append_hash(*args): - """ Convert each argument to a lower case string, appended, then hash """ - ret_hash = "" - for i in args: - ret_hash += str(i).lower() - - return hash(ret_hash) - - class MessageLayer(object): """ Handles matching between messages (Message ID) and request/response (Token) @@ -48,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] @@ -71,13 +65,13 @@ class MessageLayer(object): :rtype : Transaction :return: the edited transaction """ - logger.debug("receive_request - " + str(request)) + logger.info("receive_request - " + str(request)) try: host, port = request.source except AttributeError: return - key_mid = str_append_hash(host, port, request.mid) - key_token = str_append_hash(host, port, request.token) + 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 @@ -100,15 +94,16 @@ class MessageLayer(object): :rtype : Transaction :return: the transaction to which the response belongs to """ - logger.debug("receive_response - " + str(response)) + logger.info("receive_response - " + str(response)) try: host, port = response.source except AttributeError: return - key_mid = str_append_hash(host, port, response.mid) - key_mid_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.mid) - key_token = str_append_hash(host, port, response.token) - key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.token) + 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(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: @@ -146,15 +141,16 @@ class MessageLayer(object): :rtype : Transaction :return: the transaction to which the message belongs to """ - logger.debug("receive_empty - " + str(message)) + logger.info("receive_empty - " + str(message)) try: host, port = message.source except AttributeError: return - key_mid = str_append_hash(host, port, message.mid) - key_mid_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.mid) - key_token = str_append_hash(host, port, message.token) - key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.token) + 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: @@ -198,7 +194,7 @@ class MessageLayer(object): :rtype : Transaction :return: the created transaction """ - logger.debug("send_request - " + str(request)) + logger.info("send_request - " + str(request)) assert isinstance(request, Request) try: host, port = request.destination @@ -213,10 +209,10 @@ class MessageLayer(object): if transaction.request.mid is None: transaction.request.mid = self.fetch_mid() - key_mid = str_append_hash(host, port, request.mid) + key_mid = utils.str_append_hash(host, port, request.mid) self._transactions[key_mid] = transaction - key_token = str_append_hash(host, port, request.token) + key_token = utils.str_append_hash(host, port, request.token) self._transactions_token[key_token] = transaction return self._transactions[key_mid] @@ -230,7 +226,7 @@ class MessageLayer(object): :rtype : Transaction :return: the edited transaction """ - logger.debug("send_response - " + str(transaction.response)) + 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"] @@ -249,7 +245,7 @@ class MessageLayer(object): host, port = transaction.response.destination except AttributeError: return - key_mid = str_append_hash(host, port, transaction.response.mid) + key_mid = utils.str_append_hash(host, port, transaction.response.mid) self._transactions[key_mid] = transaction transaction.request.acknowledged = True @@ -265,14 +261,14 @@ class MessageLayer(object): :type message: Message :param message: the ACK or RST message to send """ - logger.debug("send_empty - " + str(message)) + logger.info("send_empty - " + str(message)) if transaction is None: try: host, port = message.destination except AttributeError: return - key_mid = str_append_hash(host, port, message.mid) - key_token = str_append_hash(host, port, message.token) + 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 diff --git a/coapthon/layers/messagelayer.py.bak b/coapthon/layers/messagelayer.py.bak deleted file mode 100644 index 6c75695..0000000 --- a/coapthon/layers/messagelayer.py.bak +++ /dev/null @@ -1,314 +0,0 @@ -import logging -import random -import time -from coapthon.messages.message import Message -from coapthon import defines -from coapthon.messages.request import Request -from coapthon.transaction import Transaction - -__author__ = 'Giacomo Tanganelli' - -logger = logging.getLogger(__name__) - - -def str_append_hash(*args): - """ Convert each argument to a lower case string, appended, then hash """ - ret_hash = "" - for i in args: - ret_hash += str(i).lower() - - return hash(ret_hash) - - -class MessageLayer(object): - """ - Handles matching between messages (Message ID) and request/response (Token) - """ - def __init__(self, starting_mid): - """ - Set the layer internal structure. - - :param starting_mid: the first mid used to send messages. - """ - self._transactions = {} - self._transactions_token = {} - if starting_mid is not None: - self._current_mid = starting_mid - else: - self._current_mid = random.randint(1, 1000) - - def fetch_mid(self): - """ - Gets the next valid MID. - - :return: the mid to use - """ - current_mid = self._current_mid - self._current_mid += 1 - self._current_mid %= 65535 - return current_mid - - def purge(self): - for k in self._transactions.keys(): - now = time.time() - transaction = self._transactions[k] - if transaction.timestamp + defines.EXCHANGE_LIFETIME < now: - logger.debug("Delete transaction") - del self._transactions[k] - for k in self._transactions_token.keys(): - now = time.time() - transaction = self._transactions_token[k] - if transaction.timestamp + defines.EXCHANGE_LIFETIME < now: - logger.debug("Delete transaction") - del self._transactions_token[k] - - 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.debug("receive_request - " + str(request)) - try: - host, port = request.source - except AttributeError: - return - key_mid = str_append_hash(host, port, request.mid) - key_token = str_append_hash(host, port, request.token) - - if key_mid in self._transactions.keys(): - # Duplicated - self._transactions[key_mid].request.duplicated = True - transaction = self._transactions[key_mid] - else: - request.timestamp = time.time() - transaction = Transaction(request=request, timestamp=request.timestamp) - with transaction: - self._transactions[key_mid] = transaction - self._transactions_token[key_token] = transaction - 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.debug("receive_response - " + str(response)) - try: - host, port = response.source - except AttributeError: - return - key_mid = str_append_hash(host, port, response.mid) - key_mid_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.mid) - key_token = str_append_hash(host, port, response.token) - key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.token) - if key_mid in 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 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 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.debug("receive_empty - " + str(message)) - try: - host, port = message.source - except AttributeError: - return - key_mid = str_append_hash(host, port, message.mid) - key_mid_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.mid) - key_token = str_append_hash(host, port, message.token) - key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.token) - if key_mid in 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 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_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.debug("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() - - key_mid = str_append_hash(host, port, request.mid) - self._transactions[key_mid] = transaction - - key_token = str_append_hash(host, port, request.token) - self._transactions_token[key_token] = transaction - - return self._transactions[key_mid] - - 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.debug("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 = str_append_hash(host, port, transaction.response.mid) - self._transactions[key_mid] = transaction - - transaction.request.acknowledged = True - 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.debug("send_empty - " + str(message)) - if transaction is None: - try: - host, port = message.destination - except AttributeError: - return - key_mid = str_append_hash(host, port, message.mid) - key_token = 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 - 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 - message.code = 0 - message.token = transaction.response.token - message.destination = transaction.response.source - return message diff --git a/coapthon/layers/observelayer.py b/coapthon/layers/observelayer.py index ae9f3fd..2820765 100644 --- a/coapthon/layers/observelayer.py +++ b/coapthon/layers/observelayer.py @@ -1,6 +1,8 @@ import logging import time + from coapthon import defines +from coapthon import utils __author__ = 'Giacomo Tanganelli' @@ -40,7 +42,7 @@ class ObserveLayer(object): if request.observe == 0: # Observe request host, port = request.destination - key_token = hash(str(host) + str(port) + str(request.token)) + key_token = utils.str_append_hash(host, port, request.token) self._relations[key_token] = ObserveItem(time.time(), None, True, None) @@ -56,7 +58,7 @@ class ObserveLayer(object): :return: the modified transaction """ host, port = transaction.response.source - key_token = hash(str(host) + str(port) + str(transaction.response.token)) + 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 @@ -70,7 +72,7 @@ class ObserveLayer(object): :return: the message unmodified """ host, port = message.destination - key_token = hash(str(host) + str(port) + str(message.token)) + 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 @@ -88,7 +90,7 @@ class ObserveLayer(object): if transaction.request.observe == 0: # Observe request host, port = transaction.request.source - key_token = hash(str(host) + str(port) + str(transaction.request.token)) + key_token = utils.str_append_hash(host, port, transaction.request.token) non_counter = 0 if key_token in self._relations: # Renew registration @@ -98,7 +100,7 @@ class ObserveLayer(object): self._relations[key_token] = ObserveItem(time.time(), non_counter, allowed, transaction) elif transaction.request.observe == 1: host, port = transaction.request.source - key_token = hash(str(host) + str(port) + str(transaction.request.token)) + key_token = utils.str_append_hash(host, port, transaction.request.token) logger.info("Remove Subscriber") try: del self._relations[key_token] @@ -120,7 +122,7 @@ class ObserveLayer(object): """ if empty.type == defines.Types["RST"]: host, port = transaction.request.source - key_token = hash(str(host) + str(port) + str(transaction.request.token)) + key_token = utils.str_append_hash(host, port, transaction.request.token) logger.info("Remove Subscriber") try: del self._relations[key_token] @@ -138,7 +140,7 @@ class ObserveLayer(object): :return: the transaction unmodified """ host, port = transaction.request.source - key_token = hash(str(host) + str(port) + str(transaction.request.token)) + 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: @@ -188,9 +190,9 @@ class ObserveLayer(object): :param message: the message """ - logger.debug("Remove Subcriber") + logger.info("Remove Subcriber") host, port = message.destination - key_token = hash(str(host) + str(port) + str(message.token)) + key_token = utils.str_append_hash(host, port, message.token) try: self._relations[key_token].transaction.completed = True del self._relations[key_token] diff --git a/coapthon/layers/observelayer.py.bak b/coapthon/layers/observelayer.py.bak deleted file mode 100644 index c02e6e7..0000000 --- a/coapthon/layers/observelayer.py.bak +++ /dev/null @@ -1,199 +0,0 @@ -import logging -import time -from coapthon import defines - -__author__ = 'Giacomo Tanganelli' - -logger = logging.getLogger(__name__) - - -class ObserveItem(object): - def __init__(self, timestamp, non_counter, allowed, transaction): - """ - Data structure for the Observe option - - :param timestamp: the timestamop of last message sent - :param non_counter: the number of NON notification sent - :param allowed: if the client is allowed as observer - :param transaction: the transaction - """ - self.timestamp = timestamp - self.non_counter = non_counter - self.allowed = allowed - self.transaction = transaction - - -class ObserveLayer(object): - """ - Manage the observing feature. It store observing relationships. - """ - def __init__(self): - self._relations = {} - - 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 = hash(str(host) + str(port) + str(request.token)) - - self._relations[key_token] = ObserveItem(time.time(), None, True, None) - - return request - - 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 = hash(str(host) + str(port) + str(transaction.response.token)) - if key_token in self._relations and transaction.response.type == defines.Types["CON"]: - transaction.notification = True - return transaction - - 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 = hash(str(host) + str(port) + str(message.token)) - if key_token in self._relations and message.type == defines.Types["RST"]: - del self._relations[key_token] - return message - - 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 = hash(str(host) + str(port) + str(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 = hash(str(host) + str(port) + str(transaction.request.token)) - logger.info("Remove Subscriber") - try: - del self._relations[key_token] - except KeyError: - pass - - 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 = hash(str(host) + str(port) + str(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 = hash(str(host) + str(port) + str(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 notify(self, resource, root=None): - """ - Prepare notification for the resource to all interested observers. - - :rtype: list - :param resource: the resource for which send a new notification - :param root: deprecated - :return: the list of transactions to be notified - """ - ret = [] - if root is not None: - resource_list = root.with_prefix_resource(resource.path) - else: - resource_list = [resource] - for key in self._relations.keys(): - if self._relations[key].transaction.resource in resource_list: - if self._relations[key].non_counter > defines.MAX_NON_NOTIFICATIONS \ - or self._relations[key].transaction.request.type == defines.Types["CON"]: - self._relations[key].transaction.response.type = defines.Types["CON"] - self._relations[key].non_counter = 0 - elif self._relations[key].transaction.request.type == defines.Types["NON"]: - self._relations[key].non_counter += 1 - self._relations[key].transaction.response.type = defines.Types["NON"] - self._relations[key].transaction.resource = resource - del self._relations[key].transaction.response.mid - del self._relations[key].transaction.response.token - ret.append(self._relations[key].transaction) - return ret - - def remove_subscriber(self, message): - """ - Remove a subscriber based on token. - - :param message: the message - """ - logger.debug("Remove Subcriber") - host, port = message.destination - key_token = hash(str(host) + str(port) + str(message.token)) - try: - self._relations[key_token].transaction.completed = True - del self._relations[key_token] - except KeyError: - logger.warning("No Subscriber") - 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/layers/resourcelayer.py b/coapthon/layers/resourcelayer.py index 5e1f659..863d69f 100644 --- a/coapthon/layers/resourcelayer.py +++ b/coapthon/layers/resourcelayer.py @@ -105,10 +105,12 @@ class ResourceLayer(object): if resource.etag is not None: transaction.response.etag = resource.etag - transaction.response.location_path = resource.path + if transaction.response.code == defines.Codes.CREATED.number: + # Only on CREATED according to RFC 7252 Chapter 5.8.2 POST + transaction.response.location_path = resource.path - if resource.location_query is not None and len(resource.location_query) > 0: - transaction.response.location_query = resource.location_query + if resource.location_query is not None and len(resource.location_query) > 0: + transaction.response.location_query = resource.location_query transaction.response.payload = None @@ -229,7 +231,7 @@ class ResourceLayer(object): lp = path parent_resource = self._parent.root[imax] if parent_resource.allow_children: - return self.add_resource(transaction, parent_resource, lp) + return self.add_resource(transaction, parent_resource, lp) else: transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number return transaction diff --git a/coapthon/messages/message.py b/coapthon/messages/message.py index ccded8f..d8159f9 100644 --- a/coapthon/messages/message.py +++ b/coapthon/messages/message.py @@ -1,5 +1,9 @@ -from coapthon.utils import parse_blockwise +# -*- coding: utf-8 -*- + +import binascii + from coapthon import defines +from coapthon import utils from coapthon.messages.option import Option __author__ = 'Giacomo Tanganelli' @@ -124,6 +128,7 @@ class Message(object): return if not isinstance(value, bytes): value = bytes(value) + if len(value) > 256: raise AttributeError self._token = value @@ -454,7 +459,7 @@ class Message(object): for e in etag: option = Option() option.number = defines.OptionRegistry.ETAG.number - if not isinstance(e, bytes): + if not isinstance(e, bytes): e = bytes(e, "utf-8") option.value = e self.add_option(option) @@ -547,7 +552,7 @@ class Message(object): value = None for option in self.options: if option.number == defines.OptionRegistry.BLOCK1.number: - value = parse_blockwise(option.value) + value = utils.parse_blockwise(option.value) return value @block1.setter @@ -599,7 +604,7 @@ class Message(object): value = None for option in self.options: if option.number == defines.OptionRegistry.BLOCK2.number: - value = parse_blockwise(option.value) + value = utils.parse_blockwise(option.value) return value @block2.setter @@ -641,6 +646,44 @@ class Message(object): """ self.del_option_by_number(defines.OptionRegistry.BLOCK2.number) + @property + def size1(self): + value = None + for option in self.options: + if option.number == defines.OptionRegistry.SIZE1.number: + value = option.value if option.value is not None else 0 + return value + + @size1.setter + def size1(self, value): + option = Option() + option.number = defines.OptionRegistry.SIZE1.number + option.value = value + self.add_option(option) + + @size1.deleter + def size1(self): + self.del_option_by_number(defines.OptionRegistry.SIZE1.number) + + @property + def size2(self): + 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 + return value + + @size2.setter + def size2(self, value): + option = Option() + option.number = defines.OptionRegistry.SIZE2.number + option.value = value + self.add_option(option) + + @size2.deleter + def size2(self): + self.del_option_by_number(defines.OptionRegistry.SIZE2.number) + @property def line_print(self): """ @@ -653,11 +696,16 @@ class Message(object): if self._code is None: self._code = defines.Codes.EMPTY.number + token = binascii.hexlify(self._token).decode("utf-8") if self._token is not None else str(None) + msg = "From {source}, To {destination}, {type}-{mid}, {code}-{token}, ["\ .format(source=self._source, destination=self._destination, type=inv_types[self._type], mid=self._mid, - code=defines.Codes.LIST[self._code].name, token=self._token) + code=defines.Codes.LIST[self._code].name, token=token) for opt in self._options: - msg += "{name}: {value}, ".format(name=opt.name, value=opt.value) + if 'Block' in opt.name: + msg += "{name}: {value}, ".format(name=opt.name, value=utils.parse_blockwise(opt.value)) + else: + msg += "{name}: {value}, ".format(name=opt.name, value=opt.value) msg += "]" if self.payload is not None: if isinstance(self.payload, dict): @@ -685,9 +733,9 @@ class Message(object): msg += "MID: " + str(self._mid) + "\n" if self._code is None: self._code = 0 - + token = binascii.hexlify(self._token).decode("utf-8") if self._token is not None else str(None) msg += "Code: " + str(defines.Codes.LIST[self._code].name) + "\n" - msg += "Token: " + str(self._token) + "\n" + msg += "Token: " + token + "\n" for opt in self._options: msg += str(opt) msg += "Payload: " + "\n" diff --git a/coapthon/messages/message.py.bak b/coapthon/messages/message.py.bak deleted file mode 100644 index ea2d0a2..0000000 --- a/coapthon/messages/message.py.bak +++ /dev/null @@ -1,693 +0,0 @@ -from coapthon.utils import parse_blockwise -from coapthon import defines -from coapthon.messages.option import Option - -__author__ = 'Giacomo Tanganelli' - - -class Message(object): - """ - Class to handle the Messages. - """ - def __init__(self): - """ - Data structure that represent a CoAP message - """ - self._type = None - self._mid = None - self._token = None - self._options = [] - self._payload = None - self._destination = None - self._source = None - self._code = None - self._acknowledged = None - self._rejected = None - self._timeouted = None - self._cancelled = None - self._duplicated = None - self._timestamp = None - self._version = 1 - - @property - def version(self): - """ - Return the CoAP version - - :return: the version - """ - return self._version - - @version.setter - def version(self, v): - """ - Sets the CoAP version - - :param v: the version - :raise AttributeError: if value is not 1 - """ - if not isinstance(v, int) or v != 1: - raise AttributeError - self._version = v - - @property - def type(self): - """ - Return the type of the message. - - :return: the type - """ - return self._type - - @type.setter - def type(self, value): - """ - Sets the type of the message. - - :type value: Types - :param value: the type - :raise AttributeError: if value is not a valid type - """ - if value not in defines.Types.values(): - raise AttributeError - self._type = value - - @property - def mid(self): - """ - Return the mid of the message. - - :return: the MID - """ - return self._mid - - @mid.setter - def mid(self, value): - """ - 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: - raise AttributeError - self._mid = value - - @mid.deleter - def mid(self): - """ - Unset the MID of the message. - """ - self._mid = None - - @property - def token(self): - """ - Get the Token of the message. - - :return: the Token - """ - return self._token - - @token.setter - def token(self, value): - """ - 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 - return - if not isinstance(value, str): - value = str(value) - if len(value) > 256: - raise AttributeError - self._token = value - - @token.deleter - def token(self): - """ - Unset the Token of the message. - """ - self._token = None - - @property - def options(self): - """ - Return the options of the CoAP message. - - :rtype: list - :return: the options - """ - return self._options - - @options.setter - def options(self, value): - """ - Set the options of the CoAP message. - - :type value: list - :param value: list of options - """ - if value is None: - value = [] - assert isinstance(value, list) - self._options = value - - @property - def payload(self): - """ - Return the payload. - - :return: the payload - """ - return self._payload - - @payload.setter - def payload(self, value): - """ - Sets the payload of the message and eventually the Content-Type - - :param value: the payload - """ - if isinstance(value, tuple): - content_type, payload = value - self.content_type = content_type - self._payload = payload - else: - self._payload = value - - @property - def destination(self): - """ - Return the destination of the message. - - :rtype: tuple - :return: (ip, port) - """ - return self._destination - - @destination.setter - def destination(self, value): - """ - 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 value is not None and (not isinstance(value, tuple) or len(value)) != 2: - raise AttributeError - self._destination = value - - @property - def source(self): - """ - Return the source of the message. - - :rtype: tuple - :return: (ip, port) - """ - return self._source - - @source.setter - def source(self, value): - """ - Set the source 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: - raise AttributeError - self._source = value - - @property - def code(self): - """ - Return the code of the message. - - :rtype: Codes - :return: the code - """ - return self._code - - @code.setter - def code(self, value): - """ - Set the code of the message. - - :type value: Codes - :param value: the code - :raise AttributeError: if value is not a valid code - """ - if value not in defines.Codes.LIST.keys() and value is not None: - raise AttributeError - self._code = value - - @property - def acknowledged(self): - """ - Checks if is this message has been acknowledged. - - :return: True, if is acknowledged - """ - return self._acknowledged - - @acknowledged.setter - def acknowledged(self, value): - """ - Marks this message as acknowledged. - - :type value: Boolean - :param value: if acknowledged - """ - assert (isinstance(value, bool)) - self._acknowledged = value - if value: - self._timeouted = False - self._rejected = False - self._cancelled = False - - @property - def rejected(self): - """ - Checks if this message has been rejected. - - :return: True, if is rejected - """ - return self._rejected - - @rejected.setter - def rejected(self, value): - """ - Marks this message as rejected. - - :type value: Boolean - :param value: if rejected - """ - assert (isinstance(value, bool)) - self._rejected = value - if value: - self._timeouted = False - self._acknowledged = False - self._cancelled = True - - @property - def timeouted(self): - """ - Checks if this message has timeouted. Confirmable messages in particular - might timeout. - - :return: True, if has timeouted - """ - return self._timeouted - - @timeouted.setter - def timeouted(self, value): - """ - Marks this message as timeouted. Confirmable messages in particular might - timeout. - - :type value: Boolean - :param value: - """ - assert (isinstance(value, bool)) - self._timeouted = value - if value: - self._acknowledged = False - self._rejected = False - self._cancelled = True - - @property - def duplicated(self): - """ - Checks if this message is a duplicate. - - :return: True, if is a duplicate - """ - return self._duplicated - - @duplicated.setter - def duplicated(self, value): - """ - Marks this message as a duplicate. - - :type value: Boolean - :param value: if a duplicate - """ - assert (isinstance(value, bool)) - self._duplicated = value - - @property - def timestamp(self): - """ - Return the timestamp of the message. - """ - return self._timestamp - - @timestamp.setter - def timestamp(self, value): - """ - Set the timestamp of the message. - - :type value: timestamp - :param value: the timestamp - """ - self._timestamp = value - - def _already_in(self, option): - """ - Check if an option is already in the message. - - :type option: Option - :param option: the option to be checked - :return: True if already present, False otherwise - """ - for opt in self._options: - if option.number == opt.number: - return True - return False - - def add_option(self, 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) - repeatable = defines.OptionRegistry.LIST[option.number].repeatable - if not repeatable: - ret = self._already_in(option) - if ret: - raise TypeError("Option : %s is not repeatable", option.name) - else: - self._options.append(option) - else: - self._options.append(option) - - def del_option(self, option): - """ - Delete an option from the message - - :type option: Option - :param option: the option - """ - assert isinstance(option, Option) - while option in list(self._options): - self._options.remove(option) - - def del_option_by_name(self, name): - """ - Delete an option from the message by name - - :type name: String - :param name: option name - """ - for o in list(self._options): - assert isinstance(o, Option) - if o.name == name: - self._options.remove(o) - - def del_option_by_number(self, number): - """ - Delete an option from the message by number - - :type number: Integer - :param number: option naumber - """ - for o in list(self._options): - assert isinstance(o, Option) - if o.number == number: - self._options.remove(o) - - @property - def etag(self): - """ - Get the ETag option of the message. - - :rtype: list - :return: the ETag values or [] if not specified by the request - """ - value = [] - for option in self.options: - if option.number == defines.OptionRegistry.ETAG.number: - value.append(option.value) - return value - - @etag.setter - def etag(self, etag): - """ - Add an ETag option to the message. - - :param etag: the etag - """ - if not isinstance(etag, list): - etag = [etag] - for e in etag: - option = Option() - option.number = defines.OptionRegistry.ETAG.number - option.value = e - self.add_option(option) - - @etag.deleter - def etag(self): - """ - Delete an ETag from a message. - - """ - self.del_option_by_number(defines.OptionRegistry.ETAG.number) - - @property - def content_type(self): - """ - Get the Content-Type option of a response. - - :return: the Content-Type value or 0 if not specified by the response - """ - value = 0 - for option in self.options: - if option.number == defines.OptionRegistry.CONTENT_TYPE.number: - value = int(option.value) - return value - - @content_type.setter - def content_type(self, content_type): - """ - Set the Content-Type option of a response. - - :type content_type: int - :param content_type: the Content-Type - """ - option = Option() - option.number = defines.OptionRegistry.CONTENT_TYPE.number - option.value = int(content_type) - self.add_option(option) - - @content_type.deleter - def content_type(self): - """ - Delete the Content-Type option of a response. - """ - - self.del_option_by_number(defines.OptionRegistry.CONTENT_TYPE.number) - - @property - def observe(self): - """ - Check if the request is an observing request. - - :return: 0, if the request is an observing request - """ - for option in self.options: - if option.number == defines.OptionRegistry.OBSERVE.number: - # if option.value is None: - # return 0 - if option.value is None: - return 0 - return option.value - return None - - @observe.setter - def observe(self, ob): - """ - Add the Observe option. - - :param ob: observe count - """ - option = Option() - option.number = defines.OptionRegistry.OBSERVE.number - option.value = ob - self.del_option_by_number(defines.OptionRegistry.OBSERVE.number) - self.add_option(option) - - @observe.deleter - def observe(self): - """ - Delete the Observe option. - """ - self.del_option_by_number(defines.OptionRegistry.OBSERVE.number) - - @property - def block1(self): - """ - Get the Block1 option. - - :return: the Block1 value - """ - value = None - for option in self.options: - if option.number == defines.OptionRegistry.BLOCK1.number: - value = parse_blockwise(option.value) - return value - - @block1.setter - def block1(self, value): - """ - Set the Block1 option. - - :param value: the Block1 value - """ - option = Option() - option.number = defines.OptionRegistry.BLOCK1.number - num, m, size = value - if size > 512: - szx = 6 - elif 256 < size <= 512: - szx = 5 - elif 128 < size <= 256: - szx = 4 - elif 64 < size <= 128: - szx = 3 - elif 32 < size <= 64: - szx = 2 - elif 16 < size <= 32: - szx = 1 - else: - szx = 0 - - value = (num << 4) - value |= (m << 3) - value |= szx - - option.value = value - self.add_option(option) - - @block1.deleter - def block1(self): - """ - Delete the Block1 option. - """ - self.del_option_by_number(defines.OptionRegistry.BLOCK1.number) - - @property - def block2(self): - """ - Get the Block2 option. - - :return: the Block2 value - """ - value = None - for option in self.options: - if option.number == defines.OptionRegistry.BLOCK2.number: - value = parse_blockwise(option.value) - return value - - @block2.setter - def block2(self, value): - """ - Set the Block2 option. - - :param value: the Block2 value - """ - option = Option() - option.number = defines.OptionRegistry.BLOCK2.number - num, m, size = value - if size > 512: - szx = 6 - elif 256 < size <= 512: - szx = 5 - elif 128 < size <= 256: - szx = 4 - elif 64 < size <= 128: - szx = 3 - elif 32 < size <= 64: - szx = 2 - elif 16 < size <= 32: - szx = 1 - else: - szx = 0 - - value = (num << 4) - value |= (m << 3) - value |= szx - - option.value = value - self.add_option(option) - - @block2.deleter - def block2(self): - """ - Delete the Block2 option. - """ - self.del_option_by_number(defines.OptionRegistry.BLOCK2.number) - - @property - def line_print(self): - """ - Return the message as a one-line string. - - :return: the string representing the message - """ - inv_types = {v: k for k, v in defines.Types.iteritems()} - - if self._code is None: - self._code = defines.Codes.EMPTY.number - - msg = "From {source}, To {destination}, {type}-{mid}, {code}-{token}, ["\ - .format(source=self._source, destination=self._destination, type=inv_types[self._type], mid=self._mid, - code=defines.Codes.LIST[self._code].name, token=self._token) - for opt in self._options: - msg += "{name}: {value}, ".format(name=opt.name, value=opt.value) - msg += "]" - if self.payload is not None: - if isinstance(self.payload, dict): - tmp = self.payload.values()[0][0:20] - else: - tmp = self.payload[0:20] - msg += " {payload}...{length} bytes".format(payload=tmp, length=len(self.payload)) - else: - msg += " No payload" - return msg - - def __str__(self): - return self.line_print - - def pretty_print(self): - """ - Return the message as a formatted string. - - :return: the string representing the message - """ - msg = "Source: " + str(self._source) + "\n" - msg += "Destination: " + str(self._destination) + "\n" - inv_types = {v: k for k, v in defines.Types.iteritems()} - msg += "Type: " + str(inv_types[self._type]) + "\n" - msg += "MID: " + str(self._mid) + "\n" - if self._code is None: - self._code = 0 - - msg += "Code: " + str(defines.Codes.LIST[self._code].name) + "\n" - msg += "Token: " + str(self._token) + "\n" - for opt in self._options: - msg += str(opt) - msg += "Payload: " + "\n" - msg += str(self._payload) + "\n" - return msg diff --git a/coapthon/messages/option.py b/coapthon/messages/option.py index 9906576..aa6f941 100644 --- a/coapthon/messages/option.py +++ b/coapthon/messages/option.py @@ -42,7 +42,7 @@ class Option(object): :return: the option value in the correct format depending on the option """ if type(self._value) is None: - self._value = bytearray() + self._value = bytes() opt_type = defines.OptionRegistry.LIST[self._number].value_type if opt_type == defines.INTEGER: if byte_len(self._value) > 0: @@ -73,7 +73,6 @@ class Option(object): else: if value is not None: value = bytes(value, "utf-8") - self._value = value @property diff --git a/coapthon/messages/request.py b/coapthon/messages/request.py index f6067d0..7e9db37 100644 --- a/coapthon/messages/request.py +++ b/coapthon/messages/request.py @@ -178,6 +178,20 @@ class Request(Message): return True return False + @if_none_match.setter + def if_none_match(self, value): + """ + Set the If-Match option of a request. + + :param value: True/False + :type values : bool + """ + assert isinstance(value, bool) + option = Option() + option.number = defines.OptionRegistry.IF_NONE_MATCH.number + option.value = None + self.add_option(option) + def add_if_none_match(self): """ Add the if-none-match option to the request. diff --git a/coapthon/messages/request.py.bak b/coapthon/messages/request.py.bak deleted file mode 100644 index 07ee8fa..0000000 --- a/coapthon/messages/request.py.bak +++ /dev/null @@ -1,260 +0,0 @@ -from coapthon import defines -from coapthon.messages.message import Message -from coapthon.messages.option import Option - -__author__ = 'Giacomo Tanganelli' - - -class Request(Message): - """ - Class to handle the Requests. - """ - def __init__(self): - """ - Initialize a Request message. - - """ - super(Request, self).__init__() - - @property - def uri_path(self): - """ - Return the Uri-Path of a request - - :rtype : String - :return: the Uri-Path - """ - value = [] - for option in self.options: - if option.number == defines.OptionRegistry.URI_PATH.number: - value.append(str(option.value) + '/') - value = "".join(value) - value = value[:-1] - return value - - @uri_path.setter - def uri_path(self, path): - """ - Set the Uri-Path of a request. - - :param path: the Uri-Path - """ - path = path.strip("/") - tmp = path.split("?") - path = tmp[0] - paths = path.split("/") - for p in paths: - option = Option() - option.number = defines.OptionRegistry.URI_PATH.number - option.value = p - self.add_option(option) - if len(tmp) > 1: - query = tmp[1] - self.uri_query = query - - @uri_path.deleter - def uri_path(self): - """ - Delete the Uri-Path of a request. - """ - self.del_option_by_number(defines.OptionRegistry.URI_PATH.number) - - @property - def uri_query(self): - """ - Get the Uri-Query of a request. - - :return: the Uri-Query - :rtype : String - :return: the Uri-Query string - """ - value = [] - for option in self.options: - if option.number == defines.OptionRegistry.URI_QUERY.number: - value.append(str(option.value)) - return "&".join(value) - - @uri_query.setter - def uri_query(self, value): - """ - Adds a query. - - :param value: the query - """ - del self.uri_query - queries = value.split("&") - for q in queries: - option = Option() - option.number = defines.OptionRegistry.URI_QUERY.number - option.value = str(q) - self.add_option(option) - - @uri_query.deleter - def uri_query(self): - """ - Delete a query. - """ - self.del_option_by_number(defines.OptionRegistry.URI_QUERY.number) - - @property - def accept(self): - """ - Get the Accept option of a request. - - :return: the Accept value or None if not specified by the request - :rtype : String - """ - for option in self.options: - if option.number == defines.OptionRegistry.ACCEPT.number: - return option.value - return None - - @accept.setter - def accept(self, value): - """ - Add an Accept option to a request. - - :param value: the Accept value - """ - if value in defines.Content_types.values(): - option = Option() - option.number = defines.OptionRegistry.ACCEPT.number - option.value = value - self.add_option(option) - - @accept.deleter - def accept(self): - """ - Delete the Accept options of a request. - """ - self.del_option_by_number(defines.OptionRegistry.ACCEPT.number) - - @property - def if_match(self): - """ - Get the If-Match option of a request. - - :return: the If-Match values or [] if not specified by the request - :rtype : list - """ - value = [] - for option in self.options: - if option.number == defines.OptionRegistry.IF_MATCH.number: - value.append(option.value) - return value - - @if_match.setter - def if_match(self, values): - """ - Set the If-Match option of a request. - - :param values: the If-Match values - :type values : list - """ - assert isinstance(values, list) - for v in values: - option = Option() - option.number = defines.OptionRegistry.IF_MATCH.number - option.value = v - self.add_option(option) - - @if_match.deleter - def if_match(self): - """ - Delete the If-Match options of a request. - """ - self.del_option_by_number(defines.OptionRegistry.IF_MATCH.number) - - @property - def if_none_match(self): - """ - Get the if-none-match option of a request. - - :return: True, if if-none-match is present - :rtype : bool - """ - for option in self.options: - if option.number == defines.OptionRegistry.IF_NONE_MATCH.number: - return True - return False - - def add_if_none_match(self): - """ - Add the if-none-match option to the request. - """ - option = Option() - option.number = defines.OptionRegistry.IF_NONE_MATCH.number - option.value = None - self.add_option(option) - - @if_none_match.deleter - def if_none_match(self): - """ - Delete the if-none-match option in the request. - """ - self.del_option_by_number(defines.OptionRegistry.IF_NONE_MATCH.number) - - @property - def proxy_uri(self): - """ - Get the Proxy-Uri option of a request. - - :return: the Proxy-Uri values or None if not specified by the request - :rtype : String - """ - for option in self.options: - if option.number == defines.OptionRegistry.PROXY_URI.number: - return option.value - return None - - @proxy_uri.setter - def proxy_uri(self, value): - """ - Set the Proxy-Uri option of a request. - - :param value: the Proxy-Uri value - """ - option = Option() - option.number = defines.OptionRegistry.PROXY_URI.number - option.value = str(value) - self.add_option(option) - - @proxy_uri.deleter - def proxy_uri(self): - """ - Delete the Proxy-Uri option of a request. - """ - self.del_option_by_number(defines.OptionRegistry.PROXY_URI.number) - - @property - def proxy_schema(self): - """ - Get the Proxy-Schema option of a request. - - :return: the Proxy-Schema values or None if not specified by the request - :rtype : String - """ - for option in self.options: - if option.number == defines.OptionRegistry.PROXY_SCHEME.number: - return option.value - return None - - @proxy_schema.setter - def proxy_schema(self, value): - """ - Set the Proxy-Schema option of a request. - - :param value: the Proxy-Schema value - """ - option = Option() - option.number = defines.OptionRegistry.PROXY_SCHEME.number - option.value = str(value) - self.add_option(option) - - @proxy_schema.deleter - def proxy_schema(self): - """ - Delete the Proxy-Schema option of a request. - """ - self.del_option_by_number(defines.OptionRegistry.PROXY_SCHEME.number) - diff --git a/coapthon/resources/resource.py.bak b/coapthon/resources/resource.py.bak deleted file mode 100644 index 9a02785..0000000 --- a/coapthon/resources/resource.py.bak +++ /dev/null @@ -1,514 +0,0 @@ -from coapthon import defines - -__author__ = 'Giacomo Tanganelli' - - -class Resource(object): - """ - The Resource class. Represents the base class for all resources. - """ - def __init__(self, name, coap_server=None, visible=True, observable=True, allow_children=True): - """ - Initialize a new Resource. - - :param name: the name of the resource. - :param coap_server: the server that own the resource - :param visible: if the resource is visible - :param observable: if the resource is observable - :param allow_children: if the resource could has children - """ - # The attributes of this resource. - self._attributes = {} - - # The resource name. - self.name = name - - # The resource path. - self.path = None - - # Indicates whether this resource is visible to clients. - self._visible = visible - - # Indicates whether this resource is observable by clients. - self._observable = observable - if self._observable: - self._attributes["obs"] = "" - - self._allow_children = allow_children - - self._observe_count = 1 - - self._payload = {} - - self._content_type = None - - self._etag = [] - - self._location_query = [] - - self._max_age = None - - self._coap_server = coap_server - - self._deleted = False - - self._changed = False - - @property - def deleted(self): - """ - Check if the resource has been deleted. For observing purpose. - - :rtype: bool - :return: True, if deleted - """ - return self._deleted - - @deleted.setter - def deleted(self, b): - """ - Set the deleted parameter. For observing purpose. - - :type b: bool - :param b: True, if deleted - """ - self._deleted = b - - @property - def changed(self): - """ - Check if the resource has been changed. For observing purpose. - - :rtype: bool - :return: True, if changed - """ - return self._changed - - @changed.setter - def changed(self, b): - """ - Set the changed parameter. For observing purpose. - - :type b: bool - :param b: True, if changed - """ - self._changed = b - - @property - def etag(self): - """ - Get the last valid ETag of the resource. - - :return: the last ETag value or None if the resource doesn't have any ETag - """ - if self._etag: - return self._etag[-1] - else: - return None - - @etag.setter - def etag(self, etag): - """ - Set the ETag of the resource. - - :param etag: the ETag - """ - self._etag.append(etag) - - @property - def location_query(self): - """ - Get the Location-Query of a resource. - - :return: the Location-Query - """ - return self._location_query - - @location_query.setter - def location_query(self, lq): - """ - Set the Location-Query. - - :param lq: the Location-Query - """ - self._location_query = lq - - @location_query.deleter - def location_query(self): - """ - Delete the Location-Query. - - """ - self.location_query = [] - - @property - def max_age(self): - """ - Get the Max-Age. - - :return: the Max-Age - """ - return self._max_age - - @max_age.setter - def max_age(self, ma): - """ - Set the Max-Age. - - :param ma: the Max-Age - """ - self._max_age = ma - - @property - def payload(self): - """ - Get the payload of the resource according to the content type specified by required_content_type or - "text/plain" by default. - - :return: the payload. - """ - if self._content_type is not None: - try: - return self._payload[self._content_type] - except KeyError: - raise KeyError("Content-Type not available") - else: - - if defines.Content_types["text/plain"] in self._payload: - return self._payload[defines.Content_types["text/plain"]] - else: - val = self._payload.keys() - return val[0], self._payload[val[0]] - - @payload.setter - def payload(self, p): - """ - Set the payload of the resource. - - :param p: the new payload - """ - if isinstance(p, tuple): - k = p[0] - v = p[1] - self.actual_content_type = k - self._payload[k] = v - else: - self._payload = {defines.Content_types["text/plain"]: p} - - @property - def attributes(self): - """ - Get the CoRE Link Format attribute of the resource. - - :return: the attribute of the resource - """ - return self._attributes - - @attributes.setter - def attributes(self, att): - # TODO assert - """ - Set the CoRE Link Format attribute of the resource. - - :param att: the attributes - """ - self._attributes = att - - @property - def visible(self): - """ - Get if the resource is visible. - - :return: True, if visible - """ - return self._visible - - @property - def observable(self): - """ - Get if the resource is observable. - - :return: True, if observable - """ - return self._observable - - @property - def allow_children(self): - """ - Get if the resource allow children. - - :return: True, if allow children - """ - return self._allow_children - - @property - def observe_count(self): - """ - Get the Observe counter. - - :return: the Observe counter value - """ - return self._observe_count - - @observe_count.setter - def observe_count(self, v): - """ - Set the Observe counter. - - :param v: the Observe counter value - """ - assert isinstance(v, int) - self._observe_count = (v % 65000) - - @property - def actual_content_type(self): - """ - Get the actual required Content-Type. - - :return: the actual required Content-Type. - """ - return self._content_type - - @actual_content_type.setter - def actual_content_type(self, act): - """ - Set the actual required Content-Type. - - :param act: the actual required Content-Type. - """ - self._content_type = act - - @property - def content_type(self): - """ - Get the CoRE Link Format ct attribute of the resource. - - :return: the CoRE Link Format ct attribute - """ - value = "" - lst = self._attributes.get("ct") - if lst is not None and len(lst) > 0: - value = "ct=" - for v in lst: - value += str(v) + " " - if len(value) > 0: - value = value[:-1] - return value - - @content_type.setter - def content_type(self, lst): - """ - Set the CoRE Link Format ct attribute of the resource. - - :param lst: the list of CoRE Link Format ct attribute of the resource - """ - value = [] - if isinstance(lst, str): - ct = defines.Content_types[lst] - self.add_content_type(ct) - elif isinstance(lst, list): - for ct in lst: - self.add_content_type(ct) - - def add_content_type(self, ct): - """ - Add a CoRE Link Format ct attribute to the resource. - - :param ct: the CoRE Link Format ct attribute - """ - lst = self._attributes.get("ct") - if lst is None: - lst = [] - if isinstance(ct, str): - ct = defines.Content_types[ct] - lst.append(ct) - self._attributes["ct"] = lst - - @property - def resource_type(self): - """ - Get the CoRE Link Format rt attribute of the resource. - - :return: the CoRE Link Format rt attribute - """ - value = "rt=" - lst = self._attributes.get("rt") - if lst is None: - value = "" - else: - value += "\"" + str(lst) + "\"" - return value - - @resource_type.setter - def resource_type(self, rt): - """ - Set the CoRE Link Format rt attribute of the resource. - - :param rt: the CoRE Link Format rt attribute - """ - if not isinstance(rt, str): - rt = str(rt) - self._attributes["rt"] = rt - - @property - def interface_type(self): - """ - Get the CoRE Link Format if attribute of the resource. - - :return: the CoRE Link Format if attribute - """ - value = "if=" - lst = self._attributes.get("if") - if lst is None: - value = "" - else: - value += "\"" + str(lst) + "\"" - return value - - @interface_type.setter - def interface_type(self, ift): - """ - Set the CoRE Link Format if attribute of the resource. - - :param ift: the CoRE Link Format if attribute - """ - if not isinstance(ift, str): - ift = str(ift) - self._attributes["if"] = ift - - @property - def maximum_size_estimated(self): - """ - Get the CoRE Link Format sz attribute of the resource. - - :return: the CoRE Link Format sz attribute - """ - value = "sz=" - lst = self._attributes.get("sz") - if lst is None: - value = "" - else: - value += "\"" + str(lst) + "\"" - return value - - @maximum_size_estimated.setter - def maximum_size_estimated(self, sz): - """ - Set the CoRE Link Format sz attribute of the resource. - - :param sz: the CoRE Link Format sz attribute - """ - if not isinstance(sz, str): - sz = str(sz) - self._attributes["sz"] = sz - - @property - def observing(self): - """ - Get the CoRE Link Format obs attribute of the resource. - - :return: the CoRE Link Format obs attribute - """ - if self._observable: - return "obs" - - def init_resource(self, request, res): - """ - Helper function to initialize a new resource. - - :param request: the request that generate the new resource - :param res: the resource - :return: the edited resource - """ - res.location_query = request.uri_query - res.payload = (request.content_type, request.payload) - return res - - def edit_resource(self, request): - """ - Helper function to edit a resource - - :param request: the request that edit the resource - """ - self.location_query = request.uri_query - self.payload = (request.content_type, request.payload) - - def render_GET(self, request): - """ - Method to be redefined to render a GET request on the resource. - - :param request: the request - :return: the resource - """ - raise NotImplementedError - - def render_GET_advanced(self, request, response): - """ - Method to be redefined to render a GET request on the resource. - - :param response: the partially filled response - :param request: the request - :return: a tuple with (the resource, the response) - """ - raise NotImplementedError - - def render_PUT(self, request): - """ - Method to be redefined to render a PUTT request on the resource. - - :param request: the request - :return: the resource - """ - raise NotImplementedError - - def render_PUT_advanced(self, request, response): - """ - Method to be redefined to render a PUTT request on the resource. - - :param response: the partially filled response - :param request: the request - :return: a tuple with (the resource, the response) - """ - raise NotImplementedError - - def render_POST(self, request): - """ - Method to be redefined to render a POST request on the resource. - - :param request: the request - :return: the resource - """ - raise NotImplementedError - - def render_POST_advanced(self, request, response): - """ - Method to be redefined to render a POST request on the resource. - - :param response: the partially filled response - :param request: the request - :return: a tuple with (the resource, the response) - """ - raise NotImplementedError - - def render_DELETE(self, request): - """ - Method to be redefined to render a DELETE request on the resource. - - :param request: the request - :return: a boolean - """ - raise NotImplementedError - - def render_DELETE_advanced(self, request, response): - """ - Method to be redefined to render a DELETE request on the resource. - - :param response: the partially filled response - :param request: the request - :return: a tuple with a boolean and the response - """ - raise NotImplementedError - - - - diff --git a/coapthon/reverse_proxy/coap.py b/coapthon/reverse_proxy/coap.py index 61ba364..fbb7d26 100644 --- a/coapthon/reverse_proxy/coap.py +++ b/coapthon/reverse_proxy/coap.py @@ -1,10 +1,9 @@ -import logging.config +import logging import random import socket import struct import threading import xml.etree.ElementTree as ElementTree - import os import re @@ -81,6 +80,8 @@ class CoAP(object): # Use given socket, could be a DTLS socket self._socket = sock + self.parse_config() + elif self.multicast: # pragma: no cover # Create a socket @@ -94,27 +95,26 @@ class CoAP(object): # Allow multiple copies of this program on one machine # (not strictly needed) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._socket.bind((defines.ALL_COAP_NODES, self.server_address[1])) + self._socket.bind(('', self.server_address[1])) + mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY) self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) - self._unicast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._unicast_socket.bind(self.server_address) else: + # Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant + if not hasattr(socket, 'IPPROTO_IPV6'): + socket.IPPROTO_IPV6 = 41 + self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # Allow multiple copies of this program on one machine # (not strictly needed) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1])) + self._socket.bind(('', self.server_address[1])) addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0] group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0]) mreq = group_bin + struct.pack('@I', 0) self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) - self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._unicast_socket.bind(self.server_address) else: if addrinfo[0] == socket.AF_INET: # IPv4 self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -172,7 +172,8 @@ class CoAP(object): host, port = response.source if response.code == defines.Codes.CONTENT.number: - resource = Resource('server', self, visible=True, observable=False, allow_children=True) + resource = RemoteResource('server', (host, port), "/", + coap_server=self, visible=True, observable=False, allow_children=True) self.add_resource(name, resource) self._mapping[name] = (host, port) self.parse_core_link_format(response.payload, name, (host, port)) @@ -208,8 +209,8 @@ class CoAP(object): dict_att[a[0]] = a[0] link_format = link_format[result.end(0) + 1:] # TODO handle observing - resource = RemoteResource('server', remote_server, path, coap_server=self, visible=True, observable=False, - allow_children=True) + resource = RemoteResource('server', remote_server, path, + coap_server=self, visible=True, observable=False, allow_children=True) resource.attributes = dict_att self.add_resource(base_path + "/" + path, resource) @@ -251,7 +252,7 @@ class CoAP(object): self.stopped.set() for event in self.to_be_stopped: event.set() - self._socket.close() + # self._socket.close() def receive_datagram(self, args): """ @@ -272,7 +273,8 @@ class CoAP(object): rst.code = message self.send_datagram(rst) return - logger.debug("receive_datagram - " + str(message)) + + logger.info("receive_datagram - " + str(message)) if isinstance(message, Request): transaction = self._messageLayer.receive_request(message) @@ -318,6 +320,7 @@ class CoAP(object): transaction = self._blockLayer.send_response(transaction) transaction = self._cacheLayer.send_response(transaction) + else: transaction = self._forwardLayer.receive_request_reverse(transaction) @@ -352,7 +355,7 @@ class CoAP(object): """ if not self.stopped.isSet(): host, port = message.destination - logger.debug("send_datagram - " + str(message)) + logger.info("send_datagram - " + str(message)) serializer = Serializer() message = serializer.serialize(message) diff --git a/coapthon/serializer.py b/coapthon/serializer.py index 31598f6..2f4683f 100644 --- a/coapthon/serializer.py +++ b/coapthon/serializer.py @@ -1,6 +1,7 @@ import logging import struct import ctypes + from coapthon.messages.request import Request from coapthon.messages.response import Response from coapthon.messages.option import Option @@ -67,6 +68,7 @@ class Serializer(object): # the first 4 bits of the byte represent the option delta # delta = self._reader.read(4).uint num, option_length, pos = Serializer.read_option_value_len_from_byte(next_byte, pos, values) + logger.debug("option value (delta): %d len: %d", num, option_length) current_option += num # read option try: @@ -78,8 +80,7 @@ class Serializer(object): else: # If the non-critical option is unknown # (vendor-specific, proprietary) - just skip it - #log.err("unrecognized option %d" % current_option) - pass + logger.warning("unrecognized option %d", current_option) else: if option_length == 0: value = None @@ -110,13 +111,17 @@ class Serializer(object): raise AttributeError("Packet length %s, pos %s" % (length_packet, pos)) message.payload = "" payload = values[pos:] - try: - if message.payload_type == defines.Content_types["application/octet-stream"]: - message.payload = payload - else: + if hasattr(message, 'payload_type') and message.payload_type in [ + defines.Content_types["application/octet-stream"], + defines.Content_types["application/exi"], + defines.Content_types["application/cbor"] + ]: + message.payload = payload + else: + try: message.payload = payload.decode("utf-8") - except AttributeError: - message.payload = payload.decode("utf-8") + except AttributeError: + message.payload = payload pos += len(payload) return message @@ -124,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): @@ -137,7 +145,7 @@ class Serializer(object): """ fmt = "!BBH" - if message.token is None or message.token == "": + if message.token is None: tkl = 0 else: tkl = len(message.token) @@ -195,7 +203,6 @@ class Serializer(object): elif opt_type == defines.STRING: fmt += str(len(bytes(option.value, "utf-8"))) + "s" values.append(bytes(option.value, "utf-8")) - else: # OPAQUE for b in option.value: fmt += "B" @@ -220,9 +227,6 @@ class Serializer(object): else: fmt += str(len(bytes(payload, "utf-8"))) + "s" values.append(bytes(payload, "utf-8")) - # for b in str(payload): - # fmt += "c" - # values.append(bytes(b, "utf-8")) datagram = None if values[1] is None: @@ -260,28 +264,6 @@ class Serializer(object): """ return defines.RESPONSE_CODE_LOWER_BOUND <= code <= defines.RESPONSE_CODE_UPPER_BOUND - @staticmethod - def read_option_value_from_nibble(nibble, pos, values): - """ - Calculates the value used in the extended option fields. - - :param nibble: the 4-bit option header value. - :return: the value calculated from the nibble and the extended option value. - """ - if nibble <= 12: - return nibble, pos - elif nibble == 13: - tmp = struct.unpack("!B", values[pos].to_bytes(1, "big"))[0] + 13 - pos += 1 - return tmp, pos - elif nibble == 14: - s = struct.Struct("!H") - tmp = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269 - pos += 2 - return tmp, pos - else: - raise AttributeError("Unsupported option nibble " + str(nibble)) - @staticmethod def read_option_value_len_from_byte(byte, pos, values): """ @@ -301,7 +283,7 @@ class Serializer(object): pos += 1 elif h_nibble == 14: s = struct.Struct("!H") - value = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269 + value = s.unpack_from(values[pos:pos+2])[0] + 269 pos += 2 else: raise AttributeError("Unsupported option number nibble " + str(h_nibble)) @@ -312,7 +294,8 @@ class Serializer(object): length = struct.unpack("!B", values[pos].to_bytes(1, "big"))[0] + 13 pos += 1 elif l_nibble == 14: - length = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269 + s = struct.Struct("!H") + length = s.unpack_from(values[pos:pos+2])[0] + 269 pos += 2 else: raise AttributeError("Unsupported option length nibble " + str(l_nibble)) @@ -321,7 +304,7 @@ class Serializer(object): @staticmethod def convert_to_raw(number, value, length): """ - Get the value of an option as a ByteArray. + Get the value of an option as bytes. :param number: the option number :param value: the option value @@ -348,11 +331,11 @@ class Serializer(object): if isinstance(value, str): value = str(value) if isinstance(value, str): - return bytearray(value, "utf-8") + return bytes(value, "utf-8") elif isinstance(value, int): return value else: - return bytearray(value) + return bytes(value) @staticmethod def as_sorted_list(options): diff --git a/coapthon/serializer.py.bak b/coapthon/serializer.py.bak deleted file mode 100644 index 1c094ce..0000000 --- a/coapthon/serializer.py.bak +++ /dev/null @@ -1,397 +0,0 @@ -import logging -import struct -import ctypes -from coapthon.messages.request import Request -from coapthon.messages.response import Response -from coapthon.messages.option import Option -from coapthon import defines -from coapthon.messages.message import Message - -__author__ = 'Giacomo Tanganelli' - -logger = logging.getLogger(__name__) - - -class Serializer(object): - """ - Serializer class to serialize and deserialize CoAP message to/from udp streams. - """ - @staticmethod - def deserialize(datagram, source): - """ - De-serialize a stream of byte to a message. - - :param datagram: the incoming udp message - :param source: the source address and port (ip, port) - :return: the message - :rtype: Message - """ - try: - fmt = "!BBH" - pos = struct.calcsize(fmt) - s = struct.Struct(fmt) - values = s.unpack_from(datagram) - first = values[0] - code = values[1] - mid = values[2] - version = (first & 0xC0) >> 6 - message_type = (first & 0x30) >> 4 - token_length = (first & 0x0F) - if Serializer.is_response(code): - message = Response() - message.code = code - elif Serializer.is_request(code): - message = Request() - message.code = code - else: - message = Message() - message.source = source - message.destination = None - message.version = version - message.type = message_type - message.mid = mid - if token_length > 0: - fmt = "%ss" % token_length - s = struct.Struct(fmt) - token_value = s.unpack_from(datagram[pos:])[0] - message.token = token_value - else: - message.token = None - - pos += token_length - current_option = 0 - values = datagram[pos:] - length_packet = len(values) - pos = 0 - while pos < length_packet: - next_byte = struct.unpack("B", values[pos])[0] - pos += 1 - if next_byte != int(defines.PAYLOAD_MARKER): - # the first 4 bits of the byte represent the option delta - # delta = self._reader.read(4).uint - num, option_length, pos = Serializer.read_option_value_len_from_byte(next_byte, pos, values) - current_option += num - # read option - try: - option_item = defines.OptionRegistry.LIST[current_option] - except KeyError: - (opt_critical, _, _) = defines.OptionRegistry.get_option_flags(current_option) - if opt_critical: - raise AttributeError("Critical option %s unknown" % current_option) - else: - # If the non-critical option is unknown - # (vendor-specific, proprietary) - just skip it - #log.err("unrecognized option %d" % current_option) - pass - else: - if option_length == 0: - value = None - elif option_item.value_type == defines.INTEGER: - tmp = values[pos: pos + option_length] - value = 0 - for b in tmp: - value = (value << 8) | struct.unpack("B", b)[0] - elif option_item.value_type == defines.OPAQUE: - tmp = values[pos: pos + option_length] - value = bytearray(tmp) - else: - tmp = values[pos: pos + option_length] - value = "" - for b in tmp: - value += str(b) - - option = Option() - option.number = current_option - option.value = Serializer.convert_to_raw(current_option, value, option_length) - - message.add_option(option) - if option.number == defines.OptionRegistry.CONTENT_TYPE.number: - message.payload_type = option.value - finally: - pos += option_length - else: - - if length_packet <= pos: - # log.err("Payload Marker with no payload") - raise AttributeError("Packet length %s, pos %s" % (length_packet, pos)) - message.payload = "" - payload = values[pos:] - for b in payload: - message.payload += str(b) - pos += 1 - return message - except AttributeError: - return defines.Codes.BAD_REQUEST.number - except struct.error: - return defines.Codes.BAD_REQUEST.number - - @staticmethod - def serialize(message): - """ - Serialize a message to a udp packet - - :type message: Message - :param message: the message to be serialized - :rtype: stream of byte - :return: the message serialized - """ - fmt = "!BBH" - - if message.token is None or message.token == "": - tkl = 0 - else: - tkl = len(message.token) - tmp = (defines.VERSION << 2) - tmp |= message.type - tmp <<= 4 - tmp |= tkl - values = [tmp, message.code, message.mid] - - if message.token is not None and tkl > 0: - - for b in str(message.token): - fmt += "c" - values.append(b) - - options = Serializer.as_sorted_list(message.options) # already sorted - lastoptionnumber = 0 - for option in options: - - # write 4-bit option delta - optiondelta = option.number - lastoptionnumber - optiondeltanibble = Serializer.get_option_nibble(optiondelta) - tmp = (optiondeltanibble << defines.OPTION_DELTA_BITS) - - # write 4-bit option length - optionlength = option.length - optionlengthnibble = Serializer.get_option_nibble(optionlength) - tmp |= optionlengthnibble - fmt += "B" - values.append(tmp) - - # write extended option delta field (0 - 2 bytes) - if optiondeltanibble == 13: - fmt += "B" - values.append(optiondelta - 13) - elif optiondeltanibble == 14: - fmt += "H" - values.append(optiondelta - 269) - - # write extended option length field (0 - 2 bytes) - if optionlengthnibble == 13: - fmt += "B" - values.append(optionlength - 13) - elif optionlengthnibble == 14: - fmt += "H" - values.append(optionlength - 269) - - # write option value - if optionlength > 0: - opt_type = defines.OptionRegistry.LIST[option.number].value_type - if opt_type == defines.INTEGER: - words = Serializer.int_to_words(option.value, optionlength, 8) - for num in range(0, optionlength): - fmt += "B" - values.append(words[num]) - elif opt_type == defines.STRING: - for b in str(option.value): - fmt += "c" - values.append(b) - else: - for b in option.value: - fmt += "B" - values.append(b) - - - # update last option number - lastoptionnumber = option.number - - payload = message.payload - - 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" - values.append(defines.PAYLOAD_MARKER) - - for b in str(payload): - fmt += "c" - values.append(b) - - datagram = None - if values[1] is None: - values[1] = 0 - try: - s = struct.Struct(fmt) - datagram = ctypes.create_string_buffer(s.size) - s.pack_into(datagram, 0, *values) - except struct.error: - # The .exception method will report on the exception encountered - # and provide a traceback. - logging.exception('Failed to pack structure') - - return datagram - - @staticmethod - def is_request(code): - """ - Checks if is request. - - :return: True, if is request - """ - return defines.REQUEST_CODE_LOWER_BOUND <= code <= defines.REQUEST_CODE_UPPER_BOUND - - @staticmethod - def is_response(code): - """ - Checks if is response. - - :return: True, if is response - """ - return defines.RESPONSE_CODE_LOWER_BOUND <= code <= defines.RESPONSE_CODE_UPPER_BOUND - - @staticmethod - def read_option_value_from_nibble(nibble, pos, values): - """ - Calculates the value used in the extended option fields. - - :param nibble: the 4-bit option header value. - :return: the value calculated from the nibble and the extended option value. - """ - if nibble <= 12: - return nibble, pos - elif nibble == 13: - tmp = struct.unpack("!B", values[pos])[0] + 13 - pos += 1 - return tmp, pos - elif nibble == 14: - s = struct.Struct("!H") - tmp = s.unpack_from(values[pos:])[0] + 269 - pos += 2 - return tmp, pos - else: - raise AttributeError("Unsupported option nibble " + str(nibble)) - - @staticmethod - def read_option_value_len_from_byte(byte, pos, values): - """ - Calculates the value and length used in the extended option fields. - - :param byte: 1-byte option header value. - :return: the value and length, calculated from the header including the extended fields. - """ - h_nibble = (byte & 0xF0) >> 4 - l_nibble = byte & 0x0F - value = 0 - length = 0 - if h_nibble <= 12: - value = h_nibble - elif h_nibble == 13: - value = struct.unpack("!B", values[pos])[0] + 13 - pos += 1 - elif h_nibble == 14: - s = struct.Struct("!H") - value = s.unpack_from(values[pos:])[0] + 269 - pos += 2 - else: - raise AttributeError("Unsupported option number nibble " + str(h_nibble)) - - if l_nibble <= 12: - length = l_nibble - elif l_nibble == 13: - length = struct.unpack("!B", values[pos])[0] + 13 - pos += 1 - elif l_nibble == 14: - length = s.unpack_from(values[pos:])[0] + 269 - pos += 2 - else: - raise AttributeError("Unsupported option length nibble " + str(l_nibble)) - return value, length, pos - - @staticmethod - def convert_to_raw(number, value, length): - """ - Get the value of an option as a ByteArray. - - :param number: the option number - :param value: the option value - :param length: the option length - :return: the value of an option as a BitArray - """ - - opt_type = defines.OptionRegistry.LIST[number].value_type - - if length == 0 and opt_type != defines.INTEGER: - return bytearray() - if length == 0 and opt_type == defines.INTEGER: - return 0 - if isinstance(value, tuple): - value = value[0] - if isinstance(value, unicode): - value = str(value) - if isinstance(value, str): - return bytearray(value, "utf-8") - elif isinstance(value, int): - return value - else: - return bytearray(value) - - @staticmethod - def as_sorted_list(options): - """ - Returns all options in a list sorted according to their option numbers. - - :return: the sorted list - """ - if len(options) > 0: - options.sort(None, key=lambda o: o.number) - return options - - @staticmethod - def get_option_nibble(optionvalue): - """ - Returns the 4-bit option header value. - - :param optionvalue: the option value (delta or length) to be encoded. - :return: the 4-bit option header value. - """ - if optionvalue <= 12: - return optionvalue - elif optionvalue <= 255 + 13: - return 13 - elif optionvalue <= 65535 + 269: - return 14 - else: - raise AttributeError("Unsupported option delta " + optionvalue) - - @staticmethod - def int_to_words(int_val, num_words=4, word_size=32): - """ - Convert a int value to bytes. - - :param int_val: an arbitrary length Python integer to be split up. - Network byte order is assumed. Raises an IndexError if width of - integer (in bits) exceeds word_size * num_words. - - :param num_words: number of words expected in return value tuple. - - :param word_size: size/width of individual words (in bits). - - :return: a list of fixed width words based on provided parameters. - """ - max_int = 2 ** (word_size*num_words) - 1 - max_word_size = 2 ** word_size - 1 - - if not 0 <= int_val <= max_int: - raise AttributeError('integer %r is out of bounds!' % hex(int_val)) - - words = [] - for _ in range(num_words): - word = int_val & max_word_size - words.append(int(word)) - int_val >>= word_size - words.reverse() - - return words diff --git a/coapthon/server/coap.py b/coapthon/server/coap.py index 99d2757..5a650df 100644 --- a/coapthon/server/coap.py +++ b/coapthon/server/coap.py @@ -1,9 +1,9 @@ -import logging.config -import os +import logging import random import socket import struct import threading +import collections from coapthon import defines from coapthon.layers.blocklayer import BlockLayer @@ -16,18 +16,13 @@ from coapthon.messages.request import Request from coapthon.messages.response import Response from coapthon.resources.resource import Resource from coapthon.serializer import Serializer -from coapthon.utils import Tree, create_logging -import collections +from coapthon.utils import Tree __author__ = 'Giacomo Tanganelli' -if not os.path.isfile("logging.conf"): - create_logging() - logger = logging.getLogger(__name__) -logging.config.fileConfig("logging.conf", disable_existing_loggers=False) class CoAP(object): @@ -88,20 +83,21 @@ class CoAP(object): mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY) self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) else: + # Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant + if not hasattr(socket, 'IPPROTO_IPV6'): + socket.IPPROTO_IPV6 = 41 + self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # Allow multiple copies of this program on one machine # (not strictly needed) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1])) + self._socket.bind(('', self.server_address[1])) addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0] group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0]) mreq = group_bin + struct.pack('@I', 0) self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) - self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._unicast_socket.bind(self.server_address) else: if addrinfo[0] == socket.AF_INET: # IPv4 self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -154,7 +150,7 @@ class CoAP(object): self.send_datagram(rst) continue - logger.debug("receive_datagram - " + str(message)) + logger.info("receive_datagram - " + str(message)) if isinstance(message, Request): transaction = self._messageLayer.receive_request(message) if transaction.request.duplicated and transaction.completed: @@ -246,7 +242,7 @@ class CoAP(object): """ if not self.stopped.isSet(): host, port = message.destination - logger.debug("send_datagram - " + str(message)) + logger.info("send_datagram - " + str(message)) serializer = Serializer() message = serializer.serialize(message) self._socket.sendto(message, (host, port)) @@ -382,10 +378,11 @@ class CoAP(object): ack = Message() ack.type = defines.Types['ACK'] - # TODO handle mutex on transaction - if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]: - ack = self._messageLayer.send_empty(transaction, transaction.request, ack) - self.send_datagram(ack) + with transaction: + if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]: + ack = self._messageLayer.send_empty(transaction, transaction.request, ack) + if ack.type is not None and ack.mid is not None: + self.send_datagram(ack) def notify(self, resource): """ diff --git a/coapthon/server/coap.py.bak b/coapthon/server/coap.py.bak deleted file mode 100644 index c4db32f..0000000 --- a/coapthon/server/coap.py.bak +++ /dev/null @@ -1,421 +0,0 @@ -import logging.config -import os -import random -import socket -import struct -import threading - -from coapthon import defines -from coapthon.layers.blocklayer import BlockLayer -from coapthon.layers.messagelayer import MessageLayer -from coapthon.layers.observelayer import ObserveLayer -from coapthon.layers.requestlayer import RequestLayer -from coapthon.layers.resourcelayer import ResourceLayer -from coapthon.messages.message import Message -from coapthon.messages.request import Request -from coapthon.messages.response import Response -from coapthon.resources.resource import Resource -from coapthon.serializer import Serializer -from coapthon.utils import Tree -from coapthon.utils import create_logging - - -__author__ = 'Giacomo Tanganelli' - - -if not os.path.isfile("logging.conf"): - create_logging() - -logger = logging.getLogger(__name__) -logging.config.fileConfig("logging.conf", disable_existing_loggers=False) - - -class CoAP(object): - """ - Implementation of the CoAP server - """ - def __init__(self, server_address, multicast=False, starting_mid=None, sock=None, cb_ignore_listen_exception=None): - """ - Initialize the server. - - :param server_address: Server address for incoming connections - :param multicast: if the ip is a multicast address - :param starting_mid: used for testing purposes - :param sock: if a socket has been created externally, it can be used directly - :param cb_ignore_listen_exception: Callback function to handle exception raised during the socket listen operation - """ - self.stopped = threading.Event() - self.stopped.clear() - self.to_be_stopped = [] - self.purge = threading.Thread(target=self.purge) - self.purge.start() - - self._messageLayer = MessageLayer(starting_mid) - self._blockLayer = BlockLayer() - self._observeLayer = ObserveLayer() - self._requestLayer = RequestLayer(self) - self.resourceLayer = ResourceLayer(self) - - # Resource directory - root = Resource('root', self, visible=False, observable=False, allow_children=False) - root.path = '/' - self.root = Tree() - self.root["/"] = root - self._serializer = None - - self.server_address = server_address - self.multicast = multicast - self._cb_ignore_listen_exception = cb_ignore_listen_exception - - addrinfo = socket.getaddrinfo(self.server_address[0], None)[0] - - if sock is not None: - - # Use given socket, could be a DTLS socket - self._socket = sock - - elif self.multicast: # pragma: no cover - - # Create a socket - # self._socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255) - # self._socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) - - # Join group - if addrinfo[0] == socket.AF_INET: # IPv4 - self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - - # Allow multiple copies of this program on one machine - # (not strictly needed) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._socket.bind((defines.ALL_COAP_NODES, self.server_address[1])) - mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY) - self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) - self._unicast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._unicast_socket.bind(self.server_address) - else: - self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - - # Allow multiple copies of this program on one machine - # (not strictly needed) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1])) - - addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0] - group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0]) - mreq = group_bin + struct.pack('@I', 0) - self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) - self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._unicast_socket.bind(self.server_address) - - else: - if addrinfo[0] == socket.AF_INET: # IPv4 - self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - else: - self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - - self._socket.bind(self.server_address) - - def purge(self): - """ - Clean old transactions - - """ - while not self.stopped.isSet(): - self.stopped.wait(timeout=defines.EXCHANGE_LIFETIME) - self._messageLayer.purge() - - def listen(self, timeout=10): - """ - Listen for incoming messages. Timeout is used to check if the server must be switched off. - - :param timeout: Socket Timeout in seconds - """ - self._socket.settimeout(float(timeout)) - while not self.stopped.isSet(): - try: - data, client_address = self._socket.recvfrom(4096) - if len(client_address) > 2: - client_address = (client_address[0], client_address[1]) - except socket.timeout: - continue - except Exception as e: - if self._cb_ignore_listen_exception is not None and callable(self._cb_ignore_listen_exception): - if self._cb_ignore_listen_exception(e, self): - continue - raise - try: - serializer = Serializer() - message = serializer.deserialize(data, client_address) - if isinstance(message, int): - logger.error("receive_datagram - BAD REQUEST") - - rst = Message() - rst.destination = client_address - rst.type = defines.Types["RST"] - rst.code = message - rst.mid = self._messageLayer.fetch_mid() - self.send_datagram(rst) - continue - - logger.debug("receive_datagram - " + str(message)) - if isinstance(message, Request): - transaction = self._messageLayer.receive_request(message) - if transaction.request.duplicated and transaction.completed: - logger.debug("message duplicated, transaction completed") - if transaction.response is not None: - self.send_datagram(transaction.response) - continue - elif transaction.request.duplicated and not transaction.completed: - logger.debug("message duplicated, transaction NOT completed") - self._send_ack(transaction) - continue - args = (transaction, ) - t = threading.Thread(target=self.receive_request, args=args) - t.start() - # self.receive_datagram(data, client_address) - elif isinstance(message, Response): - logger.error("Received response from %s", message.source) - - else: # is Message - transaction = self._messageLayer.receive_empty(message) - if transaction is not None: - with transaction: - self._blockLayer.receive_empty(message, transaction) - self._observeLayer.receive_empty(message, transaction) - - except RuntimeError: - logger.exception("Exception with Executor") - self._socket.close() - - def close(self): - """ - Stop the server. - - """ - logger.info("Stop server") - self.stopped.set() - for event in self.to_be_stopped: - event.set() - - def receive_request(self, transaction): - """ - Handle requests coming from the udp socket. - - :param transaction: the transaction created to manage the request - """ - - with transaction: - - transaction.separate_timer = self._start_separate_timer(transaction) - - self._blockLayer.receive_request(transaction) - - if transaction.block_transfer: - self._stop_separate_timer(transaction.separate_timer) - self._messageLayer.send_response(transaction) - self.send_datagram(transaction.response) - return - - self._observeLayer.receive_request(transaction) - - self._requestLayer.receive_request(transaction) - - if transaction.resource is not None and transaction.resource.changed: - self.notify(transaction.resource) - transaction.resource.changed = False - elif transaction.resource is not None and transaction.resource.deleted: - self.notify(transaction.resource) - transaction.resource.deleted = False - - self._observeLayer.send_response(transaction) - - self._blockLayer.send_response(transaction) - - self._stop_separate_timer(transaction.separate_timer) - - self._messageLayer.send_response(transaction) - - if transaction.response is not None: - if transaction.response.type == defines.Types["CON"]: - self._start_retransmission(transaction, transaction.response) - self.send_datagram(transaction.response) - - def send_datagram(self, message): - """ - Send a message through the udp socket. - - :type message: Message - :param message: the message to send - """ - if not self.stopped.isSet(): - host, port = message.destination - logger.debug("send_datagram - " + str(message)) - serializer = Serializer() - message = serializer.serialize(message) - if self.multicast: - self._unicast_socket.sendto(message, (host, port)) - else: - self._socket.sendto(message, (host, port)) - - def add_resource(self, path, resource): - """ - Helper function to add resources to the resource directory during server initialization. - - :param path: the path for the new created resource - :type resource: Resource - :param resource: the resource to be added - """ - - assert isinstance(resource, Resource) - path = path.strip("/") - paths = path.split("/") - actual_path = "" - i = 0 - for p in paths: - i += 1 - actual_path += "/" + p - try: - res = self.root[actual_path] - except KeyError: - res = None - if res is None: - if len(paths) != i: - return False - resource.path = actual_path - self.root[actual_path] = resource - return True - - def remove_resource(self, path): - """ - Helper function to remove resources. - - :param path: the path for the unwanted resource - :rtype : the removed object - """ - - path = path.strip("/") - paths = path.split("/") - actual_path = "" - i = 0 - for p in paths: - i += 1 - actual_path += "/" + p - try: - res = self.root[actual_path] - except KeyError: - res = None - if res is not None: - del(self.root[actual_path]) - return res - - def _start_retransmission(self, transaction, message): - """ - Start the retransmission task. - - :type transaction: Transaction - :param transaction: the transaction that owns the message that needs retransmission - :type message: Message - :param message: the message that needs the retransmission task - """ - with transaction: - if message.type == defines.Types['CON']: - future_time = random.uniform(defines.ACK_TIMEOUT, (defines.ACK_TIMEOUT * defines.ACK_RANDOM_FACTOR)) - transaction.retransmit_thread = threading.Thread(target=self._retransmit, - args=(transaction, message, future_time, 0)) - transaction.retransmit_stop = threading.Event() - self.to_be_stopped.append(transaction.retransmit_stop) - transaction.retransmit_thread.start() - - def _retransmit(self, transaction, message, future_time, retransmit_count): - """ - Thread function to retransmit the message in the future - - :param transaction: the transaction that owns the message that needs retransmission - :param message: the message that needs the retransmission task - :param future_time: the amount of time to wait before a new attempt - :param retransmit_count: the number of retransmissions - """ - with transaction: - while retransmit_count < defines.MAX_RETRANSMIT and (not message.acknowledged and not message.rejected) \ - and not self.stopped.isSet(): - if transaction.retransmit_stop is not None: - transaction.retransmit_stop.wait(timeout=future_time) - if not message.acknowledged and not message.rejected and not self.stopped.isSet(): - retransmit_count += 1 - future_time *= 2 - self.send_datagram(message) - - if message.acknowledged or message.rejected: - message.timeouted = False - else: - logger.warning("Give up on message {message}".format(message=message.line_print)) - message.timeouted = True - if message.observe is not None: - self._observeLayer.remove_subscriber(message) - - try: - self.to_be_stopped.remove(transaction.retransmit_stop) - except ValueError: - pass - transaction.retransmit_stop = None - transaction.retransmit_thread = None - - def _start_separate_timer(self, transaction): - """ - Start a thread to handle separate mode. - - :type transaction: Transaction - :param transaction: the transaction that is in processing - :rtype : the Timer object - """ - t = threading.Timer(defines.ACK_TIMEOUT, self._send_ack, (transaction,)) - t.start() - return t - - @staticmethod - def _stop_separate_timer(timer): - """ - Stop the separate Thread if an answer has been already provided to the client. - - :param timer: The Timer object - """ - timer.cancel() - - def _send_ack(self, transaction): - """ - Sends an ACK message for the request. - - :param transaction: the transaction that owns the request - """ - - ack = Message() - ack.type = defines.Types['ACK'] - # TODO handle mutex on transaction - if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]: - ack = self._messageLayer.send_empty(transaction, transaction.request, ack) - self.send_datagram(ack) - - def notify(self, resource): - """ - Notifies the observers of a certain resource. - - :param resource: the resource - """ - observers = self._observeLayer.notify(resource) - logger.debug("Notify") - for transaction in observers: - with transaction: - transaction.response = None - transaction = self._requestLayer.receive_request(transaction) - transaction = self._observeLayer.send_response(transaction) - transaction = self._blockLayer.send_response(transaction) - transaction = self._messageLayer.send_response(transaction) - if transaction.response is not None: - if transaction.response.type == defines.Types["CON"]: - self._start_retransmission(transaction, transaction.response) - - self.send_datagram(transaction.response) diff --git a/coapthon/utils.py b/coapthon/utils.py index 505054c..20948e4 100644 --- a/coapthon/utils.py +++ b/coapthon/utils.py @@ -1,9 +1,24 @@ +# -*- coding: utf-8 -*- + +import binascii import random import string __author__ = 'Giacomo Tanganelli' +def str_append_hash(*args): + """ Convert each argument to a lower case string, appended, then hash """ + ret_hash = "" + for i in args: + if isinstance(i, (str, int)): + ret_hash += str(i).lower() + elif isinstance(i, bytes): + ret_hash += binascii.hexlify(i).decode("utf-8") + + return hash(ret_hash) + + def check_nocachekey(option): """ checks if an option is a NoCacheKey option or Etag @@ -51,7 +66,7 @@ def is_uri_option(number): def generate_random_token(size): - return ''.join(random.choice(string.ascii_letters) for _ in range(size)) + return bytes([random.randint(0, 255) for _ in range(size)]) def parse_blockwise(value): @@ -186,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/coapthon/utils.py.bak b/coapthon/utils.py.bak deleted file mode 100644 index d107033..0000000 --- a/coapthon/utils.py.bak +++ /dev/null @@ -1,188 +0,0 @@ -import random -import string - -__author__ = 'Giacomo Tanganelli' - - -def check_nocachekey(option): - """ - checks if an option is a NoCacheKey option or Etag - - :param option: - :return: - """ - return ((option.number & 0x1E) == 0x1C) | (option.number == 4) - - -def check_code(code): - """ - checks if the response code is one of the valid ones defined in the rfc - - :param code: - :return: - """ - if (65 <= code <= 69) or (128 <= code <= 134) or (code == 140) or (code == 141) or (code == 143) or ( - 160 <= code <= 165): - return - - else: - raise InvalidResponseCode - -""" -exception used to signal an invalid response code -""" - - -class InvalidResponseCode: - def __init__(self, code): - self.inv_code = code - - -def is_uri_option(number): - """ - checks if the option is part of uri-path, uri-host, uri-port, uri-query - - :param number: - :return: - """ - if number == 3 | number == 7 | number == 11 | number == 15: - return True - return False - - -def generate_random_token(size): - return ''.join(random.choice(string.ascii_letters) for _ in range(size)) - - -def parse_blockwise(value): - """ - Parse Blockwise option. - - :param value: option value - :return: num, m, size - """ - - length = byte_len(value) - if length == 1: - num = value & 0xF0 - num >>= 4 - m = value & 0x08 - m >>= 3 - size = value & 0x07 - elif length == 2: - num = value & 0xFFF0 - num >>= 4 - m = value & 0x0008 - m >>= 3 - size = value & 0x0007 - else: - num = value & 0xFFFFF0 - num >>= 4 - m = value & 0x000008 - m >>= 3 - size = value & 0x000007 - return num, int(m), pow(2, (size + 4)) - - -def byte_len(int_type): - """ - Get the number of byte needed to encode the int passed. - - :param int_type: the int to be converted - :return: the number of bits needed to encode the int passed. - """ - length = 0 - while int_type: - int_type >>= 1 - length += 1 - if length > 0: - if length % 8 != 0: - length = int(length / 8) + 1 - else: - length = int(length / 8) - return length - - -def parse_uri(uri): - t = uri.split("://") - tmp = t[1] - t = tmp.split("/", 1) - tmp = t[0] - path = t[1] - if tmp.startswith("["): - t = tmp.split("]") - host = t[0][1:] - port = int(t[1][1:]) - else: - t = tmp.split(":", 1) - try: - host = t[0] - port = int(t[1]) - except IndexError: - host = tmp - port = 5683 - - return str(host), port, path - - -def create_logging(): # pragma: no cover - with open("logging.conf", "w") as f: - f.writelines("[loggers]\n") - f.writelines("keys=root\n\n") - f.writelines("[handlers]\n") - f.writelines("keys=consoleHandler\n\n") - f.writelines("[formatters]\n") - f.writelines("keys=simpleFormatter\n\n") - f.writelines("[logger_root]\n") - f.writelines("level=DEBUG\n") - f.writelines("handlers=consoleHandler\n\n") - f.writelines("[handler_consoleHandler]\n") - f.writelines("class=StreamHandler\n") - f.writelines("level=DEBUG\n") - f.writelines("formatter=simpleFormatter\n") - f.writelines("args=(sys.stdout,)\n\n") - f.writelines("[formatter_simpleFormatter]\n") - f.writelines("format=%(asctime)s - %(threadName)-10s - %(name)s - %(levelname)s - %(message)s\n") - f.writelines("datefmt=") - - -class Tree(object): - def __init__(self): - self.tree = {} - - def dump(self): - """ - Get all the paths registered in the server. - - :return: registered resources. - """ - return self.tree.keys() - - def with_prefix(self, path): - ret = [] - for key in self.tree.keys(): - if path.startswith(key): - ret.append(key) - - if len(ret) > 0: - return ret - raise KeyError - - def with_prefix_resource(self, path): - ret = [] - for key, value in self.tree.iteritems(): - if path.startswith(key): - ret.append(value) - - if len(ret) > 0: - return ret - raise KeyError - - def __getitem__(self, item): - return self.tree[item] - - def __setitem__(self, key, value): - self.tree[key] = value - - def __delitem__(self, key): - del self.tree[key] diff --git a/coverage_test.py b/coverage_test.py index 3fb397b..6f6b6c0 100644 --- a/coverage_test.py +++ b/coverage_test.py @@ -1,8 +1,11 @@ +# -*- coding: utf-8 -*- + from queue import Queue import random import socket import threading import unittest + from coapclient import HelperClient from coapserver import CoAPServer from coapthon import defines @@ -15,6 +18,48 @@ from coapthon.serializer import Serializer __author__ = 'Giacomo Tanganelli' __version__ = "2.0" +PAYLOAD = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut " \ + "labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et " \ + "ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum " \ + "dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore " \ + "magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " \ + "Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit " \ + "amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " \ + "aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita " \ + "kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. " \ + "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore " \ + "eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum " \ + "zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer " \ + "adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. " \ + "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip " \ + "ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie " \ + "consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim " \ + "qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. " \ + "Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat " \ + "facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh " \ + "euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis " \ + "nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. " \ + "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore " \ + "eu feugiat nulla facilisis. " \ + "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata " \ + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " \ + "nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et " \ + "accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem " \ + "ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam " \ + "diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea " \ + "et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor " \ + "sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor " \ + "invidunt ut labore et dolore magna aliquyam erat. " \ + "Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " \ + "aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita " \ + "kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " \ + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam " \ + "erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd " \ + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " \ + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam " \ + "erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd " \ + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." + class Tests(unittest.TestCase): @@ -23,7 +68,7 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServer("127.0.0.1", 5683) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.queue = Queue() @@ -222,88 +267,86 @@ class Tests(unittest.TestCase): self.current_mid += 1 self._test_with_client([exchange1, exchange2, exchange3, exchange4]) - # def test_separate(self): - # print "TEST_SEPARATE" - # path = "/separate" - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["CON"] - # expected._mid = None - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.max_age = 60 - # - # exchange1 = (req, expected) - # - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "POST" - # - # expected = Response() - # expected.type = defines.Types["CON"] - # expected._mid = None - # expected.code = defines.Codes.CHANGED.number - # expected.token = None - # expected.options = None - # - # exchange2 = (req, expected) - # - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.PUT.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "PUT" - # - # expected = Response() - # expected.type = defines.Types["CON"] - # expected._mid = None - # expected.code = defines.Codes.CHANGED.number - # expected.token = None - # expected.options = None - # - # exchange3 = (req, expected) - # - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.DELETE.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["CON"] - # expected._mid = None - # expected.code = defines.Codes.DELETED.number - # expected.token = None - # - # exchange4 = (req, expected) - # - # self.current_mid += 1 - # self._test_with_client([exchange1, exchange2, exchange3, exchange4]) + def test_separate(self): + print("TEST_SEPARATE") + path = "/separate" + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["CON"] + expected._mid = None + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.max_age = 60 + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "POST" + + expected = Response() + expected.type = defines.Types["CON"] + expected._mid = None + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.options = None + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.PUT.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "PUT" + + expected = Response() + expected.type = defines.Types["CON"] + expected._mid = None + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.options = None + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.DELETE.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["CON"] + expected._mid = None + expected.code = defines.Codes.DELETED.number + expected.token = None + + exchange4 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) def test_post(self): print("TEST_POST") path = "/storage/new_res?id=1" - req = Request() + req = Request() req.code = defines.Codes.POST.number req.uri_path = path req.type = defines.Types["CON"] @@ -407,18 +450,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.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" + req.payload = PAYLOAD req.block1 = (1, 1, 1024) expected = Response() @@ -437,18 +469,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.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" + req.payload = PAYLOAD req.block1 = (0, 1, 1024) expected = Response() @@ -468,10 +489,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "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." + req.payload = PAYLOAD req.block1 = (1, 1, 64) expected = Response() @@ -491,10 +509,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "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." + req.payload = PAYLOAD req.block1 = (3, 1, 64) expected = Response() @@ -513,10 +528,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "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." + req.payload = PAYLOAD req.block1 = (2, 0, 64) expected = Response() @@ -536,6 +548,27 @@ class Tests(unittest.TestCase): print("TEST_GET_BLOCK") path = "/big" + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = None + # req.block2 = (0, 0, 512) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = None + expected.block2 = (0, 1, defines.MAX_PAYLOAD) + expected.size2 = 2041 + + exchange0 = (req, expected) + self.current_mid += 1 + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -552,6 +585,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (0, 1, 512) + expected.size2 = 2041 exchange1 = (req, expected) self.current_mid += 1 @@ -572,6 +606,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (1, 1, 256) + expected.size2 = 2041 exchange2 = (req, expected) self.current_mid += 1 @@ -592,6 +627,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (2, 1, 128) + expected.size2 = 2041 exchange3 = (req, expected) self.current_mid += 1 @@ -612,6 +648,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (3, 1, 64) + expected.size2 = 2041 exchange4 = (req, expected) self.current_mid += 1 @@ -632,6 +669,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (4, 1, 32) + expected.size2 = 2041 exchange5 = (req, expected) self.current_mid += 1 @@ -652,6 +690,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (5, 1, 16) + expected.size2 = 2041 exchange6 = (req, expected) self.current_mid += 1 @@ -671,7 +710,8 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CONTENT.number expected.token = None expected.payload = None - expected.block2 = (6, 1, 1024) + expected.block2 = (6, 0, 1024) + expected.size2 = 2041 exchange7 = (req, expected) self.current_mid += 1 @@ -692,11 +732,13 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (7, 0, 1024) + expected.size2 = 2041 exchange8 = (req, expected) self.current_mid += 1 - self._test_plugtest([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7, exchange8]) + self._test_plugtest([exchange0, exchange1, exchange2, exchange3, exchange4, + exchange5, exchange6, exchange7, exchange8]) def test_post_block_big(self): print("TEST_POST_BLOCK_BIG") @@ -708,7 +750,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "Lorem ipsum dolo" + req.payload = PAYLOAD req.block1 = (0, 1, 16) expected = Response() @@ -728,7 +770,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "r sit amet, consectetur adipisci" + req.payload = PAYLOAD req.block1 = (1, 1, 32) expected = Response() @@ -748,7 +790,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "ng elit. Sed ut ultrices ligula. Pellentesque purus augue, cursu" + req.payload = PAYLOAD req.block1 = (2, 1, 64) expected = Response() @@ -768,8 +810,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "s ultricies est in, vehicula congue metus. Vestibulum vel justo lacinia, porttitor quam vitae, " \ - "feugiat sapien. Quisque finibus, " + req.payload = PAYLOAD req.block1 = (3, 1, 128) expected = Response() @@ -789,9 +830,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "nisi vitae rhoncus malesuada, augue mauris dapibus tellus, sit amet venenatis libero" \ - " libero sed lorem. In pharetra turpis sed eros porta mollis. Quisque dictum dolor nisl," \ - " imperdiet tincidunt augue malesuada vitae. Donec non felis urna. Suspendisse at hend" + req.payload = PAYLOAD req.block1 = (4, 1, 256) expected = Response() @@ -811,12 +850,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "rerit ex, quis aliquet ante. Vivamus ultrices dolor at elit tincidunt, eget fringilla " \ - "ligula vestibulum. In molestie sagittis nibh, ut efficitur tellus faucibus non. Maecenas " \ - "posuere elementum faucibus. Morbi nisi diam, molestie non feugiat et, elementum eget magna." \ - " Donec vel sem facilisis quam viverra ultrices nec eu lacus. Sed molestie nisi id ultrices " \ - "interdum. Curabitur pharetra sed tellus in dignissim. Duis placerat aliquam metus, volutpat " \ - "elementum augue aliquam a. Nunc sed dolor at orci maximus portt" + req.payload = PAYLOAD req.block1 = (5, 1, 512) expected = Response() @@ -836,18 +870,7 @@ class Tests(unittest.TestCase): req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.payload = "itor ac sit amet eros. Mauris et nisi in tortor pharetra rhoncus sit amet hendrerit metus. " \ - "Integer laoreet placerat cursus. Nam a nulla ex. Donec laoreet sagittis libero quis " \ - "imperdiet. Vivamus facilisis turpis nec rhoncus venenatis. Duis pulvinar tellus vel quam " \ - "maximus imperdiet. Mauris eget nibh orci. Duis ut cursus nibh. Nulla sed commodo elit. " \ - "Suspendisse ac eros lacinia, mattis turpis at, porttitor justo. Vivamus molestie " \ - "tincidunt libero. Etiam porttitor lacus odio, at lobortis tortor scelerisque nec. " \ - "Nullam non ante vel nisi ultrices consectetur. Maecenas massa felis, tempor eget " \ - "malesuada eget, pretium eu sapien. Vivamus dapibus ante erat, non faucibus orci sodales " \ - "sit amet. Cras magna felis, sodales eget magna sed, eleifend rutrum ligula. Vivamus interdum " \ - "enim enim, eu facilisis tortor dignissim quis. Ut metus nulla, mattis non lorem et, " \ - "elementum ultrices orci. Quisque eleifend, arcu vitae ullamcorper pulvinar, ipsum ex " \ - "sodales arcu, eget consectetur mauris metus ac tortor. Donec id sem felis. Maur" + req.payload = PAYLOAD req.block1 = (6, 0, 1024) expected = Response() @@ -856,7 +879,6 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CHANGED.number expected.token = None expected.payload = None - expected.location_path = "big" exchange7 = (req, expected) self.current_mid += 1 @@ -941,75 +963,74 @@ class Tests(unittest.TestCase): self._test_with_client([exchange1, exchange2, exchange3]) - # def test_long_options(self): - # """ - # Test processing of options with extended length - # """ - # print("TEST_LONG_OPTIONS") - # - # path = "/storage/" - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # option = Option() - # # This option should be silently ignored by the server - # # since it is not critical - # option.number = defines.OptionRegistry.RM_MESSAGE_SWITCHING.number - # option.value = "\1\1\1\1\0\0" - # options = req.options - # req.add_option(option) - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = None - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1]) - # - # # This option (244) should be silently ignored by the server - # req = ("\x40\x01\x01\x01\xd6\xe7\x01\x01\x01\x01\x00\x00", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = None - # expected.code = defines.Codes.NOT_FOUND.number - # expected.token = None - # expected.payload = None - # - # exchange21 = (req, expected) - # self.current_mid += 1 - # - # # This option (245) should cause BAD REQUEST, as unrecognizable critical - # req = ("\x40\x01\x01\x01\xd6\xe8\x01\x01\x01\x01\x00\x00", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange22 = (req, expected) - # self.current_mid += 1 - # - # # This option (65525) should cause BAD REQUEST, as unrecognizable critical - # req = ("\x40\x01\x01\x01\xe6\xfe\xe8\x01\x01\x01\x01\x00\x00", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange23 = (req, expected) - # self.current_mid += 1 - # - # self._test_datagram([exchange21, exchange22, exchange23]) + def test_long_options(self): + """ + Test processing of options with extended length + """ + print("TEST_LONG_OPTIONS") + path = "/storage/" + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + option = Option() + # This option should be silently ignored by the server + # since it is not critical + option.number = defines.OptionRegistry.RM_MESSAGE_SWITCHING.number + option.value = b'\1\1\1\1\0\0' + req.add_option(option) + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = None + + exchange1 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1]) + + # This option (244) should be silently ignored by the server + req = (b'\x40\x01\x01\x01\xd6\xe7\x01\x01\x01\x01\x00\x00', self.server_address) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.NOT_FOUND.number + expected.token = None + expected.payload = None + + exchange21 = (req, expected) + self.current_mid += 1 + + # This option (245) should cause BAD REQUEST, as unrecognizable critical + req = (b'\x40\x01\x01\x01\xd6\xe8\x01\x01\x01\x01\x00\x00', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange22 = (req, expected) + self.current_mid += 1 + + # This option (65525) should cause BAD REQUEST, as unrecognizable critical + req = (b'\x40\x01\x01\x01\xe6\xfe\xe8\x01\x01\x01\x01\x00\x00', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange23 = (req, expected) + self.current_mid += 1 + + self._test_datagram([exchange21, exchange22, exchange23]) def test_content_type(self): print("TEST_CONTENT_TYPE") @@ -1138,7 +1159,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = "0" - print((expected.pretty_print())) + print(expected.pretty_print()) exchange7 = (req, expected) self.current_mid += 1 @@ -1198,9 +1219,8 @@ class Tests(unittest.TestCase): exchange10 = (req, expected) self.current_mid += 1 - # self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7, - # exchange8, exchange9, exchange10]) - self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5]) + self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5, + exchange6, exchange7, exchange8, exchange9, exchange10]) def test_ETAG(self): print("TEST_ETAG") @@ -1238,7 +1258,6 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CHANGED.number expected.token = None expected.payload = None - expected.location_path = path expected.etag = "1" exchange2 = (req, expected) @@ -1301,7 +1320,7 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CREATED.number expected.token = None expected.payload = None - expected.location_path = path + expected.location_path = "child" exchange1 = (req, expected) self.current_mid += 1 @@ -1376,7 +1395,7 @@ class Tests(unittest.TestCase): expected.type = defines.Types["ACK"] expected._mid = self.current_mid expected.code = defines.Codes.NOT_FOUND.number - expected.token = "100" + expected.token = 100 expected.payload = None exchange1 = (req, expected) @@ -1436,60 +1455,60 @@ class Tests(unittest.TestCase): self._test_with_client([exchange1, exchange2, exchange3, exchange4]) - # def test_invalid(self): - # print("TEST_INVALID") - # - # # version - # req = ("\x00\x01\x8c\xda", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange1 = (req, expected) - # - # # version - # req = ("\x40", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange2 = (req, expected) - # - # # code - # req = ("\x40\x05\x8c\xda", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange3 = (req, expected) - # - # # option - # req = ("\x40\x01\x8c\xda\x94", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange4 = (req, expected) - # - # # payload marker - # req = ("\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange5 = (req, expected) - # - # self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5]) + def test_invalid(self): + print("TEST_INVALID") + + # version + req = (b'\x00\x01\x8c\xda', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange1 = (req, expected) + + # version + req = (b'\x40', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange2 = (req, expected) + + # code + req = (b'\x40\x05\x8c\xda', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange3 = (req, expected) + + # option + req = (b'\x40\x01\x8c\xda\x94', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange4 = (req, expected) + + # payload marker + req = (b'\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange5 = (req, expected) + + self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5]) def test_post_block_big_client(self): print("TEST_POST_BLOCK_BIG_CLIENT") @@ -1571,6 +1590,6 @@ class Tests(unittest.TestCase): self._test_with_client_observe([exchange1, exchange2]) + if __name__ == '__main__': unittest.main() - diff --git a/coverage_testIPv6.py b/coverage_testIPv6.py index cdbc876..2bc6572 100644 --- a/coverage_testIPv6.py +++ b/coverage_testIPv6.py @@ -1,7 +1,10 @@ +# -*- coding: utf-8 -*- + from queue import Queue import random import threading import unittest + from coapclient import HelperClient from coapserver import CoAPServer from coapthon import defines @@ -20,7 +23,7 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServer("::1", 5683) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.queue = Queue() @@ -89,6 +92,7 @@ class Tests(unittest.TestCase): def test_not_allowed(self): print("TEST_NOT_ALLOWED") path = "/void" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -103,7 +107,6 @@ class Tests(unittest.TestCase): expected.token = None exchange1 = (req, expected) - self.current_mid += 1 req = Request() @@ -120,7 +123,6 @@ class Tests(unittest.TestCase): expected.token = None exchange2 = (req, expected) - self.current_mid += 1 req = Request() @@ -137,7 +139,6 @@ class Tests(unittest.TestCase): expected.token = None exchange3 = (req, expected) - self.current_mid += 1 req = Request() @@ -154,11 +155,10 @@ class Tests(unittest.TestCase): expected.token = None exchange4 = (req, expected) - self.current_mid += 1 + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) if __name__ == '__main__': unittest.main() - diff --git a/coverage_test_advanced.py b/coverage_test_advanced.py index 31b4657..cf189d5 100644 --- a/coverage_test_advanced.py +++ b/coverage_test_advanced.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import threading import unittest from queue import Queue @@ -18,7 +20,7 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServer("127.0.0.1", 5683) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.queue = Queue() @@ -61,6 +63,7 @@ class Tests(unittest.TestCase): def test_advanced(self): print("TEST_ADVANCED") path = "/advanced" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -76,7 +79,6 @@ class Tests(unittest.TestCase): expected.token = None exchange1 = (req, expected) - self.current_mid += 1 req = Request() @@ -94,7 +96,6 @@ class Tests(unittest.TestCase): expected.token = None exchange2 = (req, expected) - self.current_mid += 1 req = Request() @@ -112,7 +113,6 @@ class Tests(unittest.TestCase): expected.token = None exchange3 = (req, expected) - self.current_mid += 1 req = Request() @@ -130,13 +130,14 @@ class Tests(unittest.TestCase): expected.token = None exchange4 = (req, expected) - self.current_mid += 1 + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) def test_advanced_separate(self): print("TEST_ADVANCED_SEPARATE") path = "/advancedSeparate" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -152,7 +153,6 @@ class Tests(unittest.TestCase): expected.token = None exchange1 = (req, expected) - self.current_mid += 1 req = Request() @@ -170,7 +170,6 @@ class Tests(unittest.TestCase): expected.token = None exchange2 = (req, expected) - self.current_mid += 1 req = Request() @@ -188,7 +187,6 @@ class Tests(unittest.TestCase): expected.token = None exchange3 = (req, expected) - self.current_mid += 1 req = Request() @@ -206,9 +204,10 @@ class Tests(unittest.TestCase): expected.token = None exchange4 = (req, expected) - self.current_mid += 1 + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) + if __name__ == '__main__': unittest.main() diff --git a/coverage_test_multicast.py b/coverage_test_multicast.py index 994f521..0435c01 100644 --- a/coverage_test_multicast.py +++ b/coverage_test_multicast.py @@ -1,11 +1,13 @@ +# -*- coding: utf-8 -*- + from queue import Queue import random import threading import unittest + from coapclient import HelperClient from coapserver import CoAPServer from coapthon import defines -from coapthon.messages.message import Message from coapthon.messages.option import Option from coapthon.messages.request import Request from coapthon.messages.response import Response @@ -21,7 +23,7 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServer("0.0.0.0", 5683, multicast=True) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.queue = Queue() @@ -90,6 +92,7 @@ class Tests(unittest.TestCase): def test_not_allowed(self): print("TEST_NOT_ALLOWED") path = "/void" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -104,7 +107,6 @@ class Tests(unittest.TestCase): expected.token = None exchange1 = (req, expected) - self.current_mid += 1 req = Request() @@ -121,7 +123,6 @@ class Tests(unittest.TestCase): expected.token = None exchange2 = (req, expected) - self.current_mid += 1 req = Request() @@ -138,7 +139,6 @@ class Tests(unittest.TestCase): expected.token = None exchange3 = (req, expected) - self.current_mid += 1 req = Request() @@ -155,11 +155,10 @@ class Tests(unittest.TestCase): expected.token = None exchange4 = (req, expected) - self.current_mid += 1 + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) if __name__ == '__main__': unittest.main() - diff --git a/coverage_test_multicast_ipv6.py b/coverage_test_multicast_ipv6.py index ba6a87d..56ee483 100644 --- a/coverage_test_multicast_ipv6.py +++ b/coverage_test_multicast_ipv6.py @@ -1,11 +1,13 @@ +# -*- coding: utf-8 -*- + from queue import Queue import random import threading import unittest + from coapclient import HelperClient from coapserver import CoAPServer from coapthon import defines -from coapthon.messages.message import Message from coapthon.messages.option import Option from coapthon.messages.request import Request from coapthon.messages.response import Response @@ -21,7 +23,7 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServer("::1", 5683, multicast=True) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.queue = Queue() @@ -90,6 +92,7 @@ class Tests(unittest.TestCase): def test_not_allowed(self): print("TEST_NOT_ALLOWED") path = "/void" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -104,7 +107,6 @@ class Tests(unittest.TestCase): expected.token = None exchange1 = (req, expected) - self.current_mid += 1 req = Request() @@ -121,7 +123,6 @@ class Tests(unittest.TestCase): expected.token = None exchange2 = (req, expected) - self.current_mid += 1 req = Request() @@ -138,7 +139,6 @@ class Tests(unittest.TestCase): expected.token = None exchange3 = (req, expected) - self.current_mid += 1 req = Request() @@ -155,11 +155,10 @@ class Tests(unittest.TestCase): expected.token = None exchange4 = (req, expected) - self.current_mid += 1 + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) if __name__ == '__main__': unittest.main() - diff --git a/coverage_test_proxy.py b/coverage_test_proxy.py index cf27a66..8642c82 100644 --- a/coverage_test_proxy.py +++ b/coverage_test_proxy.py @@ -1,8 +1,11 @@ +# -*- coding: utf-8 -*- + from queue import Queue import random import socket import threading import unittest + from coapclient import HelperClient from coapforwardproxy import CoAPForwardProxy from coapserver import CoAPServer @@ -23,20 +26,20 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServer("127.0.0.1", 5684) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.proxy = CoAPForwardProxy("127.0.0.1", 5683) - self.proxy_thread = threading.Thread(target=self.proxy.listen, args=(10,)) + self.proxy_thread = threading.Thread(target=self.proxy.listen, args=(1,)) self.proxy_thread.start() self.queue = Queue() def tearDown(self): - self.server.close() - self.server_thread.join(timeout=25) - self.server = None self.proxy.close() self.proxy_thread.join(timeout=25) self.proxy = None + self.server.close() + self.server_thread.join(timeout=25) + self.server = None def _test_with_client(self, message_list): # pragma: no cover client = HelperClient(self.server_address) @@ -105,8 +108,8 @@ class Tests(unittest.TestCase): if expected is not None: datagram, source = sock.recvfrom(4096) received_message = serializer.deserialize(datagram, source) - print((received_message.pretty_print())) - print((expected.pretty_print())) + print(received_message.pretty_print()) + print(expected.pretty_print()) if expected.type is not None: self.assertEqual(received_message.type, expected.type) if expected.mid is not None: @@ -159,14 +162,13 @@ class Tests(unittest.TestCase): def test_get_forward(self): print("TEST_GET_FORWARD") - path = "/basic" + req = Request() req.code = defines.Codes.GET.number - req.uri_path = path + req.proxy_uri = "coap://127.0.0.1:5684/basic" req.type = defines.Types["CON"] req._mid = self.current_mid req.destination = self.server_address - req.proxy_uri = "coap://127.0.0.1:5684/basic" expected = Response() expected.type = defines.Types["ACK"] @@ -176,93 +178,88 @@ class Tests(unittest.TestCase): expected.payload = "Basic Resource" exchange1 = (req, expected) - self.current_mid += 1 self._test_with_client([exchange1]) - # def test_separate(self): - # print "TEST_SEPARATE" - # path = "/separate" - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["CON"] - # expected._mid = None - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.max_age = 60 - # - # exchange1 = (req, expected) - # - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "POST" - # - # expected = Response() - # expected.type = defines.Types["CON"] - # expected._mid = None - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.options = None - # - # exchange2 = (req, expected) - # - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.PUT.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "PUT" - # - # expected = Response() - # expected.type = defines.Types["CON"] - # expected._mid = None - # expected.code = defines.Codes.CHANGED.number - # expected.token = None - # expected.options = None - # - # exchange3 = (req, expected) - # - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.DELETE.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["CON"] - # expected._mid = None - # expected.code = defines.Codes.DELETED.number - # expected.token = None - # - # exchange4 = (req, expected) - # - # self.current_mid += 1 - # self._test_with_client([exchange1, exchange2, exchange3, exchange4]) - # + def test_separate(self): + print("TEST_SEPARATE") + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/separate" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["CON"] + expected._mid = None + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.max_age = 60 + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.proxy_uri = "coap://127.0.0.1:5684/separate" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "POST" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.options = None + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.PUT.number + req.proxy_uri = "coap://127.0.0.1:5684/separate" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "PUT" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.options = None + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.DELETE.number + req.proxy_uri = "coap://127.0.0.1:5684/separate" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.DELETED.number + expected.token = None + + exchange4 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) + def test_post(self): print("TEST_POST") - path = "/storage/new_res?id=1" - req = Request() + req = Request() req.code = defines.Codes.POST.number req.type = defines.Types["CON"] req._mid = self.current_mid @@ -358,8 +355,8 @@ class Tests(unittest.TestCase): def test_post_block(self): print("TEST_POST_BLOCK") - req = Request() + req = Request() req.code = defines.Codes.POST.number req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" req.type = defines.Types["CON"] @@ -509,6 +506,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (0, 1, 512) + expected.size2 = 2041 exchange1 = (req, expected) self.current_mid += 1 @@ -529,6 +527,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (1, 1, 256) + expected.size2 = 2041 exchange2 = (req, expected) self.current_mid += 1 @@ -549,6 +548,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (2, 1, 128) + expected.size2 = 2041 exchange3 = (req, expected) self.current_mid += 1 @@ -569,6 +569,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (3, 1, 64) + expected.size2 = 2041 exchange4 = (req, expected) self.current_mid += 1 @@ -589,6 +590,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (4, 1, 32) + expected.size2 = 2041 exchange5 = (req, expected) self.current_mid += 1 @@ -609,6 +611,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (5, 1, 16) + expected.size2 = 2041 exchange6 = (req, expected) self.current_mid += 1 @@ -628,7 +631,8 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CONTENT.number expected.token = None expected.payload = None - expected.block2 = (6, 1, 1024) + expected.block2 = (6, 0, 1024) + expected.size2 = 2041 exchange7 = (req, expected) self.current_mid += 1 @@ -649,18 +653,17 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (7, 0, 1024) + expected.size2 = 2041 exchange8 = (req, expected) self.current_mid += 1 self._test_plugtest([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7, exchange8]) - #self._test_plugtest([exchange1]) - def test_post_block_big(self): print("TEST_POST_BLOCK_BIG") - req = Request() + req = Request() req.code = defines.Codes.POST.number req.proxy_uri = "coap://127.0.0.1:5684/big" req.type = defines.Types["CON"] @@ -814,501 +817,495 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CHANGED.number expected.token = None expected.payload = None - expected.location_path = "big" exchange7 = (req, expected) self.current_mid += 1 self._test_plugtest([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7]) - # def test_options(self): - # print "TEST_OPTIONS" - # path = "/storage/new_res" - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # option = Option() - # option.number = defines.OptionRegistry.ETAG.number - # option.value = "test" - # req.add_option(option) - # req.del_option(option) - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "storage/new_res" - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # option = Option() - # option.number = defines.OptionRegistry.ETAG.number - # option.value = "test" - # req.add_option(option) - # req.del_option_by_name("ETag") - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "storage/new_res" - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # option = Option() - # option.number = defines.OptionRegistry.ETAG.number - # option.value = "test" - # req.add_option(option) - # del req.etag - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "storage/new_res" - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3]) - # - # def test_content_type(self): - # print "TEST_CONTENT_TYPE" - # path = "/storage/new_res" - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # req.content_type = defines.Content_types["application/xml"] - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "storage/new_res" - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "Basic Resource" - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.PUT.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CHANGED.number - # expected.token = None - # expected.payload = None - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "test" - # - # exchange4 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.accept = defines.Content_types["application/xml"] - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "test" - # - # exchange5 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.accept = defines.Content_types["application/json"] - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.NOT_ACCEPTABLE.number - # expected.token = None - # expected.payload = None - # expected.content_type = defines.Content_types["application/json"] - # - # exchange6 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = "/xml" - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "0" - # expected.content_type = defines.Content_types["application/xml"] - # - # print(expected.pretty_print()) - # - # exchange7 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7]) - # - # def test_ETAG(self): - # print "TEST_ETAG" - # path = "/etag" - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "ETag resource" - # expected.etag = "0" - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = path - # expected.etag = "1" - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.etag = "1" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.VALID.number - # expected.token = None - # expected.payload = "test" - # expected.etag = "1" - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3]) - # - # def test_child(self): - # print "TEST_CHILD" - # path = "/child" - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = path - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "test" - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.PUT.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "testPUT" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CHANGED.number - # expected.token = None - # expected.payload = None - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.DELETE.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.DELETED.number - # expected.token = None - # expected.payload = None - # - # exchange4 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3, exchange4]) - # - # def test_not_found(self): - # print "TEST_not_found" - # path = "/not_found" - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.token = 100 - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.NOT_FOUND.number - # expected.token = "100" - # expected.payload = None - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.METHOD_NOT_ALLOWED.number - # expected.token = None - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.PUT.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "testPUT" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.NOT_FOUND.number - # expected.token = None - # expected.payload = None - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.DELETE.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.NOT_FOUND.number - # expected.token = None - # expected.payload = None - # - # exchange4 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3, exchange4]) - # - # def test_invalid(self): - # print("TEST_INVALID") - # - # # version - # req = (bytes("\x00\x01\x8c\xda", "utf-8"), self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange1 = (req, expected) - # - # # version - # req = (bytes("\x40", "utf-8"), self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange2 = (req, expected) - # - # # code - # req = (bytes("\x40\x05\x8c\xda", "utf-8"), self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange3 = (req, expected) - # - # # option - # req = (bytes("\x40\x01\x8c\xda\x94", "utf-8"), self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange4 = (req, expected) - # - # # payload marker - # req = (bytes("\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff", "utf-8"), self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange5 = (req, expected) - # - # self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5]) + def test_options(self): + print("TEST_OPTIONS") + path = "/storage/new_res" + + req = Request() + req.code = defines.Codes.POST.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + option = Option() + option.number = defines.OptionRegistry.ETAG.number + option.value = "test" + req.add_option(option) + req.del_option(option) + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "storage/new_res" + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + option = Option() + option.number = defines.OptionRegistry.ETAG.number + option.value = "test" + req.add_option(option) + req.del_option_by_name("ETag") + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "storage/new_res" + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + option = Option() + option.number = defines.OptionRegistry.ETAG.number + option.value = "test" + req.add_option(option) + del req.etag + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "storage/new_res" + + exchange3 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3]) + + def test_content_type(self): + print("TEST_CONTENT_TYPE") + + req = Request() + req.code = defines.Codes.POST.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "test" + req.content_type = defines.Content_types["application/xml"] + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "storage/new_res" + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "Basic Resource" + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.PUT.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.payload = None + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "test" + + exchange4 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.accept = defines.Content_types["application/xml"] + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "test" + + exchange5 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/storage/new_res" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.accept = defines.Content_types["application/json"] + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.NOT_ACCEPTABLE.number + expected.token = None + expected.payload = None + # expected.content_type = defines.Content_types["application/json"] + + exchange6 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/xml" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "0" + expected.content_type = defines.Content_types["application/xml"] + + print(expected.pretty_print()) + + exchange7 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7]) + + def test_ETAG(self): + print("TEST_ETAG") + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/etag" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "ETag resource" + expected.etag = "0" + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.proxy_uri = "coap://127.0.0.1:5684/etag" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.payload = None + expected.etag = "1" + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/etag" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.etag = "1" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.VALID.number + expected.token = None + expected.payload = None + expected.etag = "1" + + exchange3 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3]) + + def test_child(self): + print("TEST_CHILD") + + req = Request() + req.code = defines.Codes.POST.number + req.proxy_uri = "coap://127.0.0.1:5684/child" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "child" + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/child" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "test" + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.PUT.number + req.proxy_uri = "coap://127.0.0.1:5684/child" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "testPUT" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.payload = None + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.DELETE.number + req.proxy_uri = "coap://127.0.0.1:5684/child" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.DELETED.number + expected.token = None + expected.payload = None + + exchange4 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) + + def test_not_found(self): + print("TEST_not_found") + + req = Request() + req.code = defines.Codes.GET.number + req.proxy_uri = "coap://127.0.0.1:5684/not_found" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.token = 100 + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.NOT_FOUND.number + expected.token = 100 + expected.payload = None + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.proxy_uri = "coap://127.0.0.1:5684/not_found" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "testPOST" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.METHOD_NOT_ALLOWED.number + expected.token = None + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.PUT.number + req.proxy_uri = "coap://127.0.0.1:5684/not_found" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "testPUT" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.NOT_FOUND.number + expected.token = None + expected.payload = None + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.DELETE.number + req.proxy_uri = "coap://127.0.0.1:5684/not_found" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.NOT_FOUND.number + expected.token = None + expected.payload = None + + exchange4 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) + + def test_invalid(self): + print("TEST_INVALID") + + # version + req = (b'\x00\x01\x8c\xda', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange1 = (req, expected) + + # version + req = (b'\x40', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange2 = (req, expected) + + # code + req = (b'\x40\x05\x8c\xda', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange3 = (req, expected) + + # option + req = (b'\x40\x01\x8c\xda\x94', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange4 = (req, expected) + + # payload marker + req = (b'\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange5 = (req, expected) + + self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5]) def test_post_block_big_client(self): print("TEST_POST_BLOCK_BIG_CLIENT") - req = Request() + req = Request() req.code = defines.Codes.POST.number req.proxy_uri = "coap://127.0.0.1:5684/big" req.type = defines.Types["CON"] @@ -1369,13 +1366,13 @@ class Tests(unittest.TestCase): expected.payload = None exchange1 = (req, expected) - self.current_mid += 1 self._test_with_client_observe([exchange1]) def test_duplicate(self): print("TEST_DUPLICATE") + req = Request() req.code = defines.Codes.GET.number req.proxy_uri = "coap://127.0.0.1:5684/basic" @@ -1390,10 +1387,12 @@ class Tests(unittest.TestCase): expected.token = None self.current_mid += 1 + self._test_plugtest([(req, expected), (req, expected)]) def test_duplicate_not_completed(self): print("TEST_DUPLICATE_NOT_COMPLETED") + req = Request() req.code = defines.Codes.GET.number req.proxy_uri = "coap://127.0.0.1:5684/long" @@ -1414,8 +1413,9 @@ class Tests(unittest.TestCase): expected2.token = None self.current_mid += 1 + self._test_plugtest([(req, None), (req, expected), (None, expected2)]) + if __name__ == '__main__': unittest.main() - diff --git a/coverage_test_reverse_proxy.py b/coverage_test_reverse_proxy.py index beab967..1938fe3 100644 --- a/coverage_test_reverse_proxy.py +++ b/coverage_test_reverse_proxy.py @@ -1,14 +1,15 @@ +# -*- coding: utf-8 -*- + from queue import Queue import random import socket import threading import unittest + from coapclient import HelperClient -from coapforwardproxy import CoAPForwardProxy from coapreverseproxy import CoAPReverseProxy from coapserver import CoAPServer from coapthon import defines -from coapthon.messages.message import Message from coapthon.messages.option import Option from coapthon.messages.request import Request from coapthon.messages.response import Response @@ -25,20 +26,20 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServer("127.0.0.1", 5684) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.proxy = CoAPReverseProxy("127.0.0.1", 5683, "reverse_proxy_mapping.xml") - self.proxy_thread = threading.Thread(target=self.proxy.listen, args=(10,)) + self.proxy_thread = threading.Thread(target=self.proxy.listen, args=(1,)) self.proxy_thread.start() self.queue = Queue() def tearDown(self): - self.server.close() - self.server_thread.join(timeout=25) - self.server = None self.proxy.close() self.proxy_thread.join(timeout=25) self.proxy = None + self.server.close() + self.server_thread.join(timeout=25) + self.server = None def _test_with_client(self, message_list): # pragma: no cover client = HelperClient(self.server_address) @@ -408,8 +409,8 @@ class Tests(unittest.TestCase): def test_post_block(self): print("TEST_POST_BLOCK") path = "/Server1/storage/new_res" - req = Request() + req = Request() req.code = defines.Codes.POST.number req.uri_path = path req.type = defines.Types["CON"] @@ -533,7 +534,7 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CREATED.number expected.token = None expected.payload = None - expected.location_path = "/Server1/storage/new_res" + expected.location_path = "Server1/storage/new_res" exchange5 = (req, expected) self.current_mid += 1 @@ -560,6 +561,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (0, 1, 512) + expected.size2 = 2041 exchange1 = (req, expected) self.current_mid += 1 @@ -580,6 +582,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (1, 1, 256) + expected.size2 = 2041 exchange2 = (req, expected) self.current_mid += 1 @@ -600,6 +603,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (2, 1, 128) + expected.size2 = 2041 exchange3 = (req, expected) self.current_mid += 1 @@ -620,6 +624,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (3, 1, 64) + expected.size2 = 2041 exchange4 = (req, expected) self.current_mid += 1 @@ -640,6 +645,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (4, 1, 32) + expected.size2 = 2041 exchange5 = (req, expected) self.current_mid += 1 @@ -660,6 +666,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (5, 1, 16) + expected.size2 = 2041 exchange6 = (req, expected) self.current_mid += 1 @@ -679,7 +686,8 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CONTENT.number expected.token = None expected.payload = None - expected.block2 = (6, 1, 1024) + expected.block2 = (6, 0, 1024) + expected.size2 = 2041 exchange7 = (req, expected) self.current_mid += 1 @@ -700,660 +708,657 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (7, 0, 1024) + expected.size2 = 2041 exchange8 = (req, expected) self.current_mid += 1 self._test_plugtest([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7, exchange8]) - # def test_post_block_big(self): - # print "TEST_POST_BLOCK_BIG" - # path = "/big" - # req = Request() - # - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "Lorem ipsum dolo" - # req.block1 = (0, 1, 16) - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = None - # expected.code = defines.Codes.CONTINUE.number - # expected.token = None - # expected.payload = None - # expected.block1 = (0, 1, 16) - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "r sit amet, consectetur adipisci" - # req.block1 = (1, 1, 32) - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = None - # expected.code = defines.Codes.CONTINUE.number - # expected.token = None - # expected.payload = None - # expected.block1 = (1, 1, 32) - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "ng elit. Sed ut ultrices ligula. Pellentesque purus augue, cursu" - # req.block1 = (2, 1, 64) - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = None - # expected.code = defines.Codes.CONTINUE.number - # expected.token = None - # expected.payload = None - # expected.block1 = (2, 1, 64) - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "s ultricies est in, vehicula congue metus. Vestibulum vel justo lacinia, porttitor quam vitae, " \ - # "feugiat sapien. Quisque finibus, " - # req.block1 = (3, 1, 128) - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = None - # expected.code = defines.Codes.CONTINUE.number - # expected.token = None - # expected.payload = None - # expected.block1 = (3, 1, 128) - # - # exchange4 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "nisi vitae rhoncus malesuada, augue mauris dapibus tellus, sit amet venenatis libero" \ - # " libero sed lorem. In pharetra turpis sed eros porta mollis. Quisque dictum dolor nisl," \ - # " imperdiet tincidunt augue malesuada vitae. Donec non felis urna. Suspendisse at hend" - # req.block1 = (4, 1, 256) - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = None - # expected.code = defines.Codes.CONTINUE.number - # expected.token = None - # expected.payload = None - # expected.block1 = (4, 1, 256) - # - # exchange5 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "rerit ex, quis aliquet ante. Vivamus ultrices dolor at elit tincidunt, eget fringilla " \ - # "ligula vestibulum. In molestie sagittis nibh, ut efficitur tellus faucibus non. Maecenas " \ - # "posuere elementum faucibus. Morbi nisi diam, molestie non feugiat et, elementum eget magna." \ - # " Donec vel sem facilisis quam viverra ultrices nec eu lacus. Sed molestie nisi id ultrices " \ - # "interdum. Curabitur pharetra sed tellus in dignissim. Duis placerat aliquam metus, volutpat " \ - # "elementum augue aliquam a. Nunc sed dolor at orci maximus portt" - # req.block1 = (5, 1, 512) - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = None - # expected.code = defines.Codes.CONTINUE.number - # expected.token = None - # expected.payload = None - # expected.block1 = (5, 1, 512) - # - # exchange6 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "itor ac sit amet eros. Mauris et nisi in tortor pharetra rhoncus sit amet hendrerit metus. " \ - # "Integer laoreet placerat cursus. Nam a nulla ex. Donec laoreet sagittis libero quis " \ - # "imperdiet. Vivamus facilisis turpis nec rhoncus venenatis. Duis pulvinar tellus vel quam " \ - # "maximus imperdiet. Mauris eget nibh orci. Duis ut cursus nibh. Nulla sed commodo elit. " \ - # "Suspendisse ac eros lacinia, mattis turpis at, porttitor justo. Vivamus molestie " \ - # "tincidunt libero. Etiam porttitor lacus odio, at lobortis tortor scelerisque nec. " \ - # "Nullam non ante vel nisi ultrices consectetur. Maecenas massa felis, tempor eget " \ - # "malesuada eget, pretium eu sapien. Vivamus dapibus ante erat, non faucibus orci sodales " \ - # "sit amet. Cras magna felis, sodales eget magna sed, eleifend rutrum ligula. Vivamus interdum " \ - # "enim enim, eu facilisis tortor dignissim quis. Ut metus nulla, mattis non lorem et, " \ - # "elementum ultrices orci. Quisque eleifend, arcu vitae ullamcorper pulvinar, ipsum ex " \ - # "sodales arcu, eget consectetur mauris metus ac tortor. Donec id sem felis. Maur" - # req.block1 = (6, 0, 1024) - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = None - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "big" - # - # exchange7 = (req, expected) - # self.current_mid += 1 - # - # self._test_plugtest([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7]) - # - # def test_options(self): - # print "TEST_OPTIONS" - # path = "/storage/new_res" - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # option = Option() - # option.number = defines.OptionRegistry.ETAG.number - # option.value = "test" - # req.add_option(option) - # req.del_option(option) - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "storage/new_res" - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # option = Option() - # option.number = defines.OptionRegistry.ETAG.number - # option.value = "test" - # req.add_option(option) - # req.del_option_by_name("ETag") - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "storage/new_res" - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # option = Option() - # option.number = defines.OptionRegistry.ETAG.number - # option.value = "test" - # req.add_option(option) - # del req.etag - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "storage/new_res" - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3]) - # - # def test_content_type(self): - # print "TEST_CONTENT_TYPE" - # path = "/storage/new_res" - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # req.content_type = defines.Content_types["application/xml"] - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = "storage/new_res" - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "Basic Resource" - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.PUT.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CHANGED.number - # expected.token = None - # expected.payload = None - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "test" - # - # exchange4 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.accept = defines.Content_types["application/xml"] - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "test" - # - # exchange5 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.accept = defines.Content_types["application/json"] - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.NOT_ACCEPTABLE.number - # expected.token = None - # expected.payload = None - # expected.content_type = defines.Content_types["application/json"] - # - # exchange6 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = "/xml" - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "0" - # expected.content_type = defines.Content_types["application/xml"] - # - # print(expected.pretty_print()) - # - # exchange7 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7]) - # - # def test_ETAG(self): - # print "TEST_ETAG" - # path = "/etag" - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "ETag resource" - # expected.etag = "0" - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = path - # expected.etag = "1" - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.etag = "1" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.VALID.number - # expected.token = None - # expected.payload = "test" - # expected.etag = "1" - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3]) - # - # def test_child(self): - # print "TEST_CHILD" - # path = "/child" - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CREATED.number - # expected.token = None - # expected.payload = None - # expected.location_path = path - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CONTENT.number - # expected.token = None - # expected.payload = "test" - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.PUT.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "testPUT" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.CHANGED.number - # expected.token = None - # expected.payload = None - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.DELETE.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.DELETED.number - # expected.token = None - # expected.payload = None - # - # exchange4 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3, exchange4]) - # - # def test_not_found(self): - # print "TEST_not_found" - # path = "/not_found" - # - # req = Request() - # req.code = defines.Codes.GET.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.token = 100 - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.NOT_FOUND.number - # expected.token = "100" - # expected.payload = None - # - # exchange1 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.POST.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "test" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.METHOD_NOT_ALLOWED.number - # expected.token = None - # - # exchange2 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.PUT.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # req.payload = "testPUT" - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.NOT_FOUND.number - # expected.token = None - # expected.payload = None - # - # exchange3 = (req, expected) - # self.current_mid += 1 - # - # req = Request() - # req.code = defines.Codes.DELETE.number - # req.uri_path = path - # req.type = defines.Types["CON"] - # req._mid = self.current_mid - # req.destination = self.server_address - # - # expected = Response() - # expected.type = defines.Types["ACK"] - # expected._mid = self.current_mid - # expected.code = defines.Codes.NOT_FOUND.number - # expected.token = None - # expected.payload = None - # - # exchange4 = (req, expected) - # self.current_mid += 1 - # - # self._test_with_client([exchange1, exchange2, exchange3, exchange4]) - # - # def test_invalid(self): - # print("TEST_INVALID") - # - # # version - # req = ("\x00\x01\x8c\xda", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange1 = (req, expected) - # - # # version - # req = ("\x40", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange2 = (req, expected) - # - # # code - # req = ("\x40\x05\x8c\xda", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange3 = (req, expected) - # - # # option - # req = ("\x40\x01\x8c\xda\x94", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange4 = (req, expected) - # - # # payload marker - # req = ("\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff", self.server_address) - # - # expected = Response() - # expected.type = defines.Types["RST"] - # expected._mid = None - # expected.code = defines.Codes.BAD_REQUEST.number - # - # exchange5 = (req, expected) - # - # self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5]) + def test_post_block_big(self): + print("TEST_POST_BLOCK_BIG") + path = "/Server1/big" + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "Lorem ipsum dolo" + req.block1 = (0, 1, 16) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.CONTINUE.number + expected.token = None + expected.payload = None + expected.block1 = (0, 1, 16) + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "r sit amet, consectetur adipisci" + req.block1 = (1, 1, 32) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.CONTINUE.number + expected.token = None + expected.payload = None + expected.block1 = (1, 1, 32) + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "ng elit. Sed ut ultrices ligula. Pellentesque purus augue, cursu" + req.block1 = (2, 1, 64) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.CONTINUE.number + expected.token = None + expected.payload = None + expected.block1 = (2, 1, 64) + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "s ultricies est in, vehicula congue metus. Vestibulum vel justo lacinia, porttitor quam vitae, " \ + "feugiat sapien. Quisque finibus, " + req.block1 = (3, 1, 128) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.CONTINUE.number + expected.token = None + expected.payload = None + expected.block1 = (3, 1, 128) + + exchange4 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "nisi vitae rhoncus malesuada, augue mauris dapibus tellus, sit amet venenatis libero" \ + " libero sed lorem. In pharetra turpis sed eros porta mollis. Quisque dictum dolor nisl," \ + " imperdiet tincidunt augue malesuada vitae. Donec non felis urna. Suspendisse at hend" + req.block1 = (4, 1, 256) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.CONTINUE.number + expected.token = None + expected.payload = None + expected.block1 = (4, 1, 256) + + exchange5 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "rerit ex, quis aliquet ante. Vivamus ultrices dolor at elit tincidunt, eget fringilla " \ + "ligula vestibulum. In molestie sagittis nibh, ut efficitur tellus faucibus non. Maecenas " \ + "posuere elementum faucibus. Morbi nisi diam, molestie non feugiat et, elementum eget magna." \ + " Donec vel sem facilisis quam viverra ultrices nec eu lacus. Sed molestie nisi id ultrices " \ + "interdum. Curabitur pharetra sed tellus in dignissim. Duis placerat aliquam metus, volutpat " \ + "elementum augue aliquam a. Nunc sed dolor at orci maximus portt" + req.block1 = (5, 1, 512) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.CONTINUE.number + expected.token = None + expected.payload = None + expected.block1 = (5, 1, 512) + + exchange6 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "itor ac sit amet eros. Mauris et nisi in tortor pharetra rhoncus sit amet hendrerit metus. " \ + "Integer laoreet placerat cursus. Nam a nulla ex. Donec laoreet sagittis libero quis " \ + "imperdiet. Vivamus facilisis turpis nec rhoncus venenatis. Duis pulvinar tellus vel quam " \ + "maximus imperdiet. Mauris eget nibh orci. Duis ut cursus nibh. Nulla sed commodo elit. " \ + "Suspendisse ac eros lacinia, mattis turpis at, porttitor justo. Vivamus molestie " \ + "tincidunt libero. Etiam porttitor lacus odio, at lobortis tortor scelerisque nec. " \ + "Nullam non ante vel nisi ultrices consectetur. Maecenas massa felis, tempor eget " \ + "malesuada eget, pretium eu sapien. Vivamus dapibus ante erat, non faucibus orci sodales " \ + "sit amet. Cras magna felis, sodales eget magna sed, eleifend rutrum ligula. Vivamus interdum " \ + "enim enim, eu facilisis tortor dignissim quis. Ut metus nulla, mattis non lorem et, " \ + "elementum ultrices orci. Quisque eleifend, arcu vitae ullamcorper pulvinar, ipsum ex " \ + "sodales arcu, eget consectetur mauris metus ac tortor. Donec id sem felis. Maur" + req.block1 = (6, 0, 1024) + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = None + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.payload = None + + exchange7 = (req, expected) + self.current_mid += 1 + + self._test_plugtest([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7]) + + def test_options(self): + print("TEST_OPTIONS") + path = "/Server1/storage/new_res" + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + option = Option() + option.number = defines.OptionRegistry.ETAG.number + option.value = "test" + req.add_option(option) + req.del_option(option) + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "Server1/storage/new_res" + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + option = Option() + option.number = defines.OptionRegistry.ETAG.number + option.value = "test" + req.add_option(option) + req.del_option_by_name("ETag") + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "Server1/storage/new_res" + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + option = Option() + option.number = defines.OptionRegistry.ETAG.number + option.value = "test" + req.add_option(option) + del req.etag + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "Server1/storage/new_res" + + exchange3 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3]) + + def test_content_type(self): + print("TEST_CONTENT_TYPE") + path = "/Server1/storage/new_res" + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "test" + req.content_type = defines.Content_types["application/xml"] + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "Server1/storage/new_res" + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "Basic Resource" + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.PUT.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.payload = None + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "test" + + exchange4 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.accept = defines.Content_types["application/xml"] + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "test" + + exchange5 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.accept = defines.Content_types["application/json"] + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.NOT_ACCEPTABLE.number + expected.token = None + expected.payload = None + # expected.content_type = defines.Content_types["application/json"] + + exchange6 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = "/Server1/xml" + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "0" + expected.content_type = defines.Content_types["application/xml"] + + exchange7 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7]) + + def test_ETAG(self): + print("TEST_ETAG") + path = "/Server1/etag" + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "ETag resource" + expected.etag = "0" + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.payload = None + expected.etag = "1" + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.etag = "1" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.VALID.number + expected.token = None + expected.payload = None + expected.etag = "1" + + exchange3 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3]) + + def test_child(self): + print("TEST_CHILD") + path = "/Server1/child" + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "test" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CREATED.number + expected.token = None + expected.payload = None + expected.location_path = "Server1/child" + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CONTENT.number + expected.token = None + expected.payload = "test" + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.PUT.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "testPUT" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.CHANGED.number + expected.token = None + expected.payload = None + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.DELETE.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.DELETED.number + expected.token = None + expected.payload = None + + exchange4 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) + + def test_not_found(self): + print("TEST_not_found") + path = "/Server1/not_found" + + req = Request() + req.code = defines.Codes.GET.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.token = 100 + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.NOT_FOUND.number + expected.token = 100 + expected.payload = None + + exchange1 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.POST.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "testPOST" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.METHOD_NOT_ALLOWED.number + expected.token = None + + exchange2 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.PUT.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + req.payload = "testPUT" + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.NOT_FOUND.number + expected.token = None + expected.payload = None + + exchange3 = (req, expected) + self.current_mid += 1 + + req = Request() + req.code = defines.Codes.DELETE.number + req.uri_path = path + req.type = defines.Types["CON"] + req._mid = self.current_mid + req.destination = self.server_address + + expected = Response() + expected.type = defines.Types["ACK"] + expected._mid = self.current_mid + expected.code = defines.Codes.NOT_FOUND.number + expected.token = None + expected.payload = None + + exchange4 = (req, expected) + self.current_mid += 1 + + self._test_with_client([exchange1, exchange2, exchange3, exchange4]) + + def test_invalid(self): + print("TEST_INVALID") + + # version + req = (b'\x00\x01\x8c\xda', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange1 = (req, expected) + + # version + req = (b'\x40', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange2 = (req, expected) + + # code + req = (b'\x40\x05\x8c\xda', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange3 = (req, expected) + + # get/option + req = (b'\x40\x01\x8c\xda\x94', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange4 = (req, expected) + + # post/payload marker + req = (b'\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff', self.server_address) + + expected = Response() + expected.type = defines.Types["RST"] + expected._mid = None + expected.code = defines.Codes.BAD_REQUEST.number + + exchange5 = (req, expected) + + self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5]) def test_post_block_big_client(self): print("TEST_POST_BLOCK_BIG_CLIENT") @@ -1468,6 +1473,6 @@ class Tests(unittest.TestCase): self.current_mid += 1 self._test_plugtest([(req, None), (req, expected), (None, expected2)]) + if __name__ == '__main__': unittest.main() - 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 diff --git a/plugtest.py b/plugtest.py index eb764c5..3ed12a4 100644 --- a/plugtest.py +++ b/plugtest.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- + from queue import Queue import random import socket import threading import unittest + from coapthon.messages.message import Message from coapclient import HelperClient from coapthon.messages.response import Response @@ -23,7 +25,7 @@ class Tests(unittest.TestCase): self.current_mid = random.randint(1, 1000) self.server_mid = random.randint(1000, 2000) self.server = CoAPServerPlugTest("127.0.0.1", 5683, starting_mid=self.server_mid) - self.server_thread = threading.Thread(target=self.server.listen, args=(10,)) + self.server_thread = threading.Thread(target=self.server.listen, args=(1,)) self.server_thread.start() self.queue = Queue() @@ -127,6 +129,7 @@ class Tests(unittest.TestCase): def test_td_coap_link_01(self): print("TD_COAP_LINK_01") path = "/.well-known/core" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -147,6 +150,7 @@ class Tests(unittest.TestCase): def test_td_coap_link_02(self): print("TD_COAP_LINK_02") path = "/.well-known/core" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -168,6 +172,7 @@ class Tests(unittest.TestCase): def test_td_coap_core_01(self): print("TD_COAP_CORE_01") path = "/test" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -188,8 +193,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_02(self): print("TD_COAP_CORE_02") path = "/test_post" - req = Request() + req = Request() req.code = defines.Codes.POST.number req.uri_path = path req.type = defines.Types["CON"] @@ -204,7 +209,7 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CREATED.number expected.token = None expected.payload = None - expected.location_path = "/test_post" + expected.location_path = "test_post" self.current_mid += 1 self._test_with_client([(req, expected)]) @@ -212,8 +217,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_03(self): print("TD_COAP_CORE_03") path = "/test" - req = Request() + req = Request() req.code = defines.Codes.PUT.number req.uri_path = path req.type = defines.Types["CON"] @@ -233,7 +238,6 @@ class Tests(unittest.TestCase): exchange1 = (req, expected) req = Request() - req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["CON"] @@ -251,7 +255,6 @@ class Tests(unittest.TestCase): exchange2 = (req, expected) req = Request() - req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["CON"] @@ -274,8 +277,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_04(self): print("TD_COAP_CORE_04") path = "/test" - req = Request() + req = Request() req.code = defines.Codes.DELETE.number req.uri_path = path req.type = defines.Types["CON"] @@ -295,8 +298,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_05(self): print("TD_COAP_CORE_05") path = "/test" - req = Request() + req = Request() req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["NON"] @@ -316,8 +319,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_06(self): print("TD_COAP_CORE_06") path = "/test_post" - req = Request() + req = Request() req.code = defines.Codes.POST.number req.uri_path = path req.type = defines.Types["NON"] @@ -332,7 +335,7 @@ class Tests(unittest.TestCase): expected.code = defines.Codes.CREATED.number expected.token = None expected.payload = None - expected.location_path = "/test_post" + expected.location_path = "test_post" self.current_mid += 1 self._test_with_client([(req, expected)]) @@ -340,8 +343,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_07(self): print("TD_COAP_CORE_07") path = "/test" - req = Request() + req = Request() req.code = defines.Codes.PUT.number req.uri_path = path req.type = defines.Types["NON"] @@ -363,8 +366,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_08(self): print("TD_COAP_CORE_08") path = "/test" - req = Request() + req = Request() req.code = defines.Codes.DELETE.number req.uri_path = path req.type = defines.Types["NON"] @@ -384,8 +387,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_09(self): print("TD_COAP_CORE_09") path = "/separate" - req = Request() + req = Request() req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["CON"] @@ -412,8 +415,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_10(self): print("TD_COAP_CORE_10") path = "/test" - req = Request() + req = Request() req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["CON"] @@ -435,8 +438,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_12(self): print("TD_COAP_CORE_12") path = "/seg1/seg2/seg3" - req = Request() + req = Request() req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["CON"] @@ -455,8 +458,8 @@ class Tests(unittest.TestCase): def test_td_coap_core_13(self): print("TD_COAP_CORE_13") path = "/query?first=1&second=2&third=3" - req = Request() + req = Request() req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["CON"] @@ -476,8 +479,8 @@ class Tests(unittest.TestCase): def test_td_coap_obs_01(self): print("TD_COAP_OBS_01") path = "/obs" - req = Request() + req = Request() req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["CON"] @@ -508,8 +511,8 @@ class Tests(unittest.TestCase): def test_td_coap_obs_03(self): print("TD_COAP_OBS_03") path = "/obs" - req = Request() + req = Request() req.code = defines.Codes.GET.number req.uri_path = path req.type = defines.Types["CON"] @@ -566,6 +569,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (0, 1, 1024) + expected.size2 = 1990 exchange1 = (req, expected) self.current_mid += 1 @@ -586,6 +590,7 @@ class Tests(unittest.TestCase): expected.token = None expected.payload = None expected.block2 = (1, 0, 1024) + expected.size2 = 1990 exchange2 = (req, expected) self.current_mid += 1 @@ -632,6 +637,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee when I first awoke, I could hardly tell it from the quilt, they so blended their hues together; and it was only by the sense of weight and pressure that I could tell that Queequeg was hugging""" expected.block2 = (1, 0, 1024) + expected.size2 = 1990 exchange1 = (req, expected) self.current_mid += 1 @@ -677,6 +683,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee when I first awoke, I could hardly tell it from the quilt, they so blended their hues together; and it was only by the sense of weight and pressure that I could tell that Queequeg was hugging""" expected.block2 = (1, 0, 1024) + expected.size2 = 1990 exchange1 = (req, expected) self.current_mid += 1 @@ -702,6 +709,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee expected.token = None expected.payload = None expected.block2 = (0, 1, 1024) + expected.size2 = 1990 exchange1 = (req, expected) self.current_mid += 1 @@ -722,6 +730,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee expected.token = None expected.payload = None expected.block2 = (1, 0, 1024) + expected.size2 = 1990 exchange2 = (req, expected) self.current_mid += 1 @@ -796,6 +805,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee def test_duplicate(self): print("TEST_DUPLICATE") path = "/test" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -810,11 +820,13 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee expected.token = None self.current_mid += 1 + self._test_plugtest([(req, expected), (req, expected)]) def test_duplicate_not_completed(self): print("TEST_DUPLICATE_NOT_COMPLETED") path = "/long" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -835,11 +847,13 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee expected2.token = None self.current_mid += 1 + self._test_plugtest([(req, None), (req, expected), (None, expected2)]) def test_no_response(self): print("TEST_NO_RESPONSE") path = "/long" + req = Request() req.code = defines.Codes.GET.number req.uri_path = path @@ -860,13 +874,14 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee expected2.token = None self.current_mid += 1 + self._test_plugtest([(req, expected), (None, expected2), (None, expected2), (None, expected2)]) def test_edit_resource(self): print("TEST_EDIT_RESOURCE") path = "/obs" - req = Request() + req = Request() req.code = defines.Codes.POST.number req.uri_path = path req.type = defines.Types["CON"] @@ -880,10 +895,11 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee expected.code = defines.Codes.CHANGED.number expected.token = None expected.payload = None - expected.location_path = "/obs" self.current_mid += 1 + self._test_with_client([(req, expected)]) + if __name__ == '__main__': unittest.main() diff --git a/plugtest_coapserver.py b/plugtest_coapserver.py index 2b91918..44585d9 100644 --- a/plugtest_coapserver.py +++ b/plugtest_coapserver.py @@ -1,9 +1,9 @@ import getopt -import logging import sys + +from coapthon.server.coap import CoAP from plugtest_resources import TestResource, SeparateResource, ObservableResource, LargeResource, LargeUpdateResource, \ LongResource -from coapthon.server.coap import CoAP __author__ = 'Giacomo Tanganelli' @@ -11,33 +11,15 @@ __author__ = 'Giacomo Tanganelli' class CoAPServerPlugTest(CoAP): def __init__(self, host, port, multicast=False, starting_mid=None): CoAP.__init__(self, (host, port), multicast, starting_mid) - - # create logger - logger = logging.getLogger(__name__) - logger.setLevel(logging.DEBUG) - - # create console handler and set level to debug - ch = logging.StreamHandler() - ch.setLevel(logging.DEBUG) - - # create formatter - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - - # add formatter to ch - ch.setFormatter(formatter) - - # add ch to logger - logger.addHandler(ch) - self.add_resource('test/', TestResource()) self.add_resource('separate/', SeparateResource()) self.add_resource('seg1/', TestResource()) self.add_resource('seg1/seg2/', TestResource()) self.add_resource('seg1/seg2/seg3/', TestResource()) self.add_resource('query/', TestResource()) - self.add_resource("obs/", ObservableResource(coap_server=self)) - self.add_resource("large/", LargeResource(coap_server=self)) - self.add_resource("large-update/", LargeUpdateResource(coap_server=self)) + self.add_resource('obs/', ObservableResource(coap_server=self)) + self.add_resource('large/', LargeResource(coap_server=self)) + self.add_resource('large-update/', LargeUpdateResource(coap_server=self)) self.add_resource('long/', LongResource()) @@ -66,9 +48,7 @@ def main(argv): # pragma: no cover try: server.listen(10) except KeyboardInterrupt: - print("Server Shutdown") server.close() - print("Exiting...") if __name__ == "__main__": diff --git a/plugtest_resources.py b/plugtest_resources.py index ca4ccd3..b860d94 100644 --- a/plugtest_resources.py +++ b/plugtest_resources.py @@ -1,10 +1,9 @@ -# coding=utf-8 +# -*- coding: utf-8 -*- + import logging import threading import time -import datetime - from coapthon import defines from coapthon.resources.resource import Resource diff --git a/requirements.txt b/requirements.txt index 5c59f63..09139d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Sphinx==1.2.2 -cachetools==2.0.0 +Sphinx>=1.2.2 +cachetools>=2.0.0 diff --git a/setup.py b/setup.py index b2c96e8..be333d5 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,33 @@ from distutils.core import setup +import datetime setup( name='CoAPthon3', - version='1.0.1', - packages=['coapthon', 'coapthon.caching', 'coapthon.layers', 'coapthon.client', 'coapthon.server', 'coapthon.messages', - 'coapthon.forward_proxy', 'coapthon.resources', 'coapthon.reverse_proxy'], - url='https://github.com/Tanganelli/CoAPthon3', + version='1.0.1+fb.' + datetime.datetime.now().strftime("%Y%m%d%H%M"), + packages=[ + 'coapthon', + 'coapthon.caching', + 'coapthon.client', + 'coapthon.forward_proxy', + 'coapthon.layers', + 'coapthon.messages', + 'coapthon.resources', + 'coapthon.reverse_proxy', + 'coapthon.server', + ], license='MIT License', author='Giacomo Tanganelli', author_email='giacomo.tanganelli@for.unipi.it', - download_url='https://github.com/Tanganelli/CoAPthon3/archive/1.0.1.tar.gz', - description='CoAPthon is a python library to the CoAP protocol. ', - scripts=['coapserver.py', 'coapclient.py', 'exampleresources.py', 'coapforwardproxy.py', 'coapreverseproxy.py'], + maintainer="Bjoern Freise", + maintainer_email="mcfreis@gmx.net", + url="https://github.com/mcfreis/CoAPthon3", + description='CoAPthon is a python library to the CoAP protocol.', + scripts=[ + 'coapclient.py', + 'coapforwardproxy.py', + 'coapreverseproxy.py', + 'coapserver.py', + 'exampleresources.py', + ], requires=['sphinx', 'cachetools'] )