Merge branch 'master' into fix-token
This commit is contained in:
commit
92c0258241
53 changed files with 1982 additions and 5955 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -24,6 +24,7 @@ wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
.idea/
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# Usually these files are written by a python script from a template
|
||||||
|
|
42
MANIFEST
Normal file
42
MANIFEST
Normal file
|
@ -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
|
|
@ -1,8 +1,9 @@
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
import random
|
import random
|
||||||
import socket
|
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
import time
|
||||||
|
|
||||||
from coapclient import HelperClient
|
from coapclient import HelperClient
|
||||||
from coapforwardproxy import CoAPForwardProxy
|
from coapforwardproxy import CoAPForwardProxy
|
||||||
from coapserver import CoAPServer
|
from coapserver import CoAPServer
|
||||||
|
@ -10,8 +11,6 @@ from coapthon import defines
|
||||||
from coapthon.messages.option import Option
|
from coapthon.messages.option import Option
|
||||||
from coapthon.messages.request import Request
|
from coapthon.messages.request import Request
|
||||||
from coapthon.messages.response import Response
|
from coapthon.messages.response import Response
|
||||||
from coapthon.serializer import Serializer
|
|
||||||
import time
|
|
||||||
|
|
||||||
__author__ = 'Emilio Vallati'
|
__author__ = 'Emilio Vallati'
|
||||||
__version__ = "1.0"
|
__version__ = "1.0"
|
||||||
|
@ -24,10 +23,10 @@ class Tests(unittest.TestCase):
|
||||||
self.current_mid = random.randint(1, 1000)
|
self.current_mid = random.randint(1, 1000)
|
||||||
self.server_mid = random.randint(1000, 2000)
|
self.server_mid = random.randint(1000, 2000)
|
||||||
self.server = CoAPServer("127.0.0.1", 5684)
|
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.server_thread.start()
|
||||||
self.proxy = CoAPForwardProxy("127.0.0.1", 5683, cache=True)
|
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.proxy_thread.start()
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
|
|
||||||
|
@ -459,7 +458,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.etag = str(1)
|
expected.etag = str(1)
|
||||||
expected.location_path = "etag"
|
|
||||||
|
|
||||||
exchange3 = (req3, expected)
|
exchange3 = (req3, expected)
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ def main(): # pragma: no cover
|
||||||
"payload_file="])
|
"payload_file="])
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError as err:
|
||||||
# print help information and exit:
|
# 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()
|
usage()
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
|
@ -107,7 +107,7 @@ def main(): # pragma: no cover
|
||||||
usage()
|
usage()
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
response = client.get(path)
|
response = client.get(path)
|
||||||
print((response.pretty_print()))
|
print(response.pretty_print())
|
||||||
client.stop()
|
client.stop()
|
||||||
elif op == "OBSERVE":
|
elif op == "OBSERVE":
|
||||||
if path is None:
|
if path is None:
|
||||||
|
@ -122,7 +122,7 @@ def main(): # pragma: no cover
|
||||||
usage()
|
usage()
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
response = client.delete(path)
|
response = client.delete(path)
|
||||||
print((response.pretty_print()))
|
print(response.pretty_print())
|
||||||
client.stop()
|
client.stop()
|
||||||
elif op == "POST":
|
elif op == "POST":
|
||||||
if path is None:
|
if path is None:
|
||||||
|
@ -134,7 +134,7 @@ def main(): # pragma: no cover
|
||||||
usage()
|
usage()
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
response = client.post(path, payload)
|
response = client.post(path, payload)
|
||||||
print((response.pretty_print()))
|
print(response.pretty_print())
|
||||||
client.stop()
|
client.stop()
|
||||||
elif op == "PUT":
|
elif op == "PUT":
|
||||||
if path is None:
|
if path is None:
|
||||||
|
@ -146,11 +146,11 @@ def main(): # pragma: no cover
|
||||||
usage()
|
usage()
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
response = client.put(path, payload)
|
response = client.put(path, payload)
|
||||||
print((response.pretty_print()))
|
print(response.pretty_print())
|
||||||
client.stop()
|
client.stop()
|
||||||
elif op == "DISCOVER":
|
elif op == "DISCOVER":
|
||||||
response = client.discover()
|
response = client.discover()
|
||||||
print((response.pretty_print()))
|
print(response.pretty_print())
|
||||||
client.stop()
|
client.stop()
|
||||||
else:
|
else:
|
||||||
print("Operation not recognized")
|
print("Operation not recognized")
|
||||||
|
|
|
@ -11,7 +11,7 @@ class CoAPForwardProxy(CoAP):
|
||||||
def __init__(self, host, port, multicast=False, cache=False):
|
def __init__(self, host, port, multicast=False, cache=False):
|
||||||
CoAP.__init__(self, (host, port), multicast=multicast, cache=cache)
|
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
|
def usage(): # pragma: no cover
|
||||||
|
|
|
@ -51,7 +51,7 @@ if __name__ == '__main__':
|
||||||
ping_cnt = 0 # global ping cnt
|
ping_cnt = 0 # global ping cnt
|
||||||
|
|
||||||
print('COAP ping script')
|
print('COAP ping script')
|
||||||
print(('COAP ping to: %s:%s...' % (host, port)))
|
print('COAP ping to: %s:%s...' % (host, port))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
@ -69,7 +69,7 @@ if __name__ == '__main__':
|
||||||
msg += struct.pack("B", ping_no)
|
msg += struct.pack("B", ping_no)
|
||||||
|
|
||||||
try :
|
try :
|
||||||
print(('[0x%08X] Send ping:' % (ping_cnt), [hex(ord(c)) for c in msg]))
|
print('[0x%08X] Send ping:' % (ping_cnt), [hex(ord(c)) for c in msg])
|
||||||
# Set the whole string
|
# Set the whole string
|
||||||
s.sendto(msg, (host, port))
|
s.sendto(msg, (host, port))
|
||||||
s.settimeout(2 + sleep_sec)
|
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
|
# We need to check if ping peyload counter is the same in reply
|
||||||
status = bytes(msg)[3] == bytes(reply)[3]
|
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:
|
except socket.error as e:
|
||||||
print(('Error: socket.error: ', str(e)))
|
print('Error: socket.error: ', str(e))
|
||||||
#print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
|
|
||||||
sleep(3) # Waiting to recover ;)
|
sleep(3) # Waiting to recover ;)
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
print("Error: closing socket")
|
print("Error: closing socket")
|
||||||
|
|
|
@ -12,7 +12,7 @@ class CoAPReverseProxy(CoAP):
|
||||||
CoAP.__init__(self, (host, port), xml_file=xml_file, multicast=multicast, starting_mid=starting_mid,
|
CoAP.__init__(self, (host, port), xml_file=xml_file, multicast=multicast, starting_mid=starting_mid,
|
||||||
cache=cache)
|
cache=cache)
|
||||||
|
|
||||||
print(("CoAP Proxy start on " + host + ":" + str(port)))
|
print("CoAP Proxy start on " + host + ":" + str(port))
|
||||||
|
|
||||||
|
|
||||||
def usage(): # pragma: no cover
|
def usage(): # pragma: no cover
|
||||||
|
|
|
@ -26,8 +26,8 @@ class CoAPServer(CoAP):
|
||||||
self.add_resource('advanced/', AdvancedResource())
|
self.add_resource('advanced/', AdvancedResource())
|
||||||
self.add_resource('advancedSeparate/', AdvancedResourceSeparate())
|
self.add_resource('advancedSeparate/', AdvancedResourceSeparate())
|
||||||
|
|
||||||
print(("CoAP Server start on " + host + ":" + str(port)))
|
print("CoAP Server start on " + host + ":" + str(port))
|
||||||
print((self.root.dump()))
|
print(self.root.dump())
|
||||||
|
|
||||||
|
|
||||||
def usage(): # pragma: no cover
|
def usage(): # pragma: no cover
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,7 @@ class CoapLRUCache(CoapCache):
|
||||||
:param element:
|
:param element:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
logger.debug("updating cache, key: %s, element: %s", \
|
logger.debug("updating cache, key: %s, element: %s", key.hashkey, element)
|
||||||
key.hashkey, element)
|
|
||||||
self.cache.update([(key.hashkey, element)])
|
self.cache.update([(key.hashkey, element)])
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
|
|
|
@ -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())
|
|
||||||
])))
|
|
|
@ -1,9 +1,9 @@
|
||||||
import logging.config
|
import logging
|
||||||
import os
|
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import collections
|
||||||
|
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
from coapthon.layers.blocklayer import BlockLayer
|
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.request import Request
|
||||||
from coapthon.messages.response import Response
|
from coapthon.messages.response import Response
|
||||||
from coapthon.serializer import Serializer
|
from coapthon.serializer import Serializer
|
||||||
import collections
|
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__author__ = 'Giacomo Tanganelli'
|
||||||
|
@ -66,6 +65,13 @@ class CoAP(object):
|
||||||
|
|
||||||
self._receiver_thread = None
|
self._receiver_thread = None
|
||||||
|
|
||||||
|
def purge_transactions(self, timeout_time=defines.EXCHANGE_LIFETIME):
|
||||||
|
"""
|
||||||
|
Clean old transactions
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._messageLayer.purge(timeout_time)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Stop the client.
|
Stop the client.
|
||||||
|
@ -76,7 +82,7 @@ class CoAP(object):
|
||||||
event.set()
|
event.set()
|
||||||
if self._receiver_thread is not None:
|
if self._receiver_thread is not None:
|
||||||
self._receiver_thread.join()
|
self._receiver_thread.join()
|
||||||
self._socket.close()
|
# self._socket.close()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_mid(self):
|
def current_mid(self):
|
||||||
|
@ -97,16 +103,21 @@ class CoAP(object):
|
||||||
assert isinstance(c, int)
|
assert isinstance(c, int)
|
||||||
self._currentMID = c
|
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.
|
Prepare a message to send on the UDP socket. Eventually set retransmissions.
|
||||||
|
|
||||||
:param message: the message to send
|
:param message: the message to send
|
||||||
|
:param no_response: whether to await a response from the request
|
||||||
"""
|
"""
|
||||||
if isinstance(message, Request):
|
if isinstance(message, Request):
|
||||||
request = self._requestLayer.send_request(message)
|
request = self._requestLayer.send_request(message)
|
||||||
request = self._observeLayer.send_request(request)
|
request = self._observeLayer.send_request(request)
|
||||||
request = self._blockLayer.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)
|
transaction = self._messageLayer.send_request(request)
|
||||||
self.send_datagram(transaction.request)
|
self.send_datagram(transaction.request)
|
||||||
if transaction.request.type == defines.Types["CON"]:
|
if transaction.request.type == defines.Types["CON"]:
|
||||||
|
@ -149,7 +160,7 @@ class CoAP(object):
|
||||||
:param message: the message to send
|
:param message: the message to send
|
||||||
"""
|
"""
|
||||||
host, port = message.destination
|
host, port = message.destination
|
||||||
logger.debug("send_datagram - " + str(message))
|
logger.info("send_datagram - " + str(message))
|
||||||
serializer = Serializer()
|
serializer = Serializer()
|
||||||
raw_message = serializer.serialize(message)
|
raw_message = serializer.serialize(message)
|
||||||
|
|
||||||
|
@ -264,7 +275,7 @@ class CoAP(object):
|
||||||
message = serializer.deserialize(datagram, source)
|
message = serializer.deserialize(datagram, source)
|
||||||
|
|
||||||
if isinstance(message, Response):
|
if isinstance(message, Response):
|
||||||
logger.debug("receive_datagram - " + str(message))
|
logger.info("receive_datagram - " + str(message))
|
||||||
transaction, send_ack = self._messageLayer.receive_response(message)
|
transaction, send_ack = self._messageLayer.receive_response(message)
|
||||||
if transaction is None: # pragma: no cover
|
if transaction is None: # pragma: no cover
|
||||||
continue
|
continue
|
||||||
|
@ -291,6 +302,7 @@ class CoAP(object):
|
||||||
self._messageLayer.receive_empty(message)
|
self._messageLayer.receive_empty(message)
|
||||||
|
|
||||||
logger.debug("Exiting receiver Thread due to request")
|
logger.debug("Exiting receiver Thread due to request")
|
||||||
|
self._socket.close()
|
||||||
|
|
||||||
def _send_ack(self, transaction):
|
def _send_ack(self, transaction):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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)
|
|
|
@ -223,17 +223,26 @@ class HelperClient(object):
|
||||||
:param request: the request to send
|
:param request: the request to send
|
||||||
:param callback: the callback function to invoke upon response
|
:param callback: the callback function to invoke upon response
|
||||||
:param timeout: the timeout of the request
|
:param timeout: the timeout of the request
|
||||||
|
:param no_response: whether to await a response from the request
|
||||||
:return: the response
|
:return: the response
|
||||||
"""
|
"""
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
thread = threading.Thread(target=self._thread_body, args=(request, callback))
|
thread = threading.Thread(target=self._thread_body, args=(request, callback))
|
||||||
thread.start()
|
thread.start()
|
||||||
else:
|
else:
|
||||||
self.protocol.send_message(request)
|
self.protocol.send_message(request, no_response=no_response)
|
||||||
if no_response:
|
if no_response:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
while True:
|
||||||
response = self.queue.get(block=True, timeout=timeout)
|
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:
|
except Empty:
|
||||||
#if timeout is set
|
#if timeout is set
|
||||||
response = None
|
response = None
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import array
|
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__author__ = 'Giacomo Tanganelli'
|
||||||
|
@ -124,6 +125,7 @@ class OptionRegistry(object):
|
||||||
LOCATION_QUERY = OptionItem(20,"Location-Query",STRING, True, None)
|
LOCATION_QUERY = OptionItem(20,"Location-Query",STRING, True, None)
|
||||||
BLOCK2 = OptionItem(23, "Block2", INTEGER, False, None)
|
BLOCK2 = OptionItem(23, "Block2", INTEGER, False, None)
|
||||||
BLOCK1 = OptionItem(27, "Block1", 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_URI = OptionItem(35, "Proxy-Uri", STRING, False, None)
|
||||||
PROXY_SCHEME = OptionItem(39, "Proxy-Schema", STRING, False, None)
|
PROXY_SCHEME = OptionItem(39, "Proxy-Schema", STRING, False, None)
|
||||||
SIZE1 = OptionItem(60, "Size1", INTEGER, False, None)
|
SIZE1 = OptionItem(60, "Size1", INTEGER, False, None)
|
||||||
|
@ -147,6 +149,7 @@ class OptionRegistry(object):
|
||||||
20: LOCATION_QUERY,
|
20: LOCATION_QUERY,
|
||||||
23: BLOCK2,
|
23: BLOCK2,
|
||||||
27: BLOCK1,
|
27: BLOCK1,
|
||||||
|
28: SIZE2,
|
||||||
35: PROXY_URI,
|
35: PROXY_URI,
|
||||||
39: PROXY_SCHEME,
|
39: PROXY_SCHEME,
|
||||||
60: SIZE1,
|
60: SIZE1,
|
||||||
|
@ -165,7 +168,7 @@ class OptionRegistry(object):
|
||||||
:return: option flags
|
:return: option flags
|
||||||
:rtype: 3-tuple (critical, unsafe, no-cache)
|
:rtype: 3-tuple (critical, unsafe, no-cache)
|
||||||
"""
|
"""
|
||||||
opt_bytes = array.array('B', '\0\0')
|
opt_bytes = bytearray(2)
|
||||||
if option_num < 256:
|
if option_num < 256:
|
||||||
s = struct.Struct("!B")
|
s = struct.Struct("!B")
|
||||||
s.pack_into(opt_bytes, 0, option_num)
|
s.pack_into(opt_bytes, 0, option_num)
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import logging.config
|
import logging
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
from coapthon.layers.blocklayer import BlockLayer
|
from coapthon.layers.blocklayer import BlockLayer
|
||||||
from coapthon.layers.cachelayer import CacheLayer
|
from coapthon.layers.cachelayer import CacheLayer
|
||||||
|
@ -88,27 +86,26 @@ class CoAP(object):
|
||||||
# Allow multiple copies of this program on one machine
|
# Allow multiple copies of this program on one machine
|
||||||
# (not strictly needed)
|
# (not strictly needed)
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
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)
|
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._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:
|
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)
|
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||||
|
|
||||||
# Allow multiple copies of this program on one machine
|
# Allow multiple copies of this program on one machine
|
||||||
# (not strictly needed)
|
# (not strictly needed)
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
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]
|
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
|
||||||
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
||||||
mreq = group_bin + struct.pack('@I', 0)
|
mreq = group_bin + struct.pack('@I', 0)
|
||||||
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
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:
|
else:
|
||||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
if addrinfo[0] == socket.AF_INET: # IPv4
|
||||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
@ -159,7 +156,7 @@ class CoAP(object):
|
||||||
self.stopped.set()
|
self.stopped.set()
|
||||||
for event in self.to_be_stopped:
|
for event in self.to_be_stopped:
|
||||||
event.set()
|
event.set()
|
||||||
self._socket.close()
|
# self._socket.close()
|
||||||
|
|
||||||
def receive_datagram(self, args):
|
def receive_datagram(self, args):
|
||||||
"""
|
"""
|
||||||
|
@ -189,7 +186,7 @@ class CoAP(object):
|
||||||
rst.code = message
|
rst.code = message
|
||||||
self.send_datagram(rst)
|
self.send_datagram(rst)
|
||||||
return
|
return
|
||||||
logger.debug("receive_datagram - " + str(message))
|
logger.info("receive_datagram - " + str(message))
|
||||||
if isinstance(message, Request):
|
if isinstance(message, Request):
|
||||||
|
|
||||||
transaction = self._messageLayer.receive_request(message)
|
transaction = self._messageLayer.receive_request(message)
|
||||||
|
@ -269,7 +266,7 @@ class CoAP(object):
|
||||||
"""
|
"""
|
||||||
if not self.stopped.isSet():
|
if not self.stopped.isSet():
|
||||||
host, port = message.destination
|
host, port = message.destination
|
||||||
logger.debug("send_datagram - " + str(message))
|
logger.info("send_datagram - " + str(message))
|
||||||
serializer = Serializer()
|
serializer = Serializer()
|
||||||
|
|
||||||
message = serializer.serialize(message)
|
message = serializer.serialize(message)
|
||||||
|
|
|
@ -175,7 +175,7 @@ class HCProxyHandler(BaseHTTPRequestHandler):
|
||||||
logger.debug(payload)
|
logger.debug(payload)
|
||||||
coap_response = self.client.put(self.coap_uri.path, payload)
|
coap_response = self.client.put(self.coap_uri.path, payload)
|
||||||
self.client.stop()
|
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)
|
self.set_http_response(coap_response)
|
||||||
|
|
||||||
def do_DELETE(self):
|
def do_DELETE(self):
|
||||||
|
@ -185,7 +185,7 @@ class HCProxyHandler(BaseHTTPRequestHandler):
|
||||||
self.do_initial_operations()
|
self.do_initial_operations()
|
||||||
coap_response = self.client.delete(self.coap_uri.path)
|
coap_response = self.client.delete(self.coap_uri.path)
|
||||||
self.client.stop()
|
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)
|
self.set_http_response(coap_response)
|
||||||
|
|
||||||
def do_CONNECT(self):
|
def do_CONNECT(self):
|
||||||
|
|
|
@ -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 <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
|
|
||||||
|
|
||||||
: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 = "<html><body><h1>", coap_response.payload, "</h1></body></html>"
|
|
||||||
self.wfile.write("".join(body))
|
|
||||||
else:
|
|
||||||
self.wfile.write("<html><body><h1>None</h1></body></html>")
|
|
||||||
|
|
||||||
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()
|
|
|
@ -1,5 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
|
from coapthon import utils
|
||||||
from coapthon.messages.request import Request
|
from coapthon.messages.request import Request
|
||||||
from coapthon.messages.response import Response
|
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.
|
Handle the Blockwise options. Hides all the exchange to both servers and clients.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._block1_sent = {}
|
self._block1_sent = {} # type: dict[hash, BlockItem]
|
||||||
self._block2_sent = {}
|
self._block2_sent = {} # type: dict[hash, BlockItem]
|
||||||
self._block1_receive = {}
|
self._block1_receive = {} # type: dict[hash, BlockItem]
|
||||||
self._block2_receive = {}
|
self._block2_receive = {} # type: dict[hash, BlockItem]
|
||||||
|
|
||||||
def receive_request(self, transaction):
|
def receive_request(self, transaction):
|
||||||
"""
|
"""
|
||||||
|
@ -49,7 +51,7 @@ class BlockLayer(object):
|
||||||
"""
|
"""
|
||||||
if transaction.request.block2 is not None:
|
if transaction.request.block2 is not None:
|
||||||
host, port = transaction.request.source
|
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
|
num, m, size = transaction.request.block2
|
||||||
if key_token in self._block2_receive:
|
if key_token in self._block2_receive:
|
||||||
self._block2_receive[key_token].num = num
|
self._block2_receive[key_token].num = num
|
||||||
|
@ -58,16 +60,20 @@ class BlockLayer(object):
|
||||||
del transaction.request.block2
|
del transaction.request.block2
|
||||||
else:
|
else:
|
||||||
# early negotiation
|
# early negotiation
|
||||||
byte = 0
|
byte = num * size
|
||||||
self._block2_receive[key_token] = BlockItem(byte, num, m, size)
|
self._block2_receive[key_token] = BlockItem(byte, num, m, size)
|
||||||
del transaction.request.block2
|
del transaction.request.block2
|
||||||
|
|
||||||
elif transaction.request.block1 is not None:
|
elif transaction.request.block1 is not None:
|
||||||
# POST or PUT
|
# POST or PUT
|
||||||
host, port = transaction.request.source
|
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
|
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:
|
if key_token in self._block1_receive:
|
||||||
|
# n-th block
|
||||||
content_type = transaction.request.content_type
|
content_type = transaction.request.content_type
|
||||||
if num != self._block1_receive[key_token].num \
|
if num != self._block1_receive[key_token].num \
|
||||||
or content_type != self._block1_receive[key_token].content_type:
|
or content_type != self._block1_receive[key_token].content_type:
|
||||||
|
@ -118,7 +124,7 @@ class BlockLayer(object):
|
||||||
:return: the edited transaction
|
:return: the edited transaction
|
||||||
"""
|
"""
|
||||||
host, port = transaction.response.source
|
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:
|
if key_token in self._block1_sent and transaction.response.block1 is not None:
|
||||||
item = self._block1_sent[key_token]
|
item = self._block1_sent[key_token]
|
||||||
transaction.block_transfer = True
|
transaction.block_transfer = True
|
||||||
|
@ -145,6 +151,8 @@ class BlockLayer(object):
|
||||||
else:
|
else:
|
||||||
item.m = 1
|
item.m = 1
|
||||||
request.block1 = (item.num, item.m, item.size)
|
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:
|
elif transaction.response.block2 is not None:
|
||||||
|
|
||||||
num, m, size = transaction.response.block2
|
num, m, size = transaction.response.block2
|
||||||
|
@ -208,7 +216,7 @@ class BlockLayer(object):
|
||||||
:return: the edited transaction
|
:return: the edited transaction
|
||||||
"""
|
"""
|
||||||
host, port = transaction.request.source
|
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 \
|
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):
|
(transaction.response.payload is not None and len(transaction.response.payload) > defines.MAX_PAYLOAD):
|
||||||
if key_token in self._block2_receive:
|
if key_token in self._block2_receive:
|
||||||
|
@ -225,10 +233,13 @@ class BlockLayer(object):
|
||||||
|
|
||||||
self._block2_receive[key_token] = BlockItem(byte, num, m, size)
|
self._block2_receive[key_token] = BlockItem(byte, num, m, size)
|
||||||
|
|
||||||
if len(transaction.response.payload) > (byte + size):
|
# correct m
|
||||||
m = 1
|
m = 0 if ((num * size) + size) > len(transaction.response.payload) else 1
|
||||||
else:
|
# add size2 if requested or if payload is bigger than one datagram
|
||||||
m = 0
|
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]
|
transaction.response.payload = transaction.response.payload[byte:byte + size]
|
||||||
del transaction.response.block2
|
del transaction.response.block2
|
||||||
transaction.response.block2 = (num, m, size)
|
transaction.response.block2 = (num, m, size)
|
||||||
|
@ -251,21 +262,24 @@ class BlockLayer(object):
|
||||||
assert isinstance(request, Request)
|
assert isinstance(request, Request)
|
||||||
if request.block1 or (request.payload is not None and len(request.payload) > defines.MAX_PAYLOAD):
|
if request.block1 or (request.payload is not None and len(request.payload) > defines.MAX_PAYLOAD):
|
||||||
host, port = request.destination
|
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:
|
if request.block1:
|
||||||
num, m, size = request.block1
|
num, m, size = request.block1
|
||||||
else:
|
else:
|
||||||
num = 0
|
num = 0
|
||||||
m = 1
|
m = 1
|
||||||
size = defines.MAX_PAYLOAD
|
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)
|
self._block1_sent[key_token] = BlockItem(size, num, m, size, request.payload, request.content_type)
|
||||||
request.payload = request.payload[0:size]
|
request.payload = request.payload[0:size]
|
||||||
del request.block1
|
del request.block1
|
||||||
request.block1 = (num, m, size)
|
request.block1 = (num, m, size)
|
||||||
elif request.block2:
|
elif request.block2:
|
||||||
host, port = request.destination
|
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
|
num, m, size = request.block2
|
||||||
item = BlockItem(size, num, m, size, "", None)
|
item = BlockItem(size, num, m, size, "", None)
|
||||||
self._block2_sent[key_token] = item
|
self._block2_sent[key_token] = item
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import copy
|
import copy
|
||||||
|
import logging
|
||||||
from coapthon.messages.request import Request
|
from coapthon.messages.request import Request
|
||||||
from coapclient import HelperClient
|
from coapclient import HelperClient
|
||||||
from coapthon.messages.response import Response
|
from coapthon.messages.response import Response
|
||||||
|
@ -8,6 +9,8 @@ from coapthon.utils import parse_uri
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__author__ = 'Giacomo Tanganelli'
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ForwardLayer(object):
|
class ForwardLayer(object):
|
||||||
"""
|
"""
|
||||||
|
@ -50,11 +53,12 @@ class ForwardLayer(object):
|
||||||
:rtype : Transaction
|
:rtype : Transaction
|
||||||
:return: the edited transaction
|
:return: the edited transaction
|
||||||
"""
|
"""
|
||||||
|
wkc_resource_is_defined = defines.DISCOVERY_URL in self._server.root
|
||||||
path = str("/" + transaction.request.uri_path)
|
path = str("/" + transaction.request.uri_path)
|
||||||
transaction.response = Response()
|
transaction.response = Response()
|
||||||
transaction.response.destination = transaction.request.source
|
transaction.response.destination = transaction.request.source
|
||||||
transaction.response.token = transaction.request.token
|
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)
|
transaction = self._server.resourceLayer.discover(transaction)
|
||||||
else:
|
else:
|
||||||
new = False
|
new = False
|
||||||
|
@ -146,8 +150,10 @@ class ForwardLayer(object):
|
||||||
request.destination = transaction.resource.remote_server
|
request.destination = transaction.resource.remote_server
|
||||||
request.payload = transaction.request.payload
|
request.payload = transaction.request.payload
|
||||||
request.code = transaction.request.code
|
request.code = transaction.request.code
|
||||||
|
logger.info("forward_request - " + str(request))
|
||||||
response = client.send_request(request)
|
response = client.send_request(request)
|
||||||
client.stop()
|
client.stop()
|
||||||
|
logger.info("forward_response - " + str(response))
|
||||||
transaction.response.payload = response.payload
|
transaction.response.payload = response.payload
|
||||||
transaction.response.code = response.code
|
transaction.response.code = response.code
|
||||||
transaction.response.options = response.options
|
transaction.response.options = response.options
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from coapthon import utils
|
||||||
from coapthon.messages.message import Message
|
from coapthon.messages.message import Message
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
from coapthon.messages.request import Request
|
from coapthon.messages.request import Request
|
||||||
|
@ -11,15 +14,6 @@ __author__ = 'Giacomo Tanganelli'
|
||||||
logger = logging.getLogger(__name__)
|
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):
|
class MessageLayer(object):
|
||||||
"""
|
"""
|
||||||
Handles matching between messages (Message ID) and request/response (Token)
|
Handles matching between messages (Message ID) and request/response (Token)
|
||||||
|
@ -48,17 +42,17 @@ class MessageLayer(object):
|
||||||
self._current_mid %= 65535
|
self._current_mid %= 65535
|
||||||
return current_mid
|
return current_mid
|
||||||
|
|
||||||
def purge(self):
|
def purge(self, timeout_time=defines.EXCHANGE_LIFETIME):
|
||||||
for k in list(self._transactions.keys()):
|
for k in list(self._transactions.keys()):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
transaction = self._transactions[k]
|
transaction = self._transactions[k]
|
||||||
if transaction.timestamp + defines.EXCHANGE_LIFETIME < now:
|
if transaction.timestamp + timeout_time < now:
|
||||||
logger.debug("Delete transaction")
|
logger.debug("Delete transaction")
|
||||||
del self._transactions[k]
|
del self._transactions[k]
|
||||||
for k in list(self._transactions_token.keys()):
|
for k in list(self._transactions_token.keys()):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
transaction = self._transactions_token[k]
|
transaction = self._transactions_token[k]
|
||||||
if transaction.timestamp + defines.EXCHANGE_LIFETIME < now:
|
if transaction.timestamp + timeout_time < now:
|
||||||
logger.debug("Delete transaction")
|
logger.debug("Delete transaction")
|
||||||
del self._transactions_token[k]
|
del self._transactions_token[k]
|
||||||
|
|
||||||
|
@ -71,13 +65,13 @@ class MessageLayer(object):
|
||||||
:rtype : Transaction
|
:rtype : Transaction
|
||||||
:return: the edited transaction
|
:return: the edited transaction
|
||||||
"""
|
"""
|
||||||
logger.debug("receive_request - " + str(request))
|
logger.info("receive_request - " + str(request))
|
||||||
try:
|
try:
|
||||||
host, port = request.source
|
host, port = request.source
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
key_mid = str_append_hash(host, port, request.mid)
|
key_mid = utils.str_append_hash(host, port, request.mid)
|
||||||
key_token = str_append_hash(host, port, request.token)
|
key_token = utils.str_append_hash(host, port, request.token)
|
||||||
|
|
||||||
if key_mid in list(self._transactions.keys()):
|
if key_mid in list(self._transactions.keys()):
|
||||||
# Duplicated
|
# Duplicated
|
||||||
|
@ -100,15 +94,16 @@ class MessageLayer(object):
|
||||||
:rtype : Transaction
|
:rtype : Transaction
|
||||||
:return: the transaction to which the response belongs to
|
:return: the transaction to which the response belongs to
|
||||||
"""
|
"""
|
||||||
logger.debug("receive_response - " + str(response))
|
logger.info("receive_response - " + str(response))
|
||||||
try:
|
try:
|
||||||
host, port = response.source
|
host, port = response.source
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
key_mid = str_append_hash(host, port, response.mid)
|
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_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.mid)
|
key_mid = utils.str_append_hash(host, port, response.mid)
|
||||||
key_token = str_append_hash(host, port, response.token)
|
key_mid_multicast = utils.str_append_hash(all_coap_nodes, port, response.mid)
|
||||||
key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.token)
|
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()):
|
if key_mid in list(self._transactions.keys()):
|
||||||
transaction = self._transactions[key_mid]
|
transaction = self._transactions[key_mid]
|
||||||
if response.token != transaction.request.token:
|
if response.token != transaction.request.token:
|
||||||
|
@ -146,15 +141,16 @@ class MessageLayer(object):
|
||||||
:rtype : Transaction
|
:rtype : Transaction
|
||||||
:return: the transaction to which the message belongs to
|
:return: the transaction to which the message belongs to
|
||||||
"""
|
"""
|
||||||
logger.debug("receive_empty - " + str(message))
|
logger.info("receive_empty - " + str(message))
|
||||||
try:
|
try:
|
||||||
host, port = message.source
|
host, port = message.source
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
key_mid = str_append_hash(host, port, message.mid)
|
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_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.mid)
|
key_mid = utils.str_append_hash(host, port, message.mid)
|
||||||
key_token = str_append_hash(host, port, message.token)
|
key_mid_multicast = utils.str_append_hash(all_coap_nodes, port, message.mid)
|
||||||
key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.token)
|
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()):
|
if key_mid in list(self._transactions.keys()):
|
||||||
transaction = self._transactions[key_mid]
|
transaction = self._transactions[key_mid]
|
||||||
elif key_token in self._transactions_token:
|
elif key_token in self._transactions_token:
|
||||||
|
@ -198,7 +194,7 @@ class MessageLayer(object):
|
||||||
:rtype : Transaction
|
:rtype : Transaction
|
||||||
:return: the created transaction
|
:return: the created transaction
|
||||||
"""
|
"""
|
||||||
logger.debug("send_request - " + str(request))
|
logger.info("send_request - " + str(request))
|
||||||
assert isinstance(request, Request)
|
assert isinstance(request, Request)
|
||||||
try:
|
try:
|
||||||
host, port = request.destination
|
host, port = request.destination
|
||||||
|
@ -213,10 +209,10 @@ class MessageLayer(object):
|
||||||
if transaction.request.mid is None:
|
if transaction.request.mid is None:
|
||||||
transaction.request.mid = self.fetch_mid()
|
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
|
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
|
self._transactions_token[key_token] = transaction
|
||||||
|
|
||||||
return self._transactions[key_mid]
|
return self._transactions[key_mid]
|
||||||
|
@ -230,7 +226,7 @@ class MessageLayer(object):
|
||||||
:rtype : Transaction
|
:rtype : Transaction
|
||||||
:return: the edited 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.response.type is None:
|
||||||
if transaction.request.type == defines.Types["CON"] and not transaction.request.acknowledged:
|
if transaction.request.type == defines.Types["CON"] and not transaction.request.acknowledged:
|
||||||
transaction.response.type = defines.Types["ACK"]
|
transaction.response.type = defines.Types["ACK"]
|
||||||
|
@ -249,7 +245,7 @@ class MessageLayer(object):
|
||||||
host, port = transaction.response.destination
|
host, port = transaction.response.destination
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
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
|
self._transactions[key_mid] = transaction
|
||||||
|
|
||||||
transaction.request.acknowledged = True
|
transaction.request.acknowledged = True
|
||||||
|
@ -265,14 +261,14 @@ class MessageLayer(object):
|
||||||
:type message: Message
|
:type message: Message
|
||||||
:param message: the ACK or RST message to send
|
: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:
|
if transaction is None:
|
||||||
try:
|
try:
|
||||||
host, port = message.destination
|
host, port = message.destination
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
key_mid = str_append_hash(host, port, message.mid)
|
key_mid = utils.str_append_hash(host, port, message.mid)
|
||||||
key_token = str_append_hash(host, port, message.token)
|
key_token = utils.str_append_hash(host, port, message.token)
|
||||||
if key_mid in self._transactions:
|
if key_mid in self._transactions:
|
||||||
transaction = self._transactions[key_mid]
|
transaction = self._transactions[key_mid]
|
||||||
related = transaction.response
|
related = transaction.response
|
||||||
|
|
|
@ -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
|
|
|
@ -1,6 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
|
from coapthon import utils
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__author__ = 'Giacomo Tanganelli'
|
||||||
|
|
||||||
|
@ -40,7 +42,7 @@ class ObserveLayer(object):
|
||||||
if request.observe == 0:
|
if request.observe == 0:
|
||||||
# Observe request
|
# Observe request
|
||||||
host, port = request.destination
|
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)
|
self._relations[key_token] = ObserveItem(time.time(), None, True, None)
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ class ObserveLayer(object):
|
||||||
:return: the modified transaction
|
:return: the modified transaction
|
||||||
"""
|
"""
|
||||||
host, port = transaction.response.source
|
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"]:
|
if key_token in self._relations and transaction.response.type == defines.Types["CON"]:
|
||||||
transaction.notification = True
|
transaction.notification = True
|
||||||
return transaction
|
return transaction
|
||||||
|
@ -70,7 +72,7 @@ class ObserveLayer(object):
|
||||||
:return: the message unmodified
|
:return: the message unmodified
|
||||||
"""
|
"""
|
||||||
host, port = message.destination
|
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"]:
|
if key_token in self._relations and message.type == defines.Types["RST"]:
|
||||||
del self._relations[key_token]
|
del self._relations[key_token]
|
||||||
return message
|
return message
|
||||||
|
@ -88,7 +90,7 @@ class ObserveLayer(object):
|
||||||
if transaction.request.observe == 0:
|
if transaction.request.observe == 0:
|
||||||
# Observe request
|
# Observe request
|
||||||
host, port = transaction.request.source
|
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
|
non_counter = 0
|
||||||
if key_token in self._relations:
|
if key_token in self._relations:
|
||||||
# Renew registration
|
# Renew registration
|
||||||
|
@ -98,7 +100,7 @@ class ObserveLayer(object):
|
||||||
self._relations[key_token] = ObserveItem(time.time(), non_counter, allowed, transaction)
|
self._relations[key_token] = ObserveItem(time.time(), non_counter, allowed, transaction)
|
||||||
elif transaction.request.observe == 1:
|
elif transaction.request.observe == 1:
|
||||||
host, port = transaction.request.source
|
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")
|
logger.info("Remove Subscriber")
|
||||||
try:
|
try:
|
||||||
del self._relations[key_token]
|
del self._relations[key_token]
|
||||||
|
@ -120,7 +122,7 @@ class ObserveLayer(object):
|
||||||
"""
|
"""
|
||||||
if empty.type == defines.Types["RST"]:
|
if empty.type == defines.Types["RST"]:
|
||||||
host, port = transaction.request.source
|
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")
|
logger.info("Remove Subscriber")
|
||||||
try:
|
try:
|
||||||
del self._relations[key_token]
|
del self._relations[key_token]
|
||||||
|
@ -138,7 +140,7 @@ class ObserveLayer(object):
|
||||||
:return: the transaction unmodified
|
:return: the transaction unmodified
|
||||||
"""
|
"""
|
||||||
host, port = transaction.request.source
|
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 key_token in self._relations:
|
||||||
if transaction.response.code == defines.Codes.CONTENT.number:
|
if transaction.response.code == defines.Codes.CONTENT.number:
|
||||||
if transaction.resource is not None and transaction.resource.observable:
|
if transaction.resource is not None and transaction.resource.observable:
|
||||||
|
@ -188,9 +190,9 @@ class ObserveLayer(object):
|
||||||
|
|
||||||
:param message: the message
|
:param message: the message
|
||||||
"""
|
"""
|
||||||
logger.debug("Remove Subcriber")
|
logger.info("Remove Subcriber")
|
||||||
host, port = message.destination
|
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:
|
try:
|
||||||
self._relations[key_token].transaction.completed = True
|
self._relations[key_token].transaction.completed = True
|
||||||
del self._relations[key_token]
|
del self._relations[key_token]
|
||||||
|
|
|
@ -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")
|
|
||||||
|
|
|
@ -52,11 +52,12 @@ class RequestLayer(object):
|
||||||
:rtype : Transaction
|
:rtype : Transaction
|
||||||
:return: the edited transaction with the response to the request
|
: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)
|
path = str("/" + transaction.request.uri_path)
|
||||||
transaction.response = Response()
|
transaction.response = Response()
|
||||||
transaction.response.destination = transaction.request.source
|
transaction.response.destination = transaction.request.source
|
||||||
transaction.response.token = transaction.request.token
|
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)
|
transaction = self._server.resourceLayer.discover(transaction)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -105,6 +105,8 @@ class ResourceLayer(object):
|
||||||
if resource.etag is not None:
|
if resource.etag is not None:
|
||||||
transaction.response.etag = resource.etag
|
transaction.response.etag = resource.etag
|
||||||
|
|
||||||
|
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
|
transaction.response.location_path = resource.path
|
||||||
|
|
||||||
if resource.location_query is not None and len(resource.location_query) > 0:
|
if resource.location_query is not None and len(resource.location_query) > 0:
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
from coapthon.utils import parse_blockwise
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
|
from coapthon import utils
|
||||||
from coapthon.messages.option import Option
|
from coapthon.messages.option import Option
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__author__ = 'Giacomo Tanganelli'
|
||||||
|
@ -124,6 +128,7 @@ class Message(object):
|
||||||
return
|
return
|
||||||
if not isinstance(value, bytes):
|
if not isinstance(value, bytes):
|
||||||
value = bytes(value)
|
value = bytes(value)
|
||||||
|
|
||||||
if len(value) > 256:
|
if len(value) > 256:
|
||||||
raise AttributeError
|
raise AttributeError
|
||||||
self._token = value
|
self._token = value
|
||||||
|
@ -547,7 +552,7 @@ class Message(object):
|
||||||
value = None
|
value = None
|
||||||
for option in self.options:
|
for option in self.options:
|
||||||
if option.number == defines.OptionRegistry.BLOCK1.number:
|
if option.number == defines.OptionRegistry.BLOCK1.number:
|
||||||
value = parse_blockwise(option.value)
|
value = utils.parse_blockwise(option.value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@block1.setter
|
@block1.setter
|
||||||
|
@ -599,7 +604,7 @@ class Message(object):
|
||||||
value = None
|
value = None
|
||||||
for option in self.options:
|
for option in self.options:
|
||||||
if option.number == defines.OptionRegistry.BLOCK2.number:
|
if option.number == defines.OptionRegistry.BLOCK2.number:
|
||||||
value = parse_blockwise(option.value)
|
value = utils.parse_blockwise(option.value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@block2.setter
|
@block2.setter
|
||||||
|
@ -641,6 +646,44 @@ class Message(object):
|
||||||
"""
|
"""
|
||||||
self.del_option_by_number(defines.OptionRegistry.BLOCK2.number)
|
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
|
@property
|
||||||
def line_print(self):
|
def line_print(self):
|
||||||
"""
|
"""
|
||||||
|
@ -653,10 +696,15 @@ class Message(object):
|
||||||
if self._code is None:
|
if self._code is None:
|
||||||
self._code = defines.Codes.EMPTY.number
|
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}, ["\
|
msg = "From {source}, To {destination}, {type}-{mid}, {code}-{token}, ["\
|
||||||
.format(source=self._source, destination=self._destination, type=inv_types[self._type], mid=self._mid,
|
.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:
|
for opt in self._options:
|
||||||
|
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 += "{name}: {value}, ".format(name=opt.name, value=opt.value)
|
||||||
msg += "]"
|
msg += "]"
|
||||||
if self.payload is not None:
|
if self.payload is not None:
|
||||||
|
@ -685,9 +733,9 @@ class Message(object):
|
||||||
msg += "MID: " + str(self._mid) + "\n"
|
msg += "MID: " + str(self._mid) + "\n"
|
||||||
if self._code is None:
|
if self._code is None:
|
||||||
self._code = 0
|
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 += "Code: " + str(defines.Codes.LIST[self._code].name) + "\n"
|
||||||
msg += "Token: " + str(self._token) + "\n"
|
msg += "Token: " + token + "\n"
|
||||||
for opt in self._options:
|
for opt in self._options:
|
||||||
msg += str(opt)
|
msg += str(opt)
|
||||||
msg += "Payload: " + "\n"
|
msg += "Payload: " + "\n"
|
||||||
|
|
|
@ -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
|
|
|
@ -42,7 +42,7 @@ class Option(object):
|
||||||
:return: the option value in the correct format depending on the option
|
:return: the option value in the correct format depending on the option
|
||||||
"""
|
"""
|
||||||
if type(self._value) is None:
|
if type(self._value) is None:
|
||||||
self._value = bytearray()
|
self._value = bytes()
|
||||||
opt_type = defines.OptionRegistry.LIST[self._number].value_type
|
opt_type = defines.OptionRegistry.LIST[self._number].value_type
|
||||||
if opt_type == defines.INTEGER:
|
if opt_type == defines.INTEGER:
|
||||||
if byte_len(self._value) > 0:
|
if byte_len(self._value) > 0:
|
||||||
|
@ -73,7 +73,6 @@ class Option(object):
|
||||||
else:
|
else:
|
||||||
if value is not None:
|
if value is not None:
|
||||||
value = bytes(value, "utf-8")
|
value = bytes(value, "utf-8")
|
||||||
|
|
||||||
self._value = value
|
self._value = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -178,6 +178,20 @@ class Request(Message):
|
||||||
return True
|
return True
|
||||||
return False
|
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):
|
def add_if_none_match(self):
|
||||||
"""
|
"""
|
||||||
Add the if-none-match option to the request.
|
Add the if-none-match option to the request.
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import logging.config
|
import logging
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import threading
|
import threading
|
||||||
import xml.etree.ElementTree as ElementTree
|
import xml.etree.ElementTree as ElementTree
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@ -81,6 +80,8 @@ class CoAP(object):
|
||||||
# Use given socket, could be a DTLS socket
|
# Use given socket, could be a DTLS socket
|
||||||
self._socket = sock
|
self._socket = sock
|
||||||
|
|
||||||
|
self.parse_config()
|
||||||
|
|
||||||
elif self.multicast: # pragma: no cover
|
elif self.multicast: # pragma: no cover
|
||||||
|
|
||||||
# Create a socket
|
# Create a socket
|
||||||
|
@ -94,27 +95,26 @@ class CoAP(object):
|
||||||
# Allow multiple copies of this program on one machine
|
# Allow multiple copies of this program on one machine
|
||||||
# (not strictly needed)
|
# (not strictly needed)
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
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)
|
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._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:
|
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)
|
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||||
|
|
||||||
# Allow multiple copies of this program on one machine
|
# Allow multiple copies of this program on one machine
|
||||||
# (not strictly needed)
|
# (not strictly needed)
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
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]
|
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
|
||||||
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
||||||
mreq = group_bin + struct.pack('@I', 0)
|
mreq = group_bin + struct.pack('@I', 0)
|
||||||
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
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:
|
else:
|
||||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
if addrinfo[0] == socket.AF_INET: # IPv4
|
||||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
@ -172,7 +172,8 @@ class CoAP(object):
|
||||||
host, port = response.source
|
host, port = response.source
|
||||||
|
|
||||||
if response.code == defines.Codes.CONTENT.number:
|
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.add_resource(name, resource)
|
||||||
self._mapping[name] = (host, port)
|
self._mapping[name] = (host, port)
|
||||||
self.parse_core_link_format(response.payload, 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]
|
dict_att[a[0]] = a[0]
|
||||||
link_format = link_format[result.end(0) + 1:]
|
link_format = link_format[result.end(0) + 1:]
|
||||||
# TODO handle observing
|
# TODO handle observing
|
||||||
resource = RemoteResource('server', remote_server, path, coap_server=self, visible=True, observable=False,
|
resource = RemoteResource('server', remote_server, path,
|
||||||
allow_children=True)
|
coap_server=self, visible=True, observable=False, allow_children=True)
|
||||||
resource.attributes = dict_att
|
resource.attributes = dict_att
|
||||||
self.add_resource(base_path + "/" + path, resource)
|
self.add_resource(base_path + "/" + path, resource)
|
||||||
|
|
||||||
|
@ -251,7 +252,7 @@ class CoAP(object):
|
||||||
self.stopped.set()
|
self.stopped.set()
|
||||||
for event in self.to_be_stopped:
|
for event in self.to_be_stopped:
|
||||||
event.set()
|
event.set()
|
||||||
self._socket.close()
|
# self._socket.close()
|
||||||
|
|
||||||
def receive_datagram(self, args):
|
def receive_datagram(self, args):
|
||||||
"""
|
"""
|
||||||
|
@ -272,7 +273,8 @@ class CoAP(object):
|
||||||
rst.code = message
|
rst.code = message
|
||||||
self.send_datagram(rst)
|
self.send_datagram(rst)
|
||||||
return
|
return
|
||||||
logger.debug("receive_datagram - " + str(message))
|
|
||||||
|
logger.info("receive_datagram - " + str(message))
|
||||||
if isinstance(message, Request):
|
if isinstance(message, Request):
|
||||||
|
|
||||||
transaction = self._messageLayer.receive_request(message)
|
transaction = self._messageLayer.receive_request(message)
|
||||||
|
@ -318,6 +320,7 @@ class CoAP(object):
|
||||||
transaction = self._blockLayer.send_response(transaction)
|
transaction = self._blockLayer.send_response(transaction)
|
||||||
|
|
||||||
transaction = self._cacheLayer.send_response(transaction)
|
transaction = self._cacheLayer.send_response(transaction)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
transaction = self._forwardLayer.receive_request_reverse(transaction)
|
transaction = self._forwardLayer.receive_request_reverse(transaction)
|
||||||
|
|
||||||
|
@ -352,7 +355,7 @@ class CoAP(object):
|
||||||
"""
|
"""
|
||||||
if not self.stopped.isSet():
|
if not self.stopped.isSet():
|
||||||
host, port = message.destination
|
host, port = message.destination
|
||||||
logger.debug("send_datagram - " + str(message))
|
logger.info("send_datagram - " + str(message))
|
||||||
serializer = Serializer()
|
serializer = Serializer()
|
||||||
message = serializer.serialize(message)
|
message = serializer.serialize(message)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
from coapthon.messages.request import Request
|
from coapthon.messages.request import Request
|
||||||
from coapthon.messages.response import Response
|
from coapthon.messages.response import Response
|
||||||
from coapthon.messages.option import Option
|
from coapthon.messages.option import Option
|
||||||
|
@ -67,6 +68,7 @@ class Serializer(object):
|
||||||
# the first 4 bits of the byte represent the option delta
|
# the first 4 bits of the byte represent the option delta
|
||||||
# delta = self._reader.read(4).uint
|
# delta = self._reader.read(4).uint
|
||||||
num, option_length, pos = Serializer.read_option_value_len_from_byte(next_byte, pos, values)
|
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
|
current_option += num
|
||||||
# read option
|
# read option
|
||||||
try:
|
try:
|
||||||
|
@ -78,8 +80,7 @@ class Serializer(object):
|
||||||
else:
|
else:
|
||||||
# If the non-critical option is unknown
|
# If the non-critical option is unknown
|
||||||
# (vendor-specific, proprietary) - just skip it
|
# (vendor-specific, proprietary) - just skip it
|
||||||
#log.err("unrecognized option %d" % current_option)
|
logger.warning("unrecognized option %d", current_option)
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
if option_length == 0:
|
if option_length == 0:
|
||||||
value = None
|
value = None
|
||||||
|
@ -110,13 +111,17 @@ class Serializer(object):
|
||||||
raise AttributeError("Packet length %s, pos %s" % (length_packet, pos))
|
raise AttributeError("Packet length %s, pos %s" % (length_packet, pos))
|
||||||
message.payload = ""
|
message.payload = ""
|
||||||
payload = values[pos:]
|
payload = values[pos:]
|
||||||
try:
|
if hasattr(message, 'payload_type') and message.payload_type in [
|
||||||
if message.payload_type == defines.Content_types["application/octet-stream"]:
|
defines.Content_types["application/octet-stream"],
|
||||||
|
defines.Content_types["application/exi"],
|
||||||
|
defines.Content_types["application/cbor"]
|
||||||
|
]:
|
||||||
message.payload = payload
|
message.payload = payload
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
message.payload = payload.decode("utf-8")
|
message.payload = payload.decode("utf-8")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
message.payload = payload.decode("utf-8")
|
message.payload = payload
|
||||||
pos += len(payload)
|
pos += len(payload)
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
@ -124,6 +129,9 @@ class Serializer(object):
|
||||||
return defines.Codes.BAD_REQUEST.number
|
return defines.Codes.BAD_REQUEST.number
|
||||||
except struct.error:
|
except struct.error:
|
||||||
return defines.Codes.BAD_REQUEST.number
|
return defines.Codes.BAD_REQUEST.number
|
||||||
|
except UnicodeDecodeError as e:
|
||||||
|
logger.debug(e)
|
||||||
|
return defines.Codes.BAD_REQUEST.number
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def serialize(message):
|
def serialize(message):
|
||||||
|
@ -137,7 +145,7 @@ class Serializer(object):
|
||||||
"""
|
"""
|
||||||
fmt = "!BBH"
|
fmt = "!BBH"
|
||||||
|
|
||||||
if message.token is None or message.token == "":
|
if message.token is None:
|
||||||
tkl = 0
|
tkl = 0
|
||||||
else:
|
else:
|
||||||
tkl = len(message.token)
|
tkl = len(message.token)
|
||||||
|
@ -195,7 +203,6 @@ class Serializer(object):
|
||||||
elif opt_type == defines.STRING:
|
elif opt_type == defines.STRING:
|
||||||
fmt += str(len(bytes(option.value, "utf-8"))) + "s"
|
fmt += str(len(bytes(option.value, "utf-8"))) + "s"
|
||||||
values.append(bytes(option.value, "utf-8"))
|
values.append(bytes(option.value, "utf-8"))
|
||||||
|
|
||||||
else: # OPAQUE
|
else: # OPAQUE
|
||||||
for b in option.value:
|
for b in option.value:
|
||||||
fmt += "B"
|
fmt += "B"
|
||||||
|
@ -220,9 +227,6 @@ class Serializer(object):
|
||||||
else:
|
else:
|
||||||
fmt += str(len(bytes(payload, "utf-8"))) + "s"
|
fmt += str(len(bytes(payload, "utf-8"))) + "s"
|
||||||
values.append(bytes(payload, "utf-8"))
|
values.append(bytes(payload, "utf-8"))
|
||||||
# for b in str(payload):
|
|
||||||
# fmt += "c"
|
|
||||||
# values.append(bytes(b, "utf-8"))
|
|
||||||
|
|
||||||
datagram = None
|
datagram = None
|
||||||
if values[1] is 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
|
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
|
@staticmethod
|
||||||
def read_option_value_len_from_byte(byte, pos, values):
|
def read_option_value_len_from_byte(byte, pos, values):
|
||||||
"""
|
"""
|
||||||
|
@ -301,7 +283,7 @@ class Serializer(object):
|
||||||
pos += 1
|
pos += 1
|
||||||
elif h_nibble == 14:
|
elif h_nibble == 14:
|
||||||
s = struct.Struct("!H")
|
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
|
pos += 2
|
||||||
else:
|
else:
|
||||||
raise AttributeError("Unsupported option number nibble " + str(h_nibble))
|
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
|
length = struct.unpack("!B", values[pos].to_bytes(1, "big"))[0] + 13
|
||||||
pos += 1
|
pos += 1
|
||||||
elif l_nibble == 14:
|
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
|
pos += 2
|
||||||
else:
|
else:
|
||||||
raise AttributeError("Unsupported option length nibble " + str(l_nibble))
|
raise AttributeError("Unsupported option length nibble " + str(l_nibble))
|
||||||
|
@ -321,7 +304,7 @@ class Serializer(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def convert_to_raw(number, value, length):
|
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 number: the option number
|
||||||
:param value: the option value
|
:param value: the option value
|
||||||
|
@ -348,11 +331,11 @@ class Serializer(object):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return bytearray(value, "utf-8")
|
return bytes(value, "utf-8")
|
||||||
elif isinstance(value, int):
|
elif isinstance(value, int):
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
return bytearray(value)
|
return bytes(value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def as_sorted_list(options):
|
def as_sorted_list(options):
|
||||||
|
|
|
@ -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
|
|
|
@ -1,9 +1,9 @@
|
||||||
import logging.config
|
import logging
|
||||||
import os
|
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import threading
|
import threading
|
||||||
|
import collections
|
||||||
|
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
from coapthon.layers.blocklayer import BlockLayer
|
from coapthon.layers.blocklayer import BlockLayer
|
||||||
|
@ -16,18 +16,13 @@ from coapthon.messages.request import Request
|
||||||
from coapthon.messages.response import Response
|
from coapthon.messages.response import Response
|
||||||
from coapthon.resources.resource import Resource
|
from coapthon.resources.resource import Resource
|
||||||
from coapthon.serializer import Serializer
|
from coapthon.serializer import Serializer
|
||||||
from coapthon.utils import Tree, create_logging
|
from coapthon.utils import Tree
|
||||||
import collections
|
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__author__ = 'Giacomo Tanganelli'
|
||||||
|
|
||||||
|
|
||||||
if not os.path.isfile("logging.conf"):
|
|
||||||
create_logging()
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logging.config.fileConfig("logging.conf", disable_existing_loggers=False)
|
|
||||||
|
|
||||||
|
|
||||||
class CoAP(object):
|
class CoAP(object):
|
||||||
|
@ -88,20 +83,21 @@ class CoAP(object):
|
||||||
mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY)
|
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._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
||||||
else:
|
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)
|
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||||
|
|
||||||
# Allow multiple copies of this program on one machine
|
# Allow multiple copies of this program on one machine
|
||||||
# (not strictly needed)
|
# (not strictly needed)
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
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]
|
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
|
||||||
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
||||||
mreq = group_bin + struct.pack('@I', 0)
|
mreq = group_bin + struct.pack('@I', 0)
|
||||||
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
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:
|
else:
|
||||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
if addrinfo[0] == socket.AF_INET: # IPv4
|
||||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
@ -154,7 +150,7 @@ class CoAP(object):
|
||||||
self.send_datagram(rst)
|
self.send_datagram(rst)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.debug("receive_datagram - " + str(message))
|
logger.info("receive_datagram - " + str(message))
|
||||||
if isinstance(message, Request):
|
if isinstance(message, Request):
|
||||||
transaction = self._messageLayer.receive_request(message)
|
transaction = self._messageLayer.receive_request(message)
|
||||||
if transaction.request.duplicated and transaction.completed:
|
if transaction.request.duplicated and transaction.completed:
|
||||||
|
@ -246,7 +242,7 @@ class CoAP(object):
|
||||||
"""
|
"""
|
||||||
if not self.stopped.isSet():
|
if not self.stopped.isSet():
|
||||||
host, port = message.destination
|
host, port = message.destination
|
||||||
logger.debug("send_datagram - " + str(message))
|
logger.info("send_datagram - " + str(message))
|
||||||
serializer = Serializer()
|
serializer = Serializer()
|
||||||
message = serializer.serialize(message)
|
message = serializer.serialize(message)
|
||||||
self._socket.sendto(message, (host, port))
|
self._socket.sendto(message, (host, port))
|
||||||
|
@ -382,9 +378,10 @@ class CoAP(object):
|
||||||
|
|
||||||
ack = Message()
|
ack = Message()
|
||||||
ack.type = defines.Types['ACK']
|
ack.type = defines.Types['ACK']
|
||||||
# TODO handle mutex on transaction
|
with transaction:
|
||||||
if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]:
|
if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]:
|
||||||
ack = self._messageLayer.send_empty(transaction, transaction.request, ack)
|
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)
|
self.send_datagram(ack)
|
||||||
|
|
||||||
def notify(self, resource):
|
def notify(self, resource):
|
||||||
|
|
|
@ -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)
|
|
|
@ -1,9 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import binascii
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__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):
|
def check_nocachekey(option):
|
||||||
"""
|
"""
|
||||||
checks if an option is a NoCacheKey option or Etag
|
checks if an option is a NoCacheKey option or Etag
|
||||||
|
@ -51,7 +66,7 @@ def is_uri_option(number):
|
||||||
|
|
||||||
|
|
||||||
def generate_random_token(size):
|
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):
|
def parse_blockwise(value):
|
||||||
|
@ -186,3 +201,6 @@ class Tree(object):
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
del self.tree[key]
|
del self.tree[key]
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item in self.tree
|
||||||
|
|
|
@ -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]
|
|
567
coverage_test.py
567
coverage_test.py
|
@ -1,8 +1,11 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from coapclient import HelperClient
|
from coapclient import HelperClient
|
||||||
from coapserver import CoAPServer
|
from coapserver import CoAPServer
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
|
@ -15,6 +18,48 @@ from coapthon.serializer import Serializer
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__author__ = 'Giacomo Tanganelli'
|
||||||
__version__ = "2.0"
|
__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):
|
class Tests(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -23,7 +68,7 @@ class Tests(unittest.TestCase):
|
||||||
self.current_mid = random.randint(1, 1000)
|
self.current_mid = random.randint(1, 1000)
|
||||||
self.server_mid = random.randint(1000, 2000)
|
self.server_mid = random.randint(1000, 2000)
|
||||||
self.server = CoAPServer("127.0.0.1", 5683)
|
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.server_thread.start()
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
|
|
||||||
|
@ -222,88 +267,86 @@ class Tests(unittest.TestCase):
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||||
|
|
||||||
# def test_separate(self):
|
def test_separate(self):
|
||||||
# print "TEST_SEPARATE"
|
print("TEST_SEPARATE")
|
||||||
# path = "/separate"
|
path = "/separate"
|
||||||
# req = Request()
|
|
||||||
# req.code = defines.Codes.GET.number
|
req = Request()
|
||||||
# req.uri_path = path
|
req.code = defines.Codes.GET.number
|
||||||
# req.type = defines.Types["CON"]
|
req.uri_path = path
|
||||||
# req._mid = self.current_mid
|
req.type = defines.Types["CON"]
|
||||||
# req.destination = self.server_address
|
req._mid = self.current_mid
|
||||||
#
|
req.destination = self.server_address
|
||||||
# expected = Response()
|
|
||||||
# expected.type = defines.Types["CON"]
|
expected = Response()
|
||||||
# expected._mid = None
|
expected.type = defines.Types["CON"]
|
||||||
# expected.code = defines.Codes.CONTENT.number
|
expected._mid = None
|
||||||
# expected.token = None
|
expected.code = defines.Codes.CONTENT.number
|
||||||
# expected.max_age = 60
|
expected.token = None
|
||||||
#
|
expected.max_age = 60
|
||||||
# exchange1 = (req, expected)
|
|
||||||
#
|
exchange1 = (req, expected)
|
||||||
# self.current_mid += 1
|
self.current_mid += 1
|
||||||
#
|
|
||||||
# req = Request()
|
req = Request()
|
||||||
# req.code = defines.Codes.POST.number
|
req.code = defines.Codes.POST.number
|
||||||
# req.uri_path = path
|
req.uri_path = path
|
||||||
# req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
# req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
# req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
# req.payload = "POST"
|
req.payload = "POST"
|
||||||
#
|
|
||||||
# expected = Response()
|
expected = Response()
|
||||||
# expected.type = defines.Types["CON"]
|
expected.type = defines.Types["CON"]
|
||||||
# expected._mid = None
|
expected._mid = None
|
||||||
# expected.code = defines.Codes.CHANGED.number
|
expected.code = defines.Codes.CHANGED.number
|
||||||
# expected.token = None
|
expected.token = None
|
||||||
# expected.options = None
|
expected.options = None
|
||||||
#
|
|
||||||
# exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
#
|
self.current_mid += 1
|
||||||
# self.current_mid += 1
|
|
||||||
#
|
req = Request()
|
||||||
# req = Request()
|
req.code = defines.Codes.PUT.number
|
||||||
# req.code = defines.Codes.PUT.number
|
req.uri_path = path
|
||||||
# req.uri_path = path
|
req.type = defines.Types["CON"]
|
||||||
# req.type = defines.Types["CON"]
|
req._mid = self.current_mid
|
||||||
# req._mid = self.current_mid
|
req.destination = self.server_address
|
||||||
# req.destination = self.server_address
|
req.payload = "PUT"
|
||||||
# req.payload = "PUT"
|
|
||||||
#
|
expected = Response()
|
||||||
# expected = Response()
|
expected.type = defines.Types["CON"]
|
||||||
# expected.type = defines.Types["CON"]
|
expected._mid = None
|
||||||
# expected._mid = None
|
expected.code = defines.Codes.CHANGED.number
|
||||||
# expected.code = defines.Codes.CHANGED.number
|
expected.token = None
|
||||||
# expected.token = None
|
expected.options = None
|
||||||
# expected.options = None
|
|
||||||
#
|
exchange3 = (req, expected)
|
||||||
# exchange3 = (req, expected)
|
self.current_mid += 1
|
||||||
#
|
|
||||||
# self.current_mid += 1
|
req = Request()
|
||||||
#
|
req.code = defines.Codes.DELETE.number
|
||||||
# req = Request()
|
req.uri_path = path
|
||||||
# req.code = defines.Codes.DELETE.number
|
req.type = defines.Types["CON"]
|
||||||
# req.uri_path = path
|
req._mid = self.current_mid
|
||||||
# req.type = defines.Types["CON"]
|
req.destination = self.server_address
|
||||||
# req._mid = self.current_mid
|
|
||||||
# req.destination = self.server_address
|
expected = Response()
|
||||||
#
|
expected.type = defines.Types["CON"]
|
||||||
# expected = Response()
|
expected._mid = None
|
||||||
# expected.type = defines.Types["CON"]
|
expected.code = defines.Codes.DELETED.number
|
||||||
# expected._mid = None
|
expected.token = None
|
||||||
# expected.code = defines.Codes.DELETED.number
|
|
||||||
# expected.token = None
|
exchange4 = (req, expected)
|
||||||
#
|
self.current_mid += 1
|
||||||
# exchange4 = (req, expected)
|
|
||||||
#
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||||
# self.current_mid += 1
|
|
||||||
# self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
|
||||||
|
|
||||||
def test_post(self):
|
def test_post(self):
|
||||||
print("TEST_POST")
|
print("TEST_POST")
|
||||||
path = "/storage/new_res?id=1"
|
path = "/storage/new_res?id=1"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.POST.number
|
req.code = defines.Codes.POST.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -407,18 +450,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sollicitudin fermentum ornare. " \
|
req.payload = PAYLOAD
|
||||||
"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.block1 = (1, 1, 1024)
|
req.block1 = (1, 1, 1024)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -437,18 +469,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sollicitudin fermentum ornare. " \
|
req.payload = PAYLOAD
|
||||||
"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.block1 = (0, 1, 1024)
|
req.block1 = (0, 1, 1024)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -468,10 +489,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "a imperdiet nisl. Quisque a iaculis libero, id tempus lacus. Aenean convallis est non justo " \
|
req.payload = PAYLOAD
|
||||||
"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.block1 = (1, 1, 64)
|
req.block1 = (1, 1, 64)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -491,10 +509,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "a imperdiet nisl. Quisque a iaculis libero, id tempus lacus. Aenean convallis est non justo " \
|
req.payload = PAYLOAD
|
||||||
"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.block1 = (3, 1, 64)
|
req.block1 = (3, 1, 64)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -513,10 +528,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "a imperdiet nisl. Quisque a iaculis libero, id tempus lacus. Aenean convallis est non justo " \
|
req.payload = PAYLOAD
|
||||||
"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.block1 = (2, 0, 64)
|
req.block1 = (2, 0, 64)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -536,6 +548,27 @@ class Tests(unittest.TestCase):
|
||||||
print("TEST_GET_BLOCK")
|
print("TEST_GET_BLOCK")
|
||||||
path = "/big"
|
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 = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -552,6 +585,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (0, 1, 512)
|
expected.block2 = (0, 1, 512)
|
||||||
|
expected.size2 = 2041
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -572,6 +606,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (1, 1, 256)
|
expected.block2 = (1, 1, 256)
|
||||||
|
expected.size2 = 2041
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -592,6 +627,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (2, 1, 128)
|
expected.block2 = (2, 1, 128)
|
||||||
|
expected.size2 = 2041
|
||||||
|
|
||||||
exchange3 = (req, expected)
|
exchange3 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -612,6 +648,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (3, 1, 64)
|
expected.block2 = (3, 1, 64)
|
||||||
|
expected.size2 = 2041
|
||||||
|
|
||||||
exchange4 = (req, expected)
|
exchange4 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -632,6 +669,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (4, 1, 32)
|
expected.block2 = (4, 1, 32)
|
||||||
|
expected.size2 = 2041
|
||||||
|
|
||||||
exchange5 = (req, expected)
|
exchange5 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -652,6 +690,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (5, 1, 16)
|
expected.block2 = (5, 1, 16)
|
||||||
|
expected.size2 = 2041
|
||||||
|
|
||||||
exchange6 = (req, expected)
|
exchange6 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -671,7 +710,8 @@ class Tests(unittest.TestCase):
|
||||||
expected.code = defines.Codes.CONTENT.number
|
expected.code = defines.Codes.CONTENT.number
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (6, 1, 1024)
|
expected.block2 = (6, 0, 1024)
|
||||||
|
expected.size2 = 2041
|
||||||
|
|
||||||
exchange7 = (req, expected)
|
exchange7 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -692,11 +732,13 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (7, 0, 1024)
|
expected.block2 = (7, 0, 1024)
|
||||||
|
expected.size2 = 2041
|
||||||
|
|
||||||
exchange8 = (req, expected)
|
exchange8 = (req, expected)
|
||||||
self.current_mid += 1
|
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):
|
def test_post_block_big(self):
|
||||||
print("TEST_POST_BLOCK_BIG")
|
print("TEST_POST_BLOCK_BIG")
|
||||||
|
@ -708,7 +750,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "Lorem ipsum dolo"
|
req.payload = PAYLOAD
|
||||||
req.block1 = (0, 1, 16)
|
req.block1 = (0, 1, 16)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -728,7 +770,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "r sit amet, consectetur adipisci"
|
req.payload = PAYLOAD
|
||||||
req.block1 = (1, 1, 32)
|
req.block1 = (1, 1, 32)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -748,7 +790,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
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)
|
req.block1 = (2, 1, 64)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -768,8 +810,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "s ultricies est in, vehicula congue metus. Vestibulum vel justo lacinia, porttitor quam vitae, " \
|
req.payload = PAYLOAD
|
||||||
"feugiat sapien. Quisque finibus, "
|
|
||||||
req.block1 = (3, 1, 128)
|
req.block1 = (3, 1, 128)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -789,9 +830,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "nisi vitae rhoncus malesuada, augue mauris dapibus tellus, sit amet venenatis libero" \
|
req.payload = PAYLOAD
|
||||||
" 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)
|
req.block1 = (4, 1, 256)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -811,12 +850,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "rerit ex, quis aliquet ante. Vivamus ultrices dolor at elit tincidunt, eget fringilla " \
|
req.payload = PAYLOAD
|
||||||
"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)
|
req.block1 = (5, 1, 512)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -836,18 +870,7 @@ class Tests(unittest.TestCase):
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
req.payload = "itor ac sit amet eros. Mauris et nisi in tortor pharetra rhoncus sit amet hendrerit metus. " \
|
req.payload = PAYLOAD
|
||||||
"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)
|
req.block1 = (6, 0, 1024)
|
||||||
|
|
||||||
expected = Response()
|
expected = Response()
|
||||||
|
@ -856,7 +879,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.code = defines.Codes.CHANGED.number
|
expected.code = defines.Codes.CHANGED.number
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.location_path = "big"
|
|
||||||
|
|
||||||
exchange7 = (req, expected)
|
exchange7 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -941,75 +963,74 @@ class Tests(unittest.TestCase):
|
||||||
|
|
||||||
self._test_with_client([exchange1, exchange2, exchange3])
|
self._test_with_client([exchange1, exchange2, exchange3])
|
||||||
|
|
||||||
# def test_long_options(self):
|
def test_long_options(self):
|
||||||
# """
|
"""
|
||||||
# Test processing of options with extended length
|
Test processing of options with extended length
|
||||||
# """
|
"""
|
||||||
# print("TEST_LONG_OPTIONS")
|
print("TEST_LONG_OPTIONS")
|
||||||
#
|
path = "/storage/"
|
||||||
# path = "/storage/"
|
|
||||||
# req = Request()
|
req = Request()
|
||||||
# req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
# req.uri_path = path
|
req.uri_path = path
|
||||||
# req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
# req._mid = self.current_mid
|
req._mid = self.current_mid
|
||||||
# req.destination = self.server_address
|
req.destination = self.server_address
|
||||||
# option = Option()
|
option = Option()
|
||||||
# # This option should be silently ignored by the server
|
# This option should be silently ignored by the server
|
||||||
# # since it is not critical
|
# since it is not critical
|
||||||
# option.number = defines.OptionRegistry.RM_MESSAGE_SWITCHING.number
|
option.number = defines.OptionRegistry.RM_MESSAGE_SWITCHING.number
|
||||||
# option.value = "\1\1\1\1\0\0"
|
option.value = b'\1\1\1\1\0\0'
|
||||||
# options = req.options
|
req.add_option(option)
|
||||||
# req.add_option(option)
|
req.payload = "test"
|
||||||
# req.payload = "test"
|
|
||||||
#
|
expected = Response()
|
||||||
# expected = Response()
|
expected.type = defines.Types["ACK"]
|
||||||
# expected.type = defines.Types["ACK"]
|
expected.code = defines.Codes.CONTENT.number
|
||||||
# expected.code = defines.Codes.CONTENT.number
|
expected.token = None
|
||||||
# expected.token = None
|
expected.payload = None
|
||||||
# expected.payload = None
|
|
||||||
#
|
exchange1 = (req, expected)
|
||||||
# exchange1 = (req, expected)
|
self.current_mid += 1
|
||||||
# self.current_mid += 1
|
|
||||||
#
|
self._test_with_client([exchange1])
|
||||||
# self._test_with_client([exchange1])
|
|
||||||
#
|
# This option (244) should be silently ignored by the server
|
||||||
# # 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)
|
||||||
# req = ("\x40\x01\x01\x01\xd6\xe7\x01\x01\x01\x01\x00\x00", self.server_address)
|
|
||||||
#
|
expected = Response()
|
||||||
# expected = Response()
|
expected.type = defines.Types["ACK"]
|
||||||
# expected.type = defines.Types["ACK"]
|
expected._mid = None
|
||||||
# expected._mid = None
|
expected.code = defines.Codes.NOT_FOUND.number
|
||||||
# expected.code = defines.Codes.NOT_FOUND.number
|
expected.token = None
|
||||||
# expected.token = None
|
expected.payload = None
|
||||||
# expected.payload = None
|
|
||||||
#
|
exchange21 = (req, expected)
|
||||||
# exchange21 = (req, expected)
|
self.current_mid += 1
|
||||||
# self.current_mid += 1
|
|
||||||
#
|
# This option (245) should cause BAD REQUEST, as unrecognizable critical
|
||||||
# # 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)
|
||||||
# req = ("\x40\x01\x01\x01\xd6\xe8\x01\x01\x01\x01\x00\x00", self.server_address)
|
|
||||||
#
|
expected = Response()
|
||||||
# expected = Response()
|
expected.type = defines.Types["RST"]
|
||||||
# expected.type = defines.Types["RST"]
|
expected._mid = None
|
||||||
# expected._mid = None
|
expected.code = defines.Codes.BAD_REQUEST.number
|
||||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
|
||||||
#
|
exchange22 = (req, expected)
|
||||||
# exchange22 = (req, expected)
|
self.current_mid += 1
|
||||||
# self.current_mid += 1
|
|
||||||
#
|
# This option (65525) should cause BAD REQUEST, as unrecognizable critical
|
||||||
# # 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)
|
||||||
# req = ("\x40\x01\x01\x01\xe6\xfe\xe8\x01\x01\x01\x01\x00\x00", self.server_address)
|
|
||||||
#
|
expected = Response()
|
||||||
# expected = Response()
|
expected.type = defines.Types["RST"]
|
||||||
# expected.type = defines.Types["RST"]
|
expected._mid = None
|
||||||
# expected._mid = None
|
expected.code = defines.Codes.BAD_REQUEST.number
|
||||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
|
||||||
#
|
exchange23 = (req, expected)
|
||||||
# exchange23 = (req, expected)
|
self.current_mid += 1
|
||||||
# self.current_mid += 1
|
|
||||||
#
|
self._test_datagram([exchange21, exchange22, exchange23])
|
||||||
# self._test_datagram([exchange21, exchange22, exchange23])
|
|
||||||
|
|
||||||
def test_content_type(self):
|
def test_content_type(self):
|
||||||
print("TEST_CONTENT_TYPE")
|
print("TEST_CONTENT_TYPE")
|
||||||
|
@ -1138,7 +1159,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = "<value>0</value>"
|
expected.payload = "<value>0</value>"
|
||||||
|
|
||||||
print((expected.pretty_print()))
|
print(expected.pretty_print())
|
||||||
|
|
||||||
exchange7 = (req, expected)
|
exchange7 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -1198,9 +1219,8 @@ class Tests(unittest.TestCase):
|
||||||
exchange10 = (req, expected)
|
exchange10 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
# self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7,
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5,
|
||||||
# exchange8, exchange9, exchange10])
|
exchange6, exchange7, exchange8, exchange9, exchange10])
|
||||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5])
|
|
||||||
|
|
||||||
def test_ETAG(self):
|
def test_ETAG(self):
|
||||||
print("TEST_ETAG")
|
print("TEST_ETAG")
|
||||||
|
@ -1238,7 +1258,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.code = defines.Codes.CHANGED.number
|
expected.code = defines.Codes.CHANGED.number
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.location_path = path
|
|
||||||
expected.etag = "1"
|
expected.etag = "1"
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
|
@ -1301,7 +1320,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.code = defines.Codes.CREATED.number
|
expected.code = defines.Codes.CREATED.number
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.location_path = path
|
expected.location_path = "child"
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -1376,7 +1395,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.type = defines.Types["ACK"]
|
expected.type = defines.Types["ACK"]
|
||||||
expected._mid = self.current_mid
|
expected._mid = self.current_mid
|
||||||
expected.code = defines.Codes.NOT_FOUND.number
|
expected.code = defines.Codes.NOT_FOUND.number
|
||||||
expected.token = "100"
|
expected.token = 100
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
|
@ -1436,60 +1455,60 @@ class Tests(unittest.TestCase):
|
||||||
|
|
||||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||||
|
|
||||||
# def test_invalid(self):
|
def test_invalid(self):
|
||||||
# print("TEST_INVALID")
|
print("TEST_INVALID")
|
||||||
#
|
|
||||||
# # version
|
# version
|
||||||
# req = ("\x00\x01\x8c\xda", self.server_address)
|
req = (b'\x00\x01\x8c\xda', self.server_address)
|
||||||
#
|
|
||||||
# expected = Response()
|
expected = Response()
|
||||||
# expected.type = defines.Types["RST"]
|
expected.type = defines.Types["RST"]
|
||||||
# expected._mid = None
|
expected._mid = None
|
||||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
expected.code = defines.Codes.BAD_REQUEST.number
|
||||||
#
|
|
||||||
# exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
#
|
|
||||||
# # version
|
# version
|
||||||
# req = ("\x40", self.server_address)
|
req = (b'\x40', self.server_address)
|
||||||
#
|
|
||||||
# expected = Response()
|
expected = Response()
|
||||||
# expected.type = defines.Types["RST"]
|
expected.type = defines.Types["RST"]
|
||||||
# expected._mid = None
|
expected._mid = None
|
||||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
expected.code = defines.Codes.BAD_REQUEST.number
|
||||||
#
|
|
||||||
# exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
#
|
|
||||||
# # code
|
# code
|
||||||
# req = ("\x40\x05\x8c\xda", self.server_address)
|
req = (b'\x40\x05\x8c\xda', self.server_address)
|
||||||
#
|
|
||||||
# expected = Response()
|
expected = Response()
|
||||||
# expected.type = defines.Types["RST"]
|
expected.type = defines.Types["RST"]
|
||||||
# expected._mid = None
|
expected._mid = None
|
||||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
expected.code = defines.Codes.BAD_REQUEST.number
|
||||||
#
|
|
||||||
# exchange3 = (req, expected)
|
exchange3 = (req, expected)
|
||||||
#
|
|
||||||
# # option
|
# option
|
||||||
# req = ("\x40\x01\x8c\xda\x94", self.server_address)
|
req = (b'\x40\x01\x8c\xda\x94', self.server_address)
|
||||||
#
|
|
||||||
# expected = Response()
|
expected = Response()
|
||||||
# expected.type = defines.Types["RST"]
|
expected.type = defines.Types["RST"]
|
||||||
# expected._mid = None
|
expected._mid = None
|
||||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
expected.code = defines.Codes.BAD_REQUEST.number
|
||||||
#
|
|
||||||
# exchange4 = (req, expected)
|
exchange4 = (req, expected)
|
||||||
#
|
|
||||||
# # payload marker
|
# payload marker
|
||||||
# req = ("\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff", self.server_address)
|
req = (b'\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff', self.server_address)
|
||||||
#
|
|
||||||
# expected = Response()
|
expected = Response()
|
||||||
# expected.type = defines.Types["RST"]
|
expected.type = defines.Types["RST"]
|
||||||
# expected._mid = None
|
expected._mid = None
|
||||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
expected.code = defines.Codes.BAD_REQUEST.number
|
||||||
#
|
|
||||||
# exchange5 = (req, expected)
|
exchange5 = (req, expected)
|
||||||
#
|
|
||||||
# self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5])
|
self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5])
|
||||||
|
|
||||||
def test_post_block_big_client(self):
|
def test_post_block_big_client(self):
|
||||||
print("TEST_POST_BLOCK_BIG_CLIENT")
|
print("TEST_POST_BLOCK_BIG_CLIENT")
|
||||||
|
@ -1571,6 +1590,6 @@ class Tests(unittest.TestCase):
|
||||||
|
|
||||||
self._test_with_client_observe([exchange1, exchange2])
|
self._test_with_client_observe([exchange1, exchange2])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from coapclient import HelperClient
|
from coapclient import HelperClient
|
||||||
from coapserver import CoAPServer
|
from coapserver import CoAPServer
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
|
@ -20,7 +23,7 @@ class Tests(unittest.TestCase):
|
||||||
self.current_mid = random.randint(1, 1000)
|
self.current_mid = random.randint(1, 1000)
|
||||||
self.server_mid = random.randint(1000, 2000)
|
self.server_mid = random.randint(1000, 2000)
|
||||||
self.server = CoAPServer("::1", 5683)
|
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.server_thread.start()
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
|
|
||||||
|
@ -89,6 +92,7 @@ class Tests(unittest.TestCase):
|
||||||
def test_not_allowed(self):
|
def test_not_allowed(self):
|
||||||
print("TEST_NOT_ALLOWED")
|
print("TEST_NOT_ALLOWED")
|
||||||
path = "/void"
|
path = "/void"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -103,7 +107,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -120,7 +123,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -137,7 +139,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange3 = (req, expected)
|
exchange3 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -154,11 +155,10 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange4 = (req, expected)
|
exchange4 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
@ -18,7 +20,7 @@ class Tests(unittest.TestCase):
|
||||||
self.current_mid = random.randint(1, 1000)
|
self.current_mid = random.randint(1, 1000)
|
||||||
self.server_mid = random.randint(1000, 2000)
|
self.server_mid = random.randint(1000, 2000)
|
||||||
self.server = CoAPServer("127.0.0.1", 5683)
|
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.server_thread.start()
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
|
|
||||||
|
@ -61,6 +63,7 @@ class Tests(unittest.TestCase):
|
||||||
def test_advanced(self):
|
def test_advanced(self):
|
||||||
print("TEST_ADVANCED")
|
print("TEST_ADVANCED")
|
||||||
path = "/advanced"
|
path = "/advanced"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -76,7 +79,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -94,7 +96,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -112,7 +113,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange3 = (req, expected)
|
exchange3 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -130,13 +130,14 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange4 = (req, expected)
|
exchange4 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||||
|
|
||||||
def test_advanced_separate(self):
|
def test_advanced_separate(self):
|
||||||
print("TEST_ADVANCED_SEPARATE")
|
print("TEST_ADVANCED_SEPARATE")
|
||||||
path = "/advancedSeparate"
|
path = "/advancedSeparate"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -152,7 +153,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -170,7 +170,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -188,7 +187,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange3 = (req, expected)
|
exchange3 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -206,9 +204,10 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange4 = (req, expected)
|
exchange4 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from coapclient import HelperClient
|
from coapclient import HelperClient
|
||||||
from coapserver import CoAPServer
|
from coapserver import CoAPServer
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
from coapthon.messages.message import Message
|
|
||||||
from coapthon.messages.option import Option
|
from coapthon.messages.option import Option
|
||||||
from coapthon.messages.request import Request
|
from coapthon.messages.request import Request
|
||||||
from coapthon.messages.response import Response
|
from coapthon.messages.response import Response
|
||||||
|
@ -21,7 +23,7 @@ class Tests(unittest.TestCase):
|
||||||
self.current_mid = random.randint(1, 1000)
|
self.current_mid = random.randint(1, 1000)
|
||||||
self.server_mid = random.randint(1000, 2000)
|
self.server_mid = random.randint(1000, 2000)
|
||||||
self.server = CoAPServer("0.0.0.0", 5683, multicast=True)
|
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.server_thread.start()
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
|
|
||||||
|
@ -90,6 +92,7 @@ class Tests(unittest.TestCase):
|
||||||
def test_not_allowed(self):
|
def test_not_allowed(self):
|
||||||
print("TEST_NOT_ALLOWED")
|
print("TEST_NOT_ALLOWED")
|
||||||
path = "/void"
|
path = "/void"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -104,7 +107,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -121,7 +123,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -138,7 +139,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange3 = (req, expected)
|
exchange3 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -155,11 +155,10 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange4 = (req, expected)
|
exchange4 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from coapclient import HelperClient
|
from coapclient import HelperClient
|
||||||
from coapserver import CoAPServer
|
from coapserver import CoAPServer
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
from coapthon.messages.message import Message
|
|
||||||
from coapthon.messages.option import Option
|
from coapthon.messages.option import Option
|
||||||
from coapthon.messages.request import Request
|
from coapthon.messages.request import Request
|
||||||
from coapthon.messages.response import Response
|
from coapthon.messages.response import Response
|
||||||
|
@ -21,7 +23,7 @@ class Tests(unittest.TestCase):
|
||||||
self.current_mid = random.randint(1, 1000)
|
self.current_mid = random.randint(1, 1000)
|
||||||
self.server_mid = random.randint(1000, 2000)
|
self.server_mid = random.randint(1000, 2000)
|
||||||
self.server = CoAPServer("::1", 5683, multicast=True)
|
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.server_thread.start()
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
|
|
||||||
|
@ -90,6 +92,7 @@ class Tests(unittest.TestCase):
|
||||||
def test_not_allowed(self):
|
def test_not_allowed(self):
|
||||||
print("TEST_NOT_ALLOWED")
|
print("TEST_NOT_ALLOWED")
|
||||||
path = "/void"
|
path = "/void"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -104,7 +107,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -121,7 +123,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -138,7 +139,6 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange3 = (req, expected)
|
exchange3 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
@ -155,11 +155,10 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
|
|
||||||
exchange4 = (req, expected)
|
exchange4 = (req, expected)
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,5 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
|
|
||||||
|
|
56
plugtest.py
56
plugtest.py
|
@ -1,9 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from coapthon.messages.message import Message
|
from coapthon.messages.message import Message
|
||||||
from coapclient import HelperClient
|
from coapclient import HelperClient
|
||||||
from coapthon.messages.response import Response
|
from coapthon.messages.response import Response
|
||||||
|
@ -23,7 +25,7 @@ class Tests(unittest.TestCase):
|
||||||
self.current_mid = random.randint(1, 1000)
|
self.current_mid = random.randint(1, 1000)
|
||||||
self.server_mid = random.randint(1000, 2000)
|
self.server_mid = random.randint(1000, 2000)
|
||||||
self.server = CoAPServerPlugTest("127.0.0.1", 5683, starting_mid=self.server_mid)
|
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.server_thread.start()
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
|
|
||||||
|
@ -127,6 +129,7 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_link_01(self):
|
def test_td_coap_link_01(self):
|
||||||
print("TD_COAP_LINK_01")
|
print("TD_COAP_LINK_01")
|
||||||
path = "/.well-known/core"
|
path = "/.well-known/core"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -147,6 +150,7 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_link_02(self):
|
def test_td_coap_link_02(self):
|
||||||
print("TD_COAP_LINK_02")
|
print("TD_COAP_LINK_02")
|
||||||
path = "/.well-known/core"
|
path = "/.well-known/core"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -168,6 +172,7 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_01(self):
|
def test_td_coap_core_01(self):
|
||||||
print("TD_COAP_CORE_01")
|
print("TD_COAP_CORE_01")
|
||||||
path = "/test"
|
path = "/test"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
|
@ -188,8 +193,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_02(self):
|
def test_td_coap_core_02(self):
|
||||||
print("TD_COAP_CORE_02")
|
print("TD_COAP_CORE_02")
|
||||||
path = "/test_post"
|
path = "/test_post"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.POST.number
|
req.code = defines.Codes.POST.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -204,7 +209,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.code = defines.Codes.CREATED.number
|
expected.code = defines.Codes.CREATED.number
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.location_path = "/test_post"
|
expected.location_path = "test_post"
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
self._test_with_client([(req, expected)])
|
self._test_with_client([(req, expected)])
|
||||||
|
@ -212,8 +217,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_03(self):
|
def test_td_coap_core_03(self):
|
||||||
print("TD_COAP_CORE_03")
|
print("TD_COAP_CORE_03")
|
||||||
path = "/test"
|
path = "/test"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.PUT.number
|
req.code = defines.Codes.PUT.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -233,7 +238,6 @@ class Tests(unittest.TestCase):
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -251,7 +255,6 @@ class Tests(unittest.TestCase):
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
|
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -274,8 +277,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_04(self):
|
def test_td_coap_core_04(self):
|
||||||
print("TD_COAP_CORE_04")
|
print("TD_COAP_CORE_04")
|
||||||
path = "/test"
|
path = "/test"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.DELETE.number
|
req.code = defines.Codes.DELETE.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -295,8 +298,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_05(self):
|
def test_td_coap_core_05(self):
|
||||||
print("TD_COAP_CORE_05")
|
print("TD_COAP_CORE_05")
|
||||||
path = "/test"
|
path = "/test"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["NON"]
|
req.type = defines.Types["NON"]
|
||||||
|
@ -316,8 +319,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_06(self):
|
def test_td_coap_core_06(self):
|
||||||
print("TD_COAP_CORE_06")
|
print("TD_COAP_CORE_06")
|
||||||
path = "/test_post"
|
path = "/test_post"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.POST.number
|
req.code = defines.Codes.POST.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["NON"]
|
req.type = defines.Types["NON"]
|
||||||
|
@ -332,7 +335,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.code = defines.Codes.CREATED.number
|
expected.code = defines.Codes.CREATED.number
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.location_path = "/test_post"
|
expected.location_path = "test_post"
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
self._test_with_client([(req, expected)])
|
self._test_with_client([(req, expected)])
|
||||||
|
@ -340,8 +343,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_07(self):
|
def test_td_coap_core_07(self):
|
||||||
print("TD_COAP_CORE_07")
|
print("TD_COAP_CORE_07")
|
||||||
path = "/test"
|
path = "/test"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.PUT.number
|
req.code = defines.Codes.PUT.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["NON"]
|
req.type = defines.Types["NON"]
|
||||||
|
@ -363,8 +366,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_08(self):
|
def test_td_coap_core_08(self):
|
||||||
print("TD_COAP_CORE_08")
|
print("TD_COAP_CORE_08")
|
||||||
path = "/test"
|
path = "/test"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.DELETE.number
|
req.code = defines.Codes.DELETE.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["NON"]
|
req.type = defines.Types["NON"]
|
||||||
|
@ -384,8 +387,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_09(self):
|
def test_td_coap_core_09(self):
|
||||||
print("TD_COAP_CORE_09")
|
print("TD_COAP_CORE_09")
|
||||||
path = "/separate"
|
path = "/separate"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -412,8 +415,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_10(self):
|
def test_td_coap_core_10(self):
|
||||||
print("TD_COAP_CORE_10")
|
print("TD_COAP_CORE_10")
|
||||||
path = "/test"
|
path = "/test"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -435,8 +438,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_12(self):
|
def test_td_coap_core_12(self):
|
||||||
print("TD_COAP_CORE_12")
|
print("TD_COAP_CORE_12")
|
||||||
path = "/seg1/seg2/seg3"
|
path = "/seg1/seg2/seg3"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -455,8 +458,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_core_13(self):
|
def test_td_coap_core_13(self):
|
||||||
print("TD_COAP_CORE_13")
|
print("TD_COAP_CORE_13")
|
||||||
path = "/query?first=1&second=2&third=3"
|
path = "/query?first=1&second=2&third=3"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -476,8 +479,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_obs_01(self):
|
def test_td_coap_obs_01(self):
|
||||||
print("TD_COAP_OBS_01")
|
print("TD_COAP_OBS_01")
|
||||||
path = "/obs"
|
path = "/obs"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -508,8 +511,8 @@ class Tests(unittest.TestCase):
|
||||||
def test_td_coap_obs_03(self):
|
def test_td_coap_obs_03(self):
|
||||||
print("TD_COAP_OBS_03")
|
print("TD_COAP_OBS_03")
|
||||||
path = "/obs"
|
path = "/obs"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
req.type = defines.Types["CON"]
|
||||||
|
@ -566,6 +569,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (0, 1, 1024)
|
expected.block2 = (0, 1, 1024)
|
||||||
|
expected.size2 = 1990
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
@ -586,6 +590,7 @@ class Tests(unittest.TestCase):
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (1, 0, 1024)
|
expected.block2 = (1, 0, 1024)
|
||||||
|
expected.size2 = 1990
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
self.current_mid += 1
|
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
|
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"""
|
the sense of weight and pressure that I could tell that Queequeg was hugging"""
|
||||||
expected.block2 = (1, 0, 1024)
|
expected.block2 = (1, 0, 1024)
|
||||||
|
expected.size2 = 1990
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
self.current_mid += 1
|
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
|
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"""
|
the sense of weight and pressure that I could tell that Queequeg was hugging"""
|
||||||
expected.block2 = (1, 0, 1024)
|
expected.block2 = (1, 0, 1024)
|
||||||
|
expected.size2 = 1990
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
self.current_mid += 1
|
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.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (0, 1, 1024)
|
expected.block2 = (0, 1, 1024)
|
||||||
|
expected.size2 = 1990
|
||||||
|
|
||||||
exchange1 = (req, expected)
|
exchange1 = (req, expected)
|
||||||
self.current_mid += 1
|
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.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.block2 = (1, 0, 1024)
|
expected.block2 = (1, 0, 1024)
|
||||||
|
expected.size2 = 1990
|
||||||
|
|
||||||
exchange2 = (req, expected)
|
exchange2 = (req, expected)
|
||||||
self.current_mid += 1
|
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):
|
def test_duplicate(self):
|
||||||
print("TEST_DUPLICATE")
|
print("TEST_DUPLICATE")
|
||||||
path = "/test"
|
path = "/test"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
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
|
expected.token = None
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_plugtest([(req, expected), (req, expected)])
|
self._test_plugtest([(req, expected), (req, expected)])
|
||||||
|
|
||||||
def test_duplicate_not_completed(self):
|
def test_duplicate_not_completed(self):
|
||||||
print("TEST_DUPLICATE_NOT_COMPLETED")
|
print("TEST_DUPLICATE_NOT_COMPLETED")
|
||||||
path = "/long"
|
path = "/long"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
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
|
expected2.token = None
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_plugtest([(req, None), (req, expected), (None, expected2)])
|
self._test_plugtest([(req, None), (req, expected), (None, expected2)])
|
||||||
|
|
||||||
def test_no_response(self):
|
def test_no_response(self):
|
||||||
print("TEST_NO_RESPONSE")
|
print("TEST_NO_RESPONSE")
|
||||||
path = "/long"
|
path = "/long"
|
||||||
|
|
||||||
req = Request()
|
req = Request()
|
||||||
req.code = defines.Codes.GET.number
|
req.code = defines.Codes.GET.number
|
||||||
req.uri_path = path
|
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
|
expected2.token = None
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_plugtest([(req, expected), (None, expected2), (None, expected2), (None, expected2)])
|
self._test_plugtest([(req, expected), (None, expected2), (None, expected2), (None, expected2)])
|
||||||
|
|
||||||
def test_edit_resource(self):
|
def test_edit_resource(self):
|
||||||
print("TEST_EDIT_RESOURCE")
|
print("TEST_EDIT_RESOURCE")
|
||||||
path = "/obs"
|
path = "/obs"
|
||||||
req = Request()
|
|
||||||
|
|
||||||
|
req = Request()
|
||||||
req.code = defines.Codes.POST.number
|
req.code = defines.Codes.POST.number
|
||||||
req.uri_path = path
|
req.uri_path = path
|
||||||
req.type = defines.Types["CON"]
|
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.code = defines.Codes.CHANGED.number
|
||||||
expected.token = None
|
expected.token = None
|
||||||
expected.payload = None
|
expected.payload = None
|
||||||
expected.location_path = "/obs"
|
|
||||||
|
|
||||||
self.current_mid += 1
|
self.current_mid += 1
|
||||||
|
|
||||||
self._test_with_client([(req, expected)])
|
self._test_with_client([(req, expected)])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import getopt
|
import getopt
|
||||||
import logging
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from coapthon.server.coap import CoAP
|
||||||
from plugtest_resources import TestResource, SeparateResource, ObservableResource, LargeResource, LargeUpdateResource, \
|
from plugtest_resources import TestResource, SeparateResource, ObservableResource, LargeResource, LargeUpdateResource, \
|
||||||
LongResource
|
LongResource
|
||||||
from coapthon.server.coap import CoAP
|
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
__author__ = 'Giacomo Tanganelli'
|
||||||
|
|
||||||
|
@ -11,33 +11,15 @@ __author__ = 'Giacomo Tanganelli'
|
||||||
class CoAPServerPlugTest(CoAP):
|
class CoAPServerPlugTest(CoAP):
|
||||||
def __init__(self, host, port, multicast=False, starting_mid=None):
|
def __init__(self, host, port, multicast=False, starting_mid=None):
|
||||||
CoAP.__init__(self, (host, port), multicast, starting_mid)
|
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('test/', TestResource())
|
||||||
self.add_resource('separate/', SeparateResource())
|
self.add_resource('separate/', SeparateResource())
|
||||||
self.add_resource('seg1/', TestResource())
|
self.add_resource('seg1/', TestResource())
|
||||||
self.add_resource('seg1/seg2/', TestResource())
|
self.add_resource('seg1/seg2/', TestResource())
|
||||||
self.add_resource('seg1/seg2/seg3/', TestResource())
|
self.add_resource('seg1/seg2/seg3/', TestResource())
|
||||||
self.add_resource('query/', TestResource())
|
self.add_resource('query/', TestResource())
|
||||||
self.add_resource("obs/", ObservableResource(coap_server=self))
|
self.add_resource('obs/', ObservableResource(coap_server=self))
|
||||||
self.add_resource("large/", LargeResource(coap_server=self))
|
self.add_resource('large/', LargeResource(coap_server=self))
|
||||||
self.add_resource("large-update/", LargeUpdateResource(coap_server=self))
|
self.add_resource('large-update/', LargeUpdateResource(coap_server=self))
|
||||||
self.add_resource('long/', LongResource())
|
self.add_resource('long/', LongResource())
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,9 +48,7 @@ def main(argv): # pragma: no cover
|
||||||
try:
|
try:
|
||||||
server.listen(10)
|
server.listen(10)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Server Shutdown")
|
|
||||||
server.close()
|
server.close()
|
||||||
print("Exiting...")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# coding=utf-8
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from coapthon import defines
|
from coapthon import defines
|
||||||
from coapthon.resources.resource import Resource
|
from coapthon.resources.resource import Resource
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
Sphinx==1.2.2
|
Sphinx>=1.2.2
|
||||||
cachetools==2.0.0
|
cachetools>=2.0.0
|
||||||
|
|
||||||
|
|
29
setup.py
29
setup.py
|
@ -1,16 +1,33 @@
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
import datetime
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='CoAPthon3',
|
name='CoAPthon3',
|
||||||
version='1.0.1',
|
version='1.0.1+fb.' + datetime.datetime.now().strftime("%Y%m%d%H%M"),
|
||||||
packages=['coapthon', 'coapthon.caching', 'coapthon.layers', 'coapthon.client', 'coapthon.server', 'coapthon.messages',
|
packages=[
|
||||||
'coapthon.forward_proxy', 'coapthon.resources', 'coapthon.reverse_proxy'],
|
'coapthon',
|
||||||
url='https://github.com/Tanganelli/CoAPthon3',
|
'coapthon.caching',
|
||||||
|
'coapthon.client',
|
||||||
|
'coapthon.forward_proxy',
|
||||||
|
'coapthon.layers',
|
||||||
|
'coapthon.messages',
|
||||||
|
'coapthon.resources',
|
||||||
|
'coapthon.reverse_proxy',
|
||||||
|
'coapthon.server',
|
||||||
|
],
|
||||||
license='MIT License',
|
license='MIT License',
|
||||||
author='Giacomo Tanganelli',
|
author='Giacomo Tanganelli',
|
||||||
author_email='giacomo.tanganelli@for.unipi.it',
|
author_email='giacomo.tanganelli@for.unipi.it',
|
||||||
download_url='https://github.com/Tanganelli/CoAPthon3/archive/1.0.1.tar.gz',
|
maintainer="Bjoern Freise",
|
||||||
|
maintainer_email="mcfreis@gmx.net",
|
||||||
|
url="https://github.com/mcfreis/CoAPthon3",
|
||||||
description='CoAPthon is a python library to the CoAP protocol.',
|
description='CoAPthon is a python library to the CoAP protocol.',
|
||||||
scripts=['coapserver.py', 'coapclient.py', 'exampleresources.py', 'coapforwardproxy.py', 'coapreverseproxy.py'],
|
scripts=[
|
||||||
|
'coapclient.py',
|
||||||
|
'coapforwardproxy.py',
|
||||||
|
'coapreverseproxy.py',
|
||||||
|
'coapserver.py',
|
||||||
|
'exampleresources.py',
|
||||||
|
],
|
||||||
requires=['sphinx', 'cachetools']
|
requires=['sphinx', 'cachetools']
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue