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/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
.idea/
|
||||
|
||||
# PyInstaller
|
||||
# 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
|
||||
import random
|
||||
import socket
|
||||
import threading
|
||||
import unittest
|
||||
import time
|
||||
|
||||
from coapclient import HelperClient
|
||||
from coapforwardproxy import CoAPForwardProxy
|
||||
from coapserver import CoAPServer
|
||||
|
@ -10,8 +11,6 @@ from coapthon import defines
|
|||
from coapthon.messages.option import Option
|
||||
from coapthon.messages.request import Request
|
||||
from coapthon.messages.response import Response
|
||||
from coapthon.serializer import Serializer
|
||||
import time
|
||||
|
||||
__author__ = 'Emilio Vallati'
|
||||
__version__ = "1.0"
|
||||
|
@ -24,10 +23,10 @@ class Tests(unittest.TestCase):
|
|||
self.current_mid = random.randint(1, 1000)
|
||||
self.server_mid = random.randint(1000, 2000)
|
||||
self.server = CoAPServer("127.0.0.1", 5684)
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(10,))
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(1,))
|
||||
self.server_thread.start()
|
||||
self.proxy = CoAPForwardProxy("127.0.0.1", 5683, cache=True)
|
||||
self.proxy_thread = threading.Thread(target=self.proxy.listen, args=(10,))
|
||||
self.proxy_thread = threading.Thread(target=self.proxy.listen, args=(1,))
|
||||
self.proxy_thread.start()
|
||||
self.queue = Queue()
|
||||
|
||||
|
@ -459,7 +458,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.etag = str(1)
|
||||
expected.location_path = "etag"
|
||||
|
||||
exchange3 = (req3, expected)
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ def main(): # pragma: no cover
|
|||
"payload_file="])
|
||||
except getopt.GetoptError as err:
|
||||
# print help information and exit:
|
||||
print((str(err))) # will print something like "option -a not recognized"
|
||||
print(str(err)) # will print something like "option -a not recognized"
|
||||
usage()
|
||||
sys.exit(2)
|
||||
for o, a in opts:
|
||||
|
@ -107,7 +107,7 @@ def main(): # pragma: no cover
|
|||
usage()
|
||||
sys.exit(2)
|
||||
response = client.get(path)
|
||||
print((response.pretty_print()))
|
||||
print(response.pretty_print())
|
||||
client.stop()
|
||||
elif op == "OBSERVE":
|
||||
if path is None:
|
||||
|
@ -122,7 +122,7 @@ def main(): # pragma: no cover
|
|||
usage()
|
||||
sys.exit(2)
|
||||
response = client.delete(path)
|
||||
print((response.pretty_print()))
|
||||
print(response.pretty_print())
|
||||
client.stop()
|
||||
elif op == "POST":
|
||||
if path is None:
|
||||
|
@ -134,7 +134,7 @@ def main(): # pragma: no cover
|
|||
usage()
|
||||
sys.exit(2)
|
||||
response = client.post(path, payload)
|
||||
print((response.pretty_print()))
|
||||
print(response.pretty_print())
|
||||
client.stop()
|
||||
elif op == "PUT":
|
||||
if path is None:
|
||||
|
@ -146,11 +146,11 @@ def main(): # pragma: no cover
|
|||
usage()
|
||||
sys.exit(2)
|
||||
response = client.put(path, payload)
|
||||
print((response.pretty_print()))
|
||||
print(response.pretty_print())
|
||||
client.stop()
|
||||
elif op == "DISCOVER":
|
||||
response = client.discover()
|
||||
print((response.pretty_print()))
|
||||
print(response.pretty_print())
|
||||
client.stop()
|
||||
else:
|
||||
print("Operation not recognized")
|
||||
|
|
|
@ -11,7 +11,7 @@ class CoAPForwardProxy(CoAP):
|
|||
def __init__(self, host, port, multicast=False, cache=False):
|
||||
CoAP.__init__(self, (host, port), multicast=multicast, cache=cache)
|
||||
|
||||
print(("CoAP Proxy start on " + host + ":" + str(port)))
|
||||
print("CoAP Proxy start on " + host + ":" + str(port))
|
||||
|
||||
|
||||
def usage(): # pragma: no cover
|
||||
|
|
|
@ -51,7 +51,7 @@ if __name__ == '__main__':
|
|||
ping_cnt = 0 # global ping cnt
|
||||
|
||||
print('COAP ping script')
|
||||
print(('COAP ping to: %s:%s...' % (host, port)))
|
||||
print('COAP ping to: %s:%s...' % (host, port))
|
||||
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
@ -69,7 +69,7 @@ if __name__ == '__main__':
|
|||
msg += struct.pack("B", ping_no)
|
||||
|
||||
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
|
||||
s.sendto(msg, (host, port))
|
||||
s.settimeout(2 + sleep_sec)
|
||||
|
@ -81,11 +81,10 @@ if __name__ == '__main__':
|
|||
|
||||
# We need to check if ping peyload counter is the same in reply
|
||||
status = bytes(msg)[3] == bytes(reply)[3]
|
||||
print(('[0x%08X] Recv ping:' % (ping_cnt), [hex(ord(c)) for c in reply], 'ok' if status else 'fail'))
|
||||
print('[0x%08X] Recv ping:' % (ping_cnt), [hex(ord(c)) for c in reply], 'ok' if status else 'fail')
|
||||
|
||||
except socket.error as e:
|
||||
print(('Error: socket.error: ', str(e)))
|
||||
#print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
|
||||
print('Error: socket.error: ', str(e))
|
||||
sleep(3) # Waiting to recover ;)
|
||||
except socket.timeout:
|
||||
print("Error: closing socket")
|
||||
|
|
|
@ -12,7 +12,7 @@ class CoAPReverseProxy(CoAP):
|
|||
CoAP.__init__(self, (host, port), xml_file=xml_file, multicast=multicast, starting_mid=starting_mid,
|
||||
cache=cache)
|
||||
|
||||
print(("CoAP Proxy start on " + host + ":" + str(port)))
|
||||
print("CoAP Proxy start on " + host + ":" + str(port))
|
||||
|
||||
|
||||
def usage(): # pragma: no cover
|
||||
|
|
|
@ -26,8 +26,8 @@ class CoAPServer(CoAP):
|
|||
self.add_resource('advanced/', AdvancedResource())
|
||||
self.add_resource('advancedSeparate/', AdvancedResourceSeparate())
|
||||
|
||||
print(("CoAP Server start on " + host + ":" + str(port)))
|
||||
print((self.root.dump()))
|
||||
print("CoAP Server start on " + host + ":" + str(port))
|
||||
print(self.root.dump())
|
||||
|
||||
|
||||
def usage(): # pragma: no cover
|
||||
|
|
|
@ -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:
|
||||
:return:
|
||||
"""
|
||||
logger.debug("updating cache, key: %s, element: %s", \
|
||||
key.hashkey, element)
|
||||
logger.debug("updating cache, key: %s, element: %s", key.hashkey, element)
|
||||
self.cache.update([(key.hashkey, element)])
|
||||
|
||||
def get(self, key):
|
||||
|
|
|
@ -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 os
|
||||
import logging
|
||||
import random
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import collections
|
||||
|
||||
from coapthon import defines
|
||||
from coapthon.layers.blocklayer import BlockLayer
|
||||
|
@ -14,7 +14,6 @@ from coapthon.messages.message import Message
|
|||
from coapthon.messages.request import Request
|
||||
from coapthon.messages.response import Response
|
||||
from coapthon.serializer import Serializer
|
||||
import collections
|
||||
|
||||
|
||||
__author__ = 'Giacomo Tanganelli'
|
||||
|
@ -66,6 +65,13 @@ class CoAP(object):
|
|||
|
||||
self._receiver_thread = None
|
||||
|
||||
def purge_transactions(self, timeout_time=defines.EXCHANGE_LIFETIME):
|
||||
"""
|
||||
Clean old transactions
|
||||
|
||||
"""
|
||||
self._messageLayer.purge(timeout_time)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Stop the client.
|
||||
|
@ -76,7 +82,7 @@ class CoAP(object):
|
|||
event.set()
|
||||
if self._receiver_thread is not None:
|
||||
self._receiver_thread.join()
|
||||
self._socket.close()
|
||||
# self._socket.close()
|
||||
|
||||
@property
|
||||
def current_mid(self):
|
||||
|
@ -97,16 +103,21 @@ class CoAP(object):
|
|||
assert isinstance(c, int)
|
||||
self._currentMID = c
|
||||
|
||||
def send_message(self, message):
|
||||
def send_message(self, message, no_response=False):
|
||||
"""
|
||||
Prepare a message to send on the UDP socket. Eventually set retransmissions.
|
||||
|
||||
:param message: the message to send
|
||||
:param no_response: whether to await a response from the request
|
||||
"""
|
||||
if isinstance(message, Request):
|
||||
request = self._requestLayer.send_request(message)
|
||||
request = self._observeLayer.send_request(request)
|
||||
request = self._blockLayer.send_request(request)
|
||||
if no_response:
|
||||
# don't add the send message to the message layer transactions
|
||||
self.send_datagram(request)
|
||||
return
|
||||
transaction = self._messageLayer.send_request(request)
|
||||
self.send_datagram(transaction.request)
|
||||
if transaction.request.type == defines.Types["CON"]:
|
||||
|
@ -149,7 +160,7 @@ class CoAP(object):
|
|||
:param message: the message to send
|
||||
"""
|
||||
host, port = message.destination
|
||||
logger.debug("send_datagram - " + str(message))
|
||||
logger.info("send_datagram - " + str(message))
|
||||
serializer = Serializer()
|
||||
raw_message = serializer.serialize(message)
|
||||
|
||||
|
@ -264,7 +275,7 @@ class CoAP(object):
|
|||
message = serializer.deserialize(datagram, source)
|
||||
|
||||
if isinstance(message, Response):
|
||||
logger.debug("receive_datagram - " + str(message))
|
||||
logger.info("receive_datagram - " + str(message))
|
||||
transaction, send_ack = self._messageLayer.receive_response(message)
|
||||
if transaction is None: # pragma: no cover
|
||||
continue
|
||||
|
@ -291,6 +302,7 @@ class CoAP(object):
|
|||
self._messageLayer.receive_empty(message)
|
||||
|
||||
logger.debug("Exiting receiver Thread due to request")
|
||||
self._socket.close()
|
||||
|
||||
def _send_ack(self, transaction):
|
||||
"""
|
||||
|
|
|
@ -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 callback: the callback function to invoke upon response
|
||||
:param timeout: the timeout of the request
|
||||
:param no_response: whether to await a response from the request
|
||||
:return: the response
|
||||
"""
|
||||
if callback is not None:
|
||||
thread = threading.Thread(target=self._thread_body, args=(request, callback))
|
||||
thread.start()
|
||||
else:
|
||||
self.protocol.send_message(request)
|
||||
self.protocol.send_message(request, no_response=no_response)
|
||||
if no_response:
|
||||
return
|
||||
try:
|
||||
while True:
|
||||
response = self.queue.get(block=True, timeout=timeout)
|
||||
if response is not None:
|
||||
if response.mid == request.mid:
|
||||
return response
|
||||
if response.type == defines.Types["NON"]:
|
||||
return response
|
||||
else:
|
||||
return response
|
||||
except Empty:
|
||||
#if timeout is set
|
||||
response = None
|
||||
|
|
|
@ -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 array
|
||||
import struct
|
||||
|
||||
__author__ = 'Giacomo Tanganelli'
|
||||
|
@ -124,6 +125,7 @@ class OptionRegistry(object):
|
|||
LOCATION_QUERY = OptionItem(20,"Location-Query",STRING, True, None)
|
||||
BLOCK2 = OptionItem(23, "Block2", INTEGER, False, None)
|
||||
BLOCK1 = OptionItem(27, "Block1", INTEGER, False, None)
|
||||
SIZE2 = OptionItem(28, "Size2", INTEGER, False, None)
|
||||
PROXY_URI = OptionItem(35, "Proxy-Uri", STRING, False, None)
|
||||
PROXY_SCHEME = OptionItem(39, "Proxy-Schema", STRING, False, None)
|
||||
SIZE1 = OptionItem(60, "Size1", INTEGER, False, None)
|
||||
|
@ -147,6 +149,7 @@ class OptionRegistry(object):
|
|||
20: LOCATION_QUERY,
|
||||
23: BLOCK2,
|
||||
27: BLOCK1,
|
||||
28: SIZE2,
|
||||
35: PROXY_URI,
|
||||
39: PROXY_SCHEME,
|
||||
60: SIZE1,
|
||||
|
@ -165,7 +168,7 @@ class OptionRegistry(object):
|
|||
:return: option flags
|
||||
:rtype: 3-tuple (critical, unsafe, no-cache)
|
||||
"""
|
||||
opt_bytes = array.array('B', '\0\0')
|
||||
opt_bytes = bytearray(2)
|
||||
if option_num < 256:
|
||||
s = struct.Struct("!B")
|
||||
s.pack_into(opt_bytes, 0, option_num)
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import logging.config
|
||||
import logging
|
||||
import random
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
|
||||
import os
|
||||
|
||||
from coapthon import defines
|
||||
from coapthon.layers.blocklayer import BlockLayer
|
||||
from coapthon.layers.cachelayer import CacheLayer
|
||||
|
@ -88,27 +86,26 @@ class CoAP(object):
|
|||
# Allow multiple copies of this program on one machine
|
||||
# (not strictly needed)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.bind((defines.ALL_COAP_NODES, self.server_address[1]))
|
||||
self._socket.bind(('', self.server_address[1]))
|
||||
|
||||
mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY)
|
||||
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
||||
self._unicast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._unicast_socket.bind(self.server_address)
|
||||
else:
|
||||
# Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant
|
||||
if not hasattr(socket, 'IPPROTO_IPV6'):
|
||||
socket.IPPROTO_IPV6 = 41
|
||||
|
||||
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
|
||||
# Allow multiple copies of this program on one machine
|
||||
# (not strictly needed)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1]))
|
||||
self._socket.bind(('', self.server_address[1]))
|
||||
|
||||
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
|
||||
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
||||
mreq = group_bin + struct.pack('@I', 0)
|
||||
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._unicast_socket.bind(self.server_address)
|
||||
else:
|
||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
@ -159,7 +156,7 @@ class CoAP(object):
|
|||
self.stopped.set()
|
||||
for event in self.to_be_stopped:
|
||||
event.set()
|
||||
self._socket.close()
|
||||
# self._socket.close()
|
||||
|
||||
def receive_datagram(self, args):
|
||||
"""
|
||||
|
@ -189,7 +186,7 @@ class CoAP(object):
|
|||
rst.code = message
|
||||
self.send_datagram(rst)
|
||||
return
|
||||
logger.debug("receive_datagram - " + str(message))
|
||||
logger.info("receive_datagram - " + str(message))
|
||||
if isinstance(message, Request):
|
||||
|
||||
transaction = self._messageLayer.receive_request(message)
|
||||
|
@ -269,7 +266,7 @@ class CoAP(object):
|
|||
"""
|
||||
if not self.stopped.isSet():
|
||||
host, port = message.destination
|
||||
logger.debug("send_datagram - " + str(message))
|
||||
logger.info("send_datagram - " + str(message))
|
||||
serializer = Serializer()
|
||||
|
||||
message = serializer.serialize(message)
|
||||
|
|
|
@ -175,7 +175,7 @@ class HCProxyHandler(BaseHTTPRequestHandler):
|
|||
logger.debug(payload)
|
||||
coap_response = self.client.put(self.coap_uri.path, payload)
|
||||
self.client.stop()
|
||||
logger.debug("Server response: %s", coap_response.pretty_print())
|
||||
logger.info("Server response: %s", coap_response.pretty_print())
|
||||
self.set_http_response(coap_response)
|
||||
|
||||
def do_DELETE(self):
|
||||
|
@ -185,7 +185,7 @@ class HCProxyHandler(BaseHTTPRequestHandler):
|
|||
self.do_initial_operations()
|
||||
coap_response = self.client.delete(self.coap_uri.path)
|
||||
self.client.stop()
|
||||
logger.debug("Server response: %s", coap_response.pretty_print())
|
||||
logger.info("Server response: %s", coap_response.pretty_print())
|
||||
self.set_http_response(coap_response)
|
||||
|
||||
def do_CONNECT(self):
|
||||
|
|
|
@ -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
|
||||
|
||||
from coapthon import defines
|
||||
from coapthon import utils
|
||||
from coapthon.messages.request import Request
|
||||
from coapthon.messages.response import Response
|
||||
|
||||
|
@ -33,10 +35,10 @@ class BlockLayer(object):
|
|||
Handle the Blockwise options. Hides all the exchange to both servers and clients.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._block1_sent = {}
|
||||
self._block2_sent = {}
|
||||
self._block1_receive = {}
|
||||
self._block2_receive = {}
|
||||
self._block1_sent = {} # type: dict[hash, BlockItem]
|
||||
self._block2_sent = {} # type: dict[hash, BlockItem]
|
||||
self._block1_receive = {} # type: dict[hash, BlockItem]
|
||||
self._block2_receive = {} # type: dict[hash, BlockItem]
|
||||
|
||||
def receive_request(self, transaction):
|
||||
"""
|
||||
|
@ -49,7 +51,7 @@ class BlockLayer(object):
|
|||
"""
|
||||
if transaction.request.block2 is not None:
|
||||
host, port = transaction.request.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.request.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.request.token)
|
||||
num, m, size = transaction.request.block2
|
||||
if key_token in self._block2_receive:
|
||||
self._block2_receive[key_token].num = num
|
||||
|
@ -58,16 +60,20 @@ class BlockLayer(object):
|
|||
del transaction.request.block2
|
||||
else:
|
||||
# early negotiation
|
||||
byte = 0
|
||||
byte = num * size
|
||||
self._block2_receive[key_token] = BlockItem(byte, num, m, size)
|
||||
del transaction.request.block2
|
||||
|
||||
elif transaction.request.block1 is not None:
|
||||
# POST or PUT
|
||||
host, port = transaction.request.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.request.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.request.token)
|
||||
num, m, size = transaction.request.block1
|
||||
if transaction.request.size1 is not None:
|
||||
# What to do if the size1 is larger than the maximum resource size or the maxium server buffer
|
||||
pass
|
||||
if key_token in self._block1_receive:
|
||||
# n-th block
|
||||
content_type = transaction.request.content_type
|
||||
if num != self._block1_receive[key_token].num \
|
||||
or content_type != self._block1_receive[key_token].content_type:
|
||||
|
@ -118,7 +124,7 @@ class BlockLayer(object):
|
|||
:return: the edited transaction
|
||||
"""
|
||||
host, port = transaction.response.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.response.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.response.token)
|
||||
if key_token in self._block1_sent and transaction.response.block1 is not None:
|
||||
item = self._block1_sent[key_token]
|
||||
transaction.block_transfer = True
|
||||
|
@ -145,6 +151,8 @@ class BlockLayer(object):
|
|||
else:
|
||||
item.m = 1
|
||||
request.block1 = (item.num, item.m, item.size)
|
||||
# The original request already has this option set
|
||||
# request.size1 = len(item.payload)
|
||||
elif transaction.response.block2 is not None:
|
||||
|
||||
num, m, size = transaction.response.block2
|
||||
|
@ -208,7 +216,7 @@ class BlockLayer(object):
|
|||
:return: the edited transaction
|
||||
"""
|
||||
host, port = transaction.request.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.request.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.request.token)
|
||||
if (key_token in self._block2_receive and transaction.response.payload is not None) or \
|
||||
(transaction.response.payload is not None and len(transaction.response.payload) > defines.MAX_PAYLOAD):
|
||||
if key_token in self._block2_receive:
|
||||
|
@ -225,10 +233,13 @@ class BlockLayer(object):
|
|||
|
||||
self._block2_receive[key_token] = BlockItem(byte, num, m, size)
|
||||
|
||||
if len(transaction.response.payload) > (byte + size):
|
||||
m = 1
|
||||
else:
|
||||
m = 0
|
||||
# correct m
|
||||
m = 0 if ((num * size) + size) > len(transaction.response.payload) else 1
|
||||
# add size2 if requested or if payload is bigger than one datagram
|
||||
del transaction.response.size2
|
||||
if (transaction.request.size2 is not None and transaction.request.size2 == 0) or \
|
||||
(transaction.response.payload is not None and len(transaction.response.payload) > defines.MAX_PAYLOAD):
|
||||
transaction.response.size2 = len(transaction.response.payload)
|
||||
transaction.response.payload = transaction.response.payload[byte:byte + size]
|
||||
del transaction.response.block2
|
||||
transaction.response.block2 = (num, m, size)
|
||||
|
@ -251,21 +262,24 @@ class BlockLayer(object):
|
|||
assert isinstance(request, Request)
|
||||
if request.block1 or (request.payload is not None and len(request.payload) > defines.MAX_PAYLOAD):
|
||||
host, port = request.destination
|
||||
key_token = hash(str(host) + str(port) + str(request.token))
|
||||
key_token = utils.str_append_hash(host, port, request.token)
|
||||
if request.block1:
|
||||
num, m, size = request.block1
|
||||
else:
|
||||
num = 0
|
||||
m = 1
|
||||
size = defines.MAX_PAYLOAD
|
||||
|
||||
# correct m
|
||||
m = 0 if ((num * size) + size) > len(request.payload) else 1
|
||||
del request.size1
|
||||
request.size1 = len(request.payload)
|
||||
self._block1_sent[key_token] = BlockItem(size, num, m, size, request.payload, request.content_type)
|
||||
request.payload = request.payload[0:size]
|
||||
del request.block1
|
||||
request.block1 = (num, m, size)
|
||||
elif request.block2:
|
||||
host, port = request.destination
|
||||
key_token = hash(str(host) + str(port) + str(request.token))
|
||||
key_token = utils.str_append_hash(host, port, request.token)
|
||||
num, m, size = request.block2
|
||||
item = BlockItem(size, num, m, size, "", None)
|
||||
self._block2_sent[key_token] = item
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import copy
|
||||
import logging
|
||||
from coapthon.messages.request import Request
|
||||
from coapclient import HelperClient
|
||||
from coapthon.messages.response import Response
|
||||
|
@ -8,6 +9,8 @@ from coapthon.utils import parse_uri
|
|||
|
||||
__author__ = 'Giacomo Tanganelli'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ForwardLayer(object):
|
||||
"""
|
||||
|
@ -50,11 +53,12 @@ class ForwardLayer(object):
|
|||
:rtype : Transaction
|
||||
:return: the edited transaction
|
||||
"""
|
||||
wkc_resource_is_defined = defines.DISCOVERY_URL in self._server.root
|
||||
path = str("/" + transaction.request.uri_path)
|
||||
transaction.response = Response()
|
||||
transaction.response.destination = transaction.request.source
|
||||
transaction.response.token = transaction.request.token
|
||||
if path == defines.DISCOVERY_URL:
|
||||
if path == defines.DISCOVERY_URL and not wkc_resource_is_defined:
|
||||
transaction = self._server.resourceLayer.discover(transaction)
|
||||
else:
|
||||
new = False
|
||||
|
@ -146,8 +150,10 @@ class ForwardLayer(object):
|
|||
request.destination = transaction.resource.remote_server
|
||||
request.payload = transaction.request.payload
|
||||
request.code = transaction.request.code
|
||||
logger.info("forward_request - " + str(request))
|
||||
response = client.send_request(request)
|
||||
client.stop()
|
||||
logger.info("forward_response - " + str(response))
|
||||
transaction.response.payload = response.payload
|
||||
transaction.response.code = response.code
|
||||
transaction.response.options = response.options
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import logging
|
||||
import random
|
||||
import time
|
||||
import socket
|
||||
|
||||
from coapthon import utils
|
||||
from coapthon.messages.message import Message
|
||||
from coapthon import defines
|
||||
from coapthon.messages.request import Request
|
||||
|
@ -11,15 +14,6 @@ __author__ = 'Giacomo Tanganelli'
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def str_append_hash(*args):
|
||||
""" Convert each argument to a lower case string, appended, then hash """
|
||||
ret_hash = ""
|
||||
for i in args:
|
||||
ret_hash += str(i).lower()
|
||||
|
||||
return hash(ret_hash)
|
||||
|
||||
|
||||
class MessageLayer(object):
|
||||
"""
|
||||
Handles matching between messages (Message ID) and request/response (Token)
|
||||
|
@ -48,17 +42,17 @@ class MessageLayer(object):
|
|||
self._current_mid %= 65535
|
||||
return current_mid
|
||||
|
||||
def purge(self):
|
||||
def purge(self, timeout_time=defines.EXCHANGE_LIFETIME):
|
||||
for k in list(self._transactions.keys()):
|
||||
now = time.time()
|
||||
transaction = self._transactions[k]
|
||||
if transaction.timestamp + defines.EXCHANGE_LIFETIME < now:
|
||||
if transaction.timestamp + timeout_time < now:
|
||||
logger.debug("Delete transaction")
|
||||
del self._transactions[k]
|
||||
for k in list(self._transactions_token.keys()):
|
||||
now = time.time()
|
||||
transaction = self._transactions_token[k]
|
||||
if transaction.timestamp + defines.EXCHANGE_LIFETIME < now:
|
||||
if transaction.timestamp + timeout_time < now:
|
||||
logger.debug("Delete transaction")
|
||||
del self._transactions_token[k]
|
||||
|
||||
|
@ -71,13 +65,13 @@ class MessageLayer(object):
|
|||
:rtype : Transaction
|
||||
:return: the edited transaction
|
||||
"""
|
||||
logger.debug("receive_request - " + str(request))
|
||||
logger.info("receive_request - " + str(request))
|
||||
try:
|
||||
host, port = request.source
|
||||
except AttributeError:
|
||||
return
|
||||
key_mid = str_append_hash(host, port, request.mid)
|
||||
key_token = str_append_hash(host, port, request.token)
|
||||
key_mid = utils.str_append_hash(host, port, request.mid)
|
||||
key_token = utils.str_append_hash(host, port, request.token)
|
||||
|
||||
if key_mid in list(self._transactions.keys()):
|
||||
# Duplicated
|
||||
|
@ -100,15 +94,16 @@ class MessageLayer(object):
|
|||
:rtype : Transaction
|
||||
:return: the transaction to which the response belongs to
|
||||
"""
|
||||
logger.debug("receive_response - " + str(response))
|
||||
logger.info("receive_response - " + str(response))
|
||||
try:
|
||||
host, port = response.source
|
||||
except AttributeError:
|
||||
return
|
||||
key_mid = str_append_hash(host, port, response.mid)
|
||||
key_mid_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.mid)
|
||||
key_token = str_append_hash(host, port, response.token)
|
||||
key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.token)
|
||||
all_coap_nodes = defines.ALL_COAP_NODES_IPV6 if socket.getaddrinfo(host, None)[0][0] == socket.AF_INET6 else defines.ALL_COAP_NODES
|
||||
key_mid = utils.str_append_hash(host, port, response.mid)
|
||||
key_mid_multicast = utils.str_append_hash(all_coap_nodes, port, response.mid)
|
||||
key_token = utils.str_append_hash(host, port, response.token)
|
||||
key_token_multicast = utils.str_append_hash(all_coap_nodes, port, response.token)
|
||||
if key_mid in list(self._transactions.keys()):
|
||||
transaction = self._transactions[key_mid]
|
||||
if response.token != transaction.request.token:
|
||||
|
@ -146,15 +141,16 @@ class MessageLayer(object):
|
|||
:rtype : Transaction
|
||||
:return: the transaction to which the message belongs to
|
||||
"""
|
||||
logger.debug("receive_empty - " + str(message))
|
||||
logger.info("receive_empty - " + str(message))
|
||||
try:
|
||||
host, port = message.source
|
||||
except AttributeError:
|
||||
return
|
||||
key_mid = str_append_hash(host, port, message.mid)
|
||||
key_mid_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.mid)
|
||||
key_token = str_append_hash(host, port, message.token)
|
||||
key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.token)
|
||||
all_coap_nodes = defines.ALL_COAP_NODES_IPV6 if socket.getaddrinfo(host, None)[0][0] == socket.AF_INET6 else defines.ALL_COAP_NODES
|
||||
key_mid = utils.str_append_hash(host, port, message.mid)
|
||||
key_mid_multicast = utils.str_append_hash(all_coap_nodes, port, message.mid)
|
||||
key_token = utils.str_append_hash(host, port, message.token)
|
||||
key_token_multicast = utils.str_append_hash(all_coap_nodes, port, message.token)
|
||||
if key_mid in list(self._transactions.keys()):
|
||||
transaction = self._transactions[key_mid]
|
||||
elif key_token in self._transactions_token:
|
||||
|
@ -198,7 +194,7 @@ class MessageLayer(object):
|
|||
:rtype : Transaction
|
||||
:return: the created transaction
|
||||
"""
|
||||
logger.debug("send_request - " + str(request))
|
||||
logger.info("send_request - " + str(request))
|
||||
assert isinstance(request, Request)
|
||||
try:
|
||||
host, port = request.destination
|
||||
|
@ -213,10 +209,10 @@ class MessageLayer(object):
|
|||
if transaction.request.mid is None:
|
||||
transaction.request.mid = self.fetch_mid()
|
||||
|
||||
key_mid = str_append_hash(host, port, request.mid)
|
||||
key_mid = utils.str_append_hash(host, port, request.mid)
|
||||
self._transactions[key_mid] = transaction
|
||||
|
||||
key_token = str_append_hash(host, port, request.token)
|
||||
key_token = utils.str_append_hash(host, port, request.token)
|
||||
self._transactions_token[key_token] = transaction
|
||||
|
||||
return self._transactions[key_mid]
|
||||
|
@ -230,7 +226,7 @@ class MessageLayer(object):
|
|||
:rtype : Transaction
|
||||
:return: the edited transaction
|
||||
"""
|
||||
logger.debug("send_response - " + str(transaction.response))
|
||||
logger.info("send_response - " + str(transaction.response))
|
||||
if transaction.response.type is None:
|
||||
if transaction.request.type == defines.Types["CON"] and not transaction.request.acknowledged:
|
||||
transaction.response.type = defines.Types["ACK"]
|
||||
|
@ -249,7 +245,7 @@ class MessageLayer(object):
|
|||
host, port = transaction.response.destination
|
||||
except AttributeError:
|
||||
return
|
||||
key_mid = str_append_hash(host, port, transaction.response.mid)
|
||||
key_mid = utils.str_append_hash(host, port, transaction.response.mid)
|
||||
self._transactions[key_mid] = transaction
|
||||
|
||||
transaction.request.acknowledged = True
|
||||
|
@ -265,14 +261,14 @@ class MessageLayer(object):
|
|||
:type message: Message
|
||||
:param message: the ACK or RST message to send
|
||||
"""
|
||||
logger.debug("send_empty - " + str(message))
|
||||
logger.info("send_empty - " + str(message))
|
||||
if transaction is None:
|
||||
try:
|
||||
host, port = message.destination
|
||||
except AttributeError:
|
||||
return
|
||||
key_mid = str_append_hash(host, port, message.mid)
|
||||
key_token = str_append_hash(host, port, message.token)
|
||||
key_mid = utils.str_append_hash(host, port, message.mid)
|
||||
key_token = utils.str_append_hash(host, port, message.token)
|
||||
if key_mid in self._transactions:
|
||||
transaction = self._transactions[key_mid]
|
||||
related = transaction.response
|
||||
|
|
|
@ -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 time
|
||||
|
||||
from coapthon import defines
|
||||
from coapthon import utils
|
||||
|
||||
__author__ = 'Giacomo Tanganelli'
|
||||
|
||||
|
@ -40,7 +42,7 @@ class ObserveLayer(object):
|
|||
if request.observe == 0:
|
||||
# Observe request
|
||||
host, port = request.destination
|
||||
key_token = hash(str(host) + str(port) + str(request.token))
|
||||
key_token = utils.str_append_hash(host, port, request.token)
|
||||
|
||||
self._relations[key_token] = ObserveItem(time.time(), None, True, None)
|
||||
|
||||
|
@ -56,7 +58,7 @@ class ObserveLayer(object):
|
|||
:return: the modified transaction
|
||||
"""
|
||||
host, port = transaction.response.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.response.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.response.token)
|
||||
if key_token in self._relations and transaction.response.type == defines.Types["CON"]:
|
||||
transaction.notification = True
|
||||
return transaction
|
||||
|
@ -70,7 +72,7 @@ class ObserveLayer(object):
|
|||
:return: the message unmodified
|
||||
"""
|
||||
host, port = message.destination
|
||||
key_token = hash(str(host) + str(port) + str(message.token))
|
||||
key_token = utils.str_append_hash(host, port, message.token)
|
||||
if key_token in self._relations and message.type == defines.Types["RST"]:
|
||||
del self._relations[key_token]
|
||||
return message
|
||||
|
@ -88,7 +90,7 @@ class ObserveLayer(object):
|
|||
if transaction.request.observe == 0:
|
||||
# Observe request
|
||||
host, port = transaction.request.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.request.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.request.token)
|
||||
non_counter = 0
|
||||
if key_token in self._relations:
|
||||
# Renew registration
|
||||
|
@ -98,7 +100,7 @@ class ObserveLayer(object):
|
|||
self._relations[key_token] = ObserveItem(time.time(), non_counter, allowed, transaction)
|
||||
elif transaction.request.observe == 1:
|
||||
host, port = transaction.request.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.request.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.request.token)
|
||||
logger.info("Remove Subscriber")
|
||||
try:
|
||||
del self._relations[key_token]
|
||||
|
@ -120,7 +122,7 @@ class ObserveLayer(object):
|
|||
"""
|
||||
if empty.type == defines.Types["RST"]:
|
||||
host, port = transaction.request.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.request.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.request.token)
|
||||
logger.info("Remove Subscriber")
|
||||
try:
|
||||
del self._relations[key_token]
|
||||
|
@ -138,7 +140,7 @@ class ObserveLayer(object):
|
|||
:return: the transaction unmodified
|
||||
"""
|
||||
host, port = transaction.request.source
|
||||
key_token = hash(str(host) + str(port) + str(transaction.request.token))
|
||||
key_token = utils.str_append_hash(host, port, transaction.request.token)
|
||||
if key_token in self._relations:
|
||||
if transaction.response.code == defines.Codes.CONTENT.number:
|
||||
if transaction.resource is not None and transaction.resource.observable:
|
||||
|
@ -188,9 +190,9 @@ class ObserveLayer(object):
|
|||
|
||||
:param message: the message
|
||||
"""
|
||||
logger.debug("Remove Subcriber")
|
||||
logger.info("Remove Subcriber")
|
||||
host, port = message.destination
|
||||
key_token = hash(str(host) + str(port) + str(message.token))
|
||||
key_token = utils.str_append_hash(host, port, message.token)
|
||||
try:
|
||||
self._relations[key_token].transaction.completed = True
|
||||
del self._relations[key_token]
|
||||
|
|
|
@ -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
|
||||
:return: the edited transaction with the response to the request
|
||||
"""
|
||||
wkc_resource_is_defined = defines.DISCOVERY_URL in self._server.root
|
||||
path = str("/" + transaction.request.uri_path)
|
||||
transaction.response = Response()
|
||||
transaction.response.destination = transaction.request.source
|
||||
transaction.response.token = transaction.request.token
|
||||
if path == defines.DISCOVERY_URL:
|
||||
if path == defines.DISCOVERY_URL and not wkc_resource_is_defined:
|
||||
transaction = self._server.resourceLayer.discover(transaction)
|
||||
else:
|
||||
try:
|
||||
|
|
|
@ -105,6 +105,8 @@ class ResourceLayer(object):
|
|||
if resource.etag is not None:
|
||||
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
|
||||
|
||||
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 utils
|
||||
from coapthon.messages.option import Option
|
||||
|
||||
__author__ = 'Giacomo Tanganelli'
|
||||
|
@ -124,6 +128,7 @@ class Message(object):
|
|||
return
|
||||
if not isinstance(value, bytes):
|
||||
value = bytes(value)
|
||||
|
||||
if len(value) > 256:
|
||||
raise AttributeError
|
||||
self._token = value
|
||||
|
@ -547,7 +552,7 @@ class Message(object):
|
|||
value = None
|
||||
for option in self.options:
|
||||
if option.number == defines.OptionRegistry.BLOCK1.number:
|
||||
value = parse_blockwise(option.value)
|
||||
value = utils.parse_blockwise(option.value)
|
||||
return value
|
||||
|
||||
@block1.setter
|
||||
|
@ -599,7 +604,7 @@ class Message(object):
|
|||
value = None
|
||||
for option in self.options:
|
||||
if option.number == defines.OptionRegistry.BLOCK2.number:
|
||||
value = parse_blockwise(option.value)
|
||||
value = utils.parse_blockwise(option.value)
|
||||
return value
|
||||
|
||||
@block2.setter
|
||||
|
@ -641,6 +646,44 @@ class Message(object):
|
|||
"""
|
||||
self.del_option_by_number(defines.OptionRegistry.BLOCK2.number)
|
||||
|
||||
@property
|
||||
def size1(self):
|
||||
value = None
|
||||
for option in self.options:
|
||||
if option.number == defines.OptionRegistry.SIZE1.number:
|
||||
value = option.value if option.value is not None else 0
|
||||
return value
|
||||
|
||||
@size1.setter
|
||||
def size1(self, value):
|
||||
option = Option()
|
||||
option.number = defines.OptionRegistry.SIZE1.number
|
||||
option.value = value
|
||||
self.add_option(option)
|
||||
|
||||
@size1.deleter
|
||||
def size1(self):
|
||||
self.del_option_by_number(defines.OptionRegistry.SIZE1.number)
|
||||
|
||||
@property
|
||||
def size2(self):
|
||||
value = None
|
||||
for option in self.options:
|
||||
if option.number == defines.OptionRegistry.SIZE2.number:
|
||||
value = option.value if option.value is not None else 0
|
||||
return value
|
||||
|
||||
@size2.setter
|
||||
def size2(self, value):
|
||||
option = Option()
|
||||
option.number = defines.OptionRegistry.SIZE2.number
|
||||
option.value = value
|
||||
self.add_option(option)
|
||||
|
||||
@size2.deleter
|
||||
def size2(self):
|
||||
self.del_option_by_number(defines.OptionRegistry.SIZE2.number)
|
||||
|
||||
@property
|
||||
def line_print(self):
|
||||
"""
|
||||
|
@ -653,10 +696,15 @@ class Message(object):
|
|||
if self._code is None:
|
||||
self._code = defines.Codes.EMPTY.number
|
||||
|
||||
token = binascii.hexlify(self._token).decode("utf-8") if self._token is not None else str(None)
|
||||
|
||||
msg = "From {source}, To {destination}, {type}-{mid}, {code}-{token}, ["\
|
||||
.format(source=self._source, destination=self._destination, type=inv_types[self._type], mid=self._mid,
|
||||
code=defines.Codes.LIST[self._code].name, token=self._token)
|
||||
code=defines.Codes.LIST[self._code].name, token=token)
|
||||
for opt in self._options:
|
||||
if 'Block' in opt.name:
|
||||
msg += "{name}: {value}, ".format(name=opt.name, value=utils.parse_blockwise(opt.value))
|
||||
else:
|
||||
msg += "{name}: {value}, ".format(name=opt.name, value=opt.value)
|
||||
msg += "]"
|
||||
if self.payload is not None:
|
||||
|
@ -685,9 +733,9 @@ class Message(object):
|
|||
msg += "MID: " + str(self._mid) + "\n"
|
||||
if self._code is None:
|
||||
self._code = 0
|
||||
|
||||
token = binascii.hexlify(self._token).decode("utf-8") if self._token is not None else str(None)
|
||||
msg += "Code: " + str(defines.Codes.LIST[self._code].name) + "\n"
|
||||
msg += "Token: " + str(self._token) + "\n"
|
||||
msg += "Token: " + token + "\n"
|
||||
for opt in self._options:
|
||||
msg += str(opt)
|
||||
msg += "Payload: " + "\n"
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
if type(self._value) is None:
|
||||
self._value = bytearray()
|
||||
self._value = bytes()
|
||||
opt_type = defines.OptionRegistry.LIST[self._number].value_type
|
||||
if opt_type == defines.INTEGER:
|
||||
if byte_len(self._value) > 0:
|
||||
|
@ -73,7 +73,6 @@ class Option(object):
|
|||
else:
|
||||
if value is not None:
|
||||
value = bytes(value, "utf-8")
|
||||
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
|
|
|
@ -178,6 +178,20 @@ class Request(Message):
|
|||
return True
|
||||
return False
|
||||
|
||||
@if_none_match.setter
|
||||
def if_none_match(self, value):
|
||||
"""
|
||||
Set the If-Match option of a request.
|
||||
|
||||
:param value: True/False
|
||||
:type values : bool
|
||||
"""
|
||||
assert isinstance(value, bool)
|
||||
option = Option()
|
||||
option.number = defines.OptionRegistry.IF_NONE_MATCH.number
|
||||
option.value = None
|
||||
self.add_option(option)
|
||||
|
||||
def add_if_none_match(self):
|
||||
"""
|
||||
Add the if-none-match option to the request.
|
||||
|
|
|
@ -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 socket
|
||||
import struct
|
||||
import threading
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
@ -81,6 +80,8 @@ class CoAP(object):
|
|||
# Use given socket, could be a DTLS socket
|
||||
self._socket = sock
|
||||
|
||||
self.parse_config()
|
||||
|
||||
elif self.multicast: # pragma: no cover
|
||||
|
||||
# Create a socket
|
||||
|
@ -94,27 +95,26 @@ class CoAP(object):
|
|||
# Allow multiple copies of this program on one machine
|
||||
# (not strictly needed)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.bind((defines.ALL_COAP_NODES, self.server_address[1]))
|
||||
self._socket.bind(('', self.server_address[1]))
|
||||
|
||||
mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY)
|
||||
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
||||
self._unicast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._unicast_socket.bind(self.server_address)
|
||||
else:
|
||||
# Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant
|
||||
if not hasattr(socket, 'IPPROTO_IPV6'):
|
||||
socket.IPPROTO_IPV6 = 41
|
||||
|
||||
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
|
||||
# Allow multiple copies of this program on one machine
|
||||
# (not strictly needed)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1]))
|
||||
self._socket.bind(('', self.server_address[1]))
|
||||
|
||||
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
|
||||
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
||||
mreq = group_bin + struct.pack('@I', 0)
|
||||
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._unicast_socket.bind(self.server_address)
|
||||
else:
|
||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
@ -172,7 +172,8 @@ class CoAP(object):
|
|||
host, port = response.source
|
||||
|
||||
if response.code == defines.Codes.CONTENT.number:
|
||||
resource = Resource('server', self, visible=True, observable=False, allow_children=True)
|
||||
resource = RemoteResource('server', (host, port), "/",
|
||||
coap_server=self, visible=True, observable=False, allow_children=True)
|
||||
self.add_resource(name, resource)
|
||||
self._mapping[name] = (host, port)
|
||||
self.parse_core_link_format(response.payload, name, (host, port))
|
||||
|
@ -208,8 +209,8 @@ class CoAP(object):
|
|||
dict_att[a[0]] = a[0]
|
||||
link_format = link_format[result.end(0) + 1:]
|
||||
# TODO handle observing
|
||||
resource = RemoteResource('server', remote_server, path, coap_server=self, visible=True, observable=False,
|
||||
allow_children=True)
|
||||
resource = RemoteResource('server', remote_server, path,
|
||||
coap_server=self, visible=True, observable=False, allow_children=True)
|
||||
resource.attributes = dict_att
|
||||
self.add_resource(base_path + "/" + path, resource)
|
||||
|
||||
|
@ -251,7 +252,7 @@ class CoAP(object):
|
|||
self.stopped.set()
|
||||
for event in self.to_be_stopped:
|
||||
event.set()
|
||||
self._socket.close()
|
||||
# self._socket.close()
|
||||
|
||||
def receive_datagram(self, args):
|
||||
"""
|
||||
|
@ -272,7 +273,8 @@ class CoAP(object):
|
|||
rst.code = message
|
||||
self.send_datagram(rst)
|
||||
return
|
||||
logger.debug("receive_datagram - " + str(message))
|
||||
|
||||
logger.info("receive_datagram - " + str(message))
|
||||
if isinstance(message, Request):
|
||||
|
||||
transaction = self._messageLayer.receive_request(message)
|
||||
|
@ -318,6 +320,7 @@ class CoAP(object):
|
|||
transaction = self._blockLayer.send_response(transaction)
|
||||
|
||||
transaction = self._cacheLayer.send_response(transaction)
|
||||
|
||||
else:
|
||||
transaction = self._forwardLayer.receive_request_reverse(transaction)
|
||||
|
||||
|
@ -352,7 +355,7 @@ class CoAP(object):
|
|||
"""
|
||||
if not self.stopped.isSet():
|
||||
host, port = message.destination
|
||||
logger.debug("send_datagram - " + str(message))
|
||||
logger.info("send_datagram - " + str(message))
|
||||
serializer = Serializer()
|
||||
message = serializer.serialize(message)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import logging
|
||||
import struct
|
||||
import ctypes
|
||||
|
||||
from coapthon.messages.request import Request
|
||||
from coapthon.messages.response import Response
|
||||
from coapthon.messages.option import Option
|
||||
|
@ -67,6 +68,7 @@ class Serializer(object):
|
|||
# the first 4 bits of the byte represent the option delta
|
||||
# delta = self._reader.read(4).uint
|
||||
num, option_length, pos = Serializer.read_option_value_len_from_byte(next_byte, pos, values)
|
||||
logger.debug("option value (delta): %d len: %d", num, option_length)
|
||||
current_option += num
|
||||
# read option
|
||||
try:
|
||||
|
@ -78,8 +80,7 @@ class Serializer(object):
|
|||
else:
|
||||
# If the non-critical option is unknown
|
||||
# (vendor-specific, proprietary) - just skip it
|
||||
#log.err("unrecognized option %d" % current_option)
|
||||
pass
|
||||
logger.warning("unrecognized option %d", current_option)
|
||||
else:
|
||||
if option_length == 0:
|
||||
value = None
|
||||
|
@ -110,13 +111,17 @@ class Serializer(object):
|
|||
raise AttributeError("Packet length %s, pos %s" % (length_packet, pos))
|
||||
message.payload = ""
|
||||
payload = values[pos:]
|
||||
try:
|
||||
if message.payload_type == defines.Content_types["application/octet-stream"]:
|
||||
if hasattr(message, 'payload_type') and message.payload_type in [
|
||||
defines.Content_types["application/octet-stream"],
|
||||
defines.Content_types["application/exi"],
|
||||
defines.Content_types["application/cbor"]
|
||||
]:
|
||||
message.payload = payload
|
||||
else:
|
||||
try:
|
||||
message.payload = payload.decode("utf-8")
|
||||
except AttributeError:
|
||||
message.payload = payload.decode("utf-8")
|
||||
message.payload = payload
|
||||
pos += len(payload)
|
||||
|
||||
return message
|
||||
|
@ -124,6 +129,9 @@ class Serializer(object):
|
|||
return defines.Codes.BAD_REQUEST.number
|
||||
except struct.error:
|
||||
return defines.Codes.BAD_REQUEST.number
|
||||
except UnicodeDecodeError as e:
|
||||
logger.debug(e)
|
||||
return defines.Codes.BAD_REQUEST.number
|
||||
|
||||
@staticmethod
|
||||
def serialize(message):
|
||||
|
@ -137,7 +145,7 @@ class Serializer(object):
|
|||
"""
|
||||
fmt = "!BBH"
|
||||
|
||||
if message.token is None or message.token == "":
|
||||
if message.token is None:
|
||||
tkl = 0
|
||||
else:
|
||||
tkl = len(message.token)
|
||||
|
@ -195,7 +203,6 @@ class Serializer(object):
|
|||
elif opt_type == defines.STRING:
|
||||
fmt += str(len(bytes(option.value, "utf-8"))) + "s"
|
||||
values.append(bytes(option.value, "utf-8"))
|
||||
|
||||
else: # OPAQUE
|
||||
for b in option.value:
|
||||
fmt += "B"
|
||||
|
@ -220,9 +227,6 @@ class Serializer(object):
|
|||
else:
|
||||
fmt += str(len(bytes(payload, "utf-8"))) + "s"
|
||||
values.append(bytes(payload, "utf-8"))
|
||||
# for b in str(payload):
|
||||
# fmt += "c"
|
||||
# values.append(bytes(b, "utf-8"))
|
||||
|
||||
datagram = None
|
||||
if values[1] is None:
|
||||
|
@ -260,28 +264,6 @@ class Serializer(object):
|
|||
"""
|
||||
return defines.RESPONSE_CODE_LOWER_BOUND <= code <= defines.RESPONSE_CODE_UPPER_BOUND
|
||||
|
||||
@staticmethod
|
||||
def read_option_value_from_nibble(nibble, pos, values):
|
||||
"""
|
||||
Calculates the value used in the extended option fields.
|
||||
|
||||
:param nibble: the 4-bit option header value.
|
||||
:return: the value calculated from the nibble and the extended option value.
|
||||
"""
|
||||
if nibble <= 12:
|
||||
return nibble, pos
|
||||
elif nibble == 13:
|
||||
tmp = struct.unpack("!B", values[pos].to_bytes(1, "big"))[0] + 13
|
||||
pos += 1
|
||||
return tmp, pos
|
||||
elif nibble == 14:
|
||||
s = struct.Struct("!H")
|
||||
tmp = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269
|
||||
pos += 2
|
||||
return tmp, pos
|
||||
else:
|
||||
raise AttributeError("Unsupported option nibble " + str(nibble))
|
||||
|
||||
@staticmethod
|
||||
def read_option_value_len_from_byte(byte, pos, values):
|
||||
"""
|
||||
|
@ -301,7 +283,7 @@ class Serializer(object):
|
|||
pos += 1
|
||||
elif h_nibble == 14:
|
||||
s = struct.Struct("!H")
|
||||
value = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269
|
||||
value = s.unpack_from(values[pos:pos+2])[0] + 269
|
||||
pos += 2
|
||||
else:
|
||||
raise AttributeError("Unsupported option number nibble " + str(h_nibble))
|
||||
|
@ -312,7 +294,8 @@ class Serializer(object):
|
|||
length = struct.unpack("!B", values[pos].to_bytes(1, "big"))[0] + 13
|
||||
pos += 1
|
||||
elif l_nibble == 14:
|
||||
length = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269
|
||||
s = struct.Struct("!H")
|
||||
length = s.unpack_from(values[pos:pos+2])[0] + 269
|
||||
pos += 2
|
||||
else:
|
||||
raise AttributeError("Unsupported option length nibble " + str(l_nibble))
|
||||
|
@ -321,7 +304,7 @@ class Serializer(object):
|
|||
@staticmethod
|
||||
def convert_to_raw(number, value, length):
|
||||
"""
|
||||
Get the value of an option as a ByteArray.
|
||||
Get the value of an option as bytes.
|
||||
|
||||
:param number: the option number
|
||||
:param value: the option value
|
||||
|
@ -348,11 +331,11 @@ class Serializer(object):
|
|||
if isinstance(value, str):
|
||||
value = str(value)
|
||||
if isinstance(value, str):
|
||||
return bytearray(value, "utf-8")
|
||||
return bytes(value, "utf-8")
|
||||
elif isinstance(value, int):
|
||||
return value
|
||||
else:
|
||||
return bytearray(value)
|
||||
return bytes(value)
|
||||
|
||||
@staticmethod
|
||||
def as_sorted_list(options):
|
||||
|
|
|
@ -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 os
|
||||
import logging
|
||||
import random
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import collections
|
||||
|
||||
from coapthon import defines
|
||||
from coapthon.layers.blocklayer import BlockLayer
|
||||
|
@ -16,18 +16,13 @@ from coapthon.messages.request import Request
|
|||
from coapthon.messages.response import Response
|
||||
from coapthon.resources.resource import Resource
|
||||
from coapthon.serializer import Serializer
|
||||
from coapthon.utils import Tree, create_logging
|
||||
import collections
|
||||
from coapthon.utils import Tree
|
||||
|
||||
|
||||
__author__ = 'Giacomo Tanganelli'
|
||||
|
||||
|
||||
if not os.path.isfile("logging.conf"):
|
||||
create_logging()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.config.fileConfig("logging.conf", disable_existing_loggers=False)
|
||||
|
||||
|
||||
class CoAP(object):
|
||||
|
@ -88,20 +83,21 @@ class CoAP(object):
|
|||
mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY)
|
||||
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
||||
else:
|
||||
# Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant
|
||||
if not hasattr(socket, 'IPPROTO_IPV6'):
|
||||
socket.IPPROTO_IPV6 = 41
|
||||
|
||||
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
|
||||
# Allow multiple copies of this program on one machine
|
||||
# (not strictly needed)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1]))
|
||||
self._socket.bind(('', self.server_address[1]))
|
||||
|
||||
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
|
||||
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
||||
mreq = group_bin + struct.pack('@I', 0)
|
||||
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._unicast_socket.bind(self.server_address)
|
||||
else:
|
||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
@ -154,7 +150,7 @@ class CoAP(object):
|
|||
self.send_datagram(rst)
|
||||
continue
|
||||
|
||||
logger.debug("receive_datagram - " + str(message))
|
||||
logger.info("receive_datagram - " + str(message))
|
||||
if isinstance(message, Request):
|
||||
transaction = self._messageLayer.receive_request(message)
|
||||
if transaction.request.duplicated and transaction.completed:
|
||||
|
@ -246,7 +242,7 @@ class CoAP(object):
|
|||
"""
|
||||
if not self.stopped.isSet():
|
||||
host, port = message.destination
|
||||
logger.debug("send_datagram - " + str(message))
|
||||
logger.info("send_datagram - " + str(message))
|
||||
serializer = Serializer()
|
||||
message = serializer.serialize(message)
|
||||
self._socket.sendto(message, (host, port))
|
||||
|
@ -382,9 +378,10 @@ class CoAP(object):
|
|||
|
||||
ack = Message()
|
||||
ack.type = defines.Types['ACK']
|
||||
# TODO handle mutex on transaction
|
||||
with transaction:
|
||||
if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]:
|
||||
ack = self._messageLayer.send_empty(transaction, transaction.request, ack)
|
||||
if ack.type is not None and ack.mid is not None:
|
||||
self.send_datagram(ack)
|
||||
|
||||
def notify(self, resource):
|
||||
|
|
|
@ -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 string
|
||||
|
||||
__author__ = 'Giacomo Tanganelli'
|
||||
|
||||
|
||||
def str_append_hash(*args):
|
||||
""" Convert each argument to a lower case string, appended, then hash """
|
||||
ret_hash = ""
|
||||
for i in args:
|
||||
if isinstance(i, (str, int)):
|
||||
ret_hash += str(i).lower()
|
||||
elif isinstance(i, bytes):
|
||||
ret_hash += binascii.hexlify(i).decode("utf-8")
|
||||
|
||||
return hash(ret_hash)
|
||||
|
||||
|
||||
def check_nocachekey(option):
|
||||
"""
|
||||
checks if an option is a NoCacheKey option or Etag
|
||||
|
@ -51,7 +66,7 @@ def is_uri_option(number):
|
|||
|
||||
|
||||
def generate_random_token(size):
|
||||
return ''.join(random.choice(string.ascii_letters) for _ in range(size))
|
||||
return bytes([random.randint(0, 255) for _ in range(size)])
|
||||
|
||||
|
||||
def parse_blockwise(value):
|
||||
|
@ -186,3 +201,6 @@ class Tree(object):
|
|||
|
||||
def __delitem__(self, key):
|
||||
del self.tree[key]
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.tree
|
||||
|
|
|
@ -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
|
||||
import random
|
||||
import socket
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from coapclient import HelperClient
|
||||
from coapserver import CoAPServer
|
||||
from coapthon import defines
|
||||
|
@ -15,6 +18,48 @@ from coapthon.serializer import Serializer
|
|||
__author__ = 'Giacomo Tanganelli'
|
||||
__version__ = "2.0"
|
||||
|
||||
PAYLOAD = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut " \
|
||||
"labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et " \
|
||||
"ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum " \
|
||||
"dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore " \
|
||||
"magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " \
|
||||
"Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit " \
|
||||
"amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " \
|
||||
"aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita " \
|
||||
"kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. " \
|
||||
"Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore " \
|
||||
"eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum " \
|
||||
"zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer " \
|
||||
"adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. " \
|
||||
"Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip " \
|
||||
"ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie " \
|
||||
"consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim " \
|
||||
"qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. " \
|
||||
"Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat " \
|
||||
"facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh " \
|
||||
"euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis " \
|
||||
"nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. " \
|
||||
"Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore " \
|
||||
"eu feugiat nulla facilisis. " \
|
||||
"At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata " \
|
||||
"sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " \
|
||||
"nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et " \
|
||||
"accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem " \
|
||||
"ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam " \
|
||||
"diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea " \
|
||||
"et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor " \
|
||||
"sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor " \
|
||||
"invidunt ut labore et dolore magna aliquyam erat. " \
|
||||
"Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " \
|
||||
"aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita " \
|
||||
"kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " \
|
||||
"consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam " \
|
||||
"erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd " \
|
||||
"gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " \
|
||||
"consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam " \
|
||||
"erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd " \
|
||||
"gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
|
||||
|
@ -23,7 +68,7 @@ class Tests(unittest.TestCase):
|
|||
self.current_mid = random.randint(1, 1000)
|
||||
self.server_mid = random.randint(1000, 2000)
|
||||
self.server = CoAPServer("127.0.0.1", 5683)
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(10,))
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(1,))
|
||||
self.server_thread.start()
|
||||
self.queue = Queue()
|
||||
|
||||
|
@ -222,88 +267,86 @@ class Tests(unittest.TestCase):
|
|||
self.current_mid += 1
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
|
||||
# def test_separate(self):
|
||||
# print "TEST_SEPARATE"
|
||||
# path = "/separate"
|
||||
# req = Request()
|
||||
# req.code = defines.Codes.GET.number
|
||||
# req.uri_path = path
|
||||
# req.type = defines.Types["CON"]
|
||||
# req._mid = self.current_mid
|
||||
# req.destination = self.server_address
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["CON"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.CONTENT.number
|
||||
# expected.token = None
|
||||
# expected.max_age = 60
|
||||
#
|
||||
# exchange1 = (req, expected)
|
||||
#
|
||||
# self.current_mid += 1
|
||||
#
|
||||
# req = Request()
|
||||
# req.code = defines.Codes.POST.number
|
||||
# req.uri_path = path
|
||||
# req.type = defines.Types["CON"]
|
||||
# req._mid = self.current_mid
|
||||
# req.destination = self.server_address
|
||||
# req.payload = "POST"
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["CON"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.CHANGED.number
|
||||
# expected.token = None
|
||||
# expected.options = None
|
||||
#
|
||||
# exchange2 = (req, expected)
|
||||
#
|
||||
# self.current_mid += 1
|
||||
#
|
||||
# req = Request()
|
||||
# req.code = defines.Codes.PUT.number
|
||||
# req.uri_path = path
|
||||
# req.type = defines.Types["CON"]
|
||||
# req._mid = self.current_mid
|
||||
# req.destination = self.server_address
|
||||
# req.payload = "PUT"
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["CON"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.CHANGED.number
|
||||
# expected.token = None
|
||||
# expected.options = None
|
||||
#
|
||||
# exchange3 = (req, expected)
|
||||
#
|
||||
# self.current_mid += 1
|
||||
#
|
||||
# req = Request()
|
||||
# req.code = defines.Codes.DELETE.number
|
||||
# req.uri_path = path
|
||||
# req.type = defines.Types["CON"]
|
||||
# req._mid = self.current_mid
|
||||
# req.destination = self.server_address
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["CON"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.DELETED.number
|
||||
# expected.token = None
|
||||
#
|
||||
# exchange4 = (req, expected)
|
||||
#
|
||||
# self.current_mid += 1
|
||||
# self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
def test_separate(self):
|
||||
print("TEST_SEPARATE")
|
||||
path = "/separate"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["CON"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.CONTENT.number
|
||||
expected.token = None
|
||||
expected.max_age = 60
|
||||
|
||||
exchange1 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.POST.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "POST"
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["CON"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.CHANGED.number
|
||||
expected.token = None
|
||||
expected.options = None
|
||||
|
||||
exchange2 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.PUT.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "PUT"
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["CON"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.CHANGED.number
|
||||
expected.token = None
|
||||
expected.options = None
|
||||
|
||||
exchange3 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.DELETE.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["CON"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.DELETED.number
|
||||
expected.token = None
|
||||
|
||||
exchange4 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
|
||||
def test_post(self):
|
||||
print("TEST_POST")
|
||||
path = "/storage/new_res?id=1"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.POST.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -407,18 +450,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sollicitudin fermentum ornare. " \
|
||||
"Cras accumsan tellus quis dui lacinia eleifend. Proin ultrices rutrum orci vitae luctus. " \
|
||||
"Nullam malesuada pretium elit, at aliquam odio vehicula in. Etiam nec maximus elit. " \
|
||||
"Etiam at erat ac ex ornare feugiat. Curabitur sed malesuada orci, id aliquet nunc. Phasellus " \
|
||||
"nec leo luctus, blandit lorem sit amet, interdum metus. Duis efficitur volutpat magna, ac " \
|
||||
"ultricies nibh aliquet sit amet. Etiam tempor egestas augue in hendrerit. Nunc eget augue " \
|
||||
"ultricies, dignissim lacus et, vulputate dolor. Nulla eros odio, fringilla vel massa ut, " \
|
||||
"facilisis cursus quam. Fusce faucibus lobortis congue. Fusce consectetur porta neque, id " \
|
||||
"sollicitudin velit maximus eu. Sed pharetra leo quam, vel finibus turpis cursus ac. " \
|
||||
"Aenean ac nisi massa. Cras commodo arcu nec ante tristique ullamcorper. Quisque eu hendrerit" \
|
||||
" urna. Cras fringilla eros ut nunc maximus, non porta nisl mollis. Aliquam in rutrum massa." \
|
||||
" Praesent tristique turpis dui, at ultri"
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (1, 1, 1024)
|
||||
|
||||
expected = Response()
|
||||
|
@ -437,18 +469,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sollicitudin fermentum ornare. " \
|
||||
"Cras accumsan tellus quis dui lacinia eleifend. Proin ultrices rutrum orci vitae luctus. " \
|
||||
"Nullam malesuada pretium elit, at aliquam odio vehicula in. Etiam nec maximus elit. " \
|
||||
"Etiam at erat ac ex ornare feugiat. Curabitur sed malesuada orci, id aliquet nunc. Phasellus " \
|
||||
"nec leo luctus, blandit lorem sit amet, interdum metus. Duis efficitur volutpat magna, ac " \
|
||||
"ultricies nibh aliquet sit amet. Etiam tempor egestas augue in hendrerit. Nunc eget augue " \
|
||||
"ultricies, dignissim lacus et, vulputate dolor. Nulla eros odio, fringilla vel massa ut, " \
|
||||
"facilisis cursus quam. Fusce faucibus lobortis congue. Fusce consectetur porta neque, id " \
|
||||
"sollicitudin velit maximus eu. Sed pharetra leo quam, vel finibus turpis cursus ac. " \
|
||||
"Aenean ac nisi massa. Cras commodo arcu nec ante tristique ullamcorper. Quisque eu hendrerit" \
|
||||
" urna. Cras fringilla eros ut nunc maximus, non porta nisl mollis. Aliquam in rutrum massa." \
|
||||
" Praesent tristique turpis dui, at ultri"
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (0, 1, 1024)
|
||||
|
||||
expected = Response()
|
||||
|
@ -468,10 +489,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "a imperdiet nisl. Quisque a iaculis libero, id tempus lacus. Aenean convallis est non justo " \
|
||||
"consectetur, a hendrerit enim consequat. In accumsan ante a egestas luctus. Etiam quis neque " \
|
||||
"nec eros vestibulum faucibus. Nunc viverra ipsum lectus, vel scelerisque dui dictum a. Ut orci " \
|
||||
"enim, ultrices a ultrices nec, pharetra in quam. Donec accumsan sit amet eros eget fermentum."
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (1, 1, 64)
|
||||
|
||||
expected = Response()
|
||||
|
@ -491,10 +509,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "a imperdiet nisl. Quisque a iaculis libero, id tempus lacus. Aenean convallis est non justo " \
|
||||
"consectetur, a hendrerit enim consequat. In accumsan ante a egestas luctus. Etiam quis neque " \
|
||||
"nec eros vestibulum faucibus. Nunc viverra ipsum lectus, vel scelerisque dui dictum a. Ut orci " \
|
||||
"enim, ultrices a ultrices nec, pharetra in quam. Donec accumsan sit amet eros eget fermentum."
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (3, 1, 64)
|
||||
|
||||
expected = Response()
|
||||
|
@ -513,10 +528,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "a imperdiet nisl. Quisque a iaculis libero, id tempus lacus. Aenean convallis est non justo " \
|
||||
"consectetur, a hendrerit enim consequat. In accumsan ante a egestas luctus. Etiam quis neque " \
|
||||
"nec eros vestibulum faucibus. Nunc viverra ipsum lectus, vel scelerisque dui dictum a. Ut orci " \
|
||||
"enim, ultrices a ultrices nec, pharetra in quam. Donec accumsan sit amet eros eget fermentum."
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (2, 0, 64)
|
||||
|
||||
expected = Response()
|
||||
|
@ -536,6 +548,27 @@ class Tests(unittest.TestCase):
|
|||
print("TEST_GET_BLOCK")
|
||||
path = "/big"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = None
|
||||
# req.block2 = (0, 0, 512)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["ACK"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.CONTENT.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (0, 1, defines.MAX_PAYLOAD)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange0 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -552,6 +585,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (0, 1, 512)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange1 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -572,6 +606,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (1, 1, 256)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange2 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -592,6 +627,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (2, 1, 128)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange3 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -612,6 +648,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (3, 1, 64)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange4 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -632,6 +669,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (4, 1, 32)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange5 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -652,6 +690,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (5, 1, 16)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange6 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -671,7 +710,8 @@ class Tests(unittest.TestCase):
|
|||
expected.code = defines.Codes.CONTENT.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (6, 1, 1024)
|
||||
expected.block2 = (6, 0, 1024)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange7 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -692,11 +732,13 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (7, 0, 1024)
|
||||
expected.size2 = 2041
|
||||
|
||||
exchange8 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_plugtest([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7, exchange8])
|
||||
self._test_plugtest([exchange0, exchange1, exchange2, exchange3, exchange4,
|
||||
exchange5, exchange6, exchange7, exchange8])
|
||||
|
||||
def test_post_block_big(self):
|
||||
print("TEST_POST_BLOCK_BIG")
|
||||
|
@ -708,7 +750,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "Lorem ipsum dolo"
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (0, 1, 16)
|
||||
|
||||
expected = Response()
|
||||
|
@ -728,7 +770,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "r sit amet, consectetur adipisci"
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (1, 1, 32)
|
||||
|
||||
expected = Response()
|
||||
|
@ -748,7 +790,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "ng elit. Sed ut ultrices ligula. Pellentesque purus augue, cursu"
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (2, 1, 64)
|
||||
|
||||
expected = Response()
|
||||
|
@ -768,8 +810,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "s ultricies est in, vehicula congue metus. Vestibulum vel justo lacinia, porttitor quam vitae, " \
|
||||
"feugiat sapien. Quisque finibus, "
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (3, 1, 128)
|
||||
|
||||
expected = Response()
|
||||
|
@ -789,9 +830,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "nisi vitae rhoncus malesuada, augue mauris dapibus tellus, sit amet venenatis libero" \
|
||||
" libero sed lorem. In pharetra turpis sed eros porta mollis. Quisque dictum dolor nisl," \
|
||||
" imperdiet tincidunt augue malesuada vitae. Donec non felis urna. Suspendisse at hend"
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (4, 1, 256)
|
||||
|
||||
expected = Response()
|
||||
|
@ -811,12 +850,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "rerit ex, quis aliquet ante. Vivamus ultrices dolor at elit tincidunt, eget fringilla " \
|
||||
"ligula vestibulum. In molestie sagittis nibh, ut efficitur tellus faucibus non. Maecenas " \
|
||||
"posuere elementum faucibus. Morbi nisi diam, molestie non feugiat et, elementum eget magna." \
|
||||
" Donec vel sem facilisis quam viverra ultrices nec eu lacus. Sed molestie nisi id ultrices " \
|
||||
"interdum. Curabitur pharetra sed tellus in dignissim. Duis placerat aliquam metus, volutpat " \
|
||||
"elementum augue aliquam a. Nunc sed dolor at orci maximus portt"
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (5, 1, 512)
|
||||
|
||||
expected = Response()
|
||||
|
@ -836,18 +870,7 @@ class Tests(unittest.TestCase):
|
|||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
req.payload = "itor ac sit amet eros. Mauris et nisi in tortor pharetra rhoncus sit amet hendrerit metus. " \
|
||||
"Integer laoreet placerat cursus. Nam a nulla ex. Donec laoreet sagittis libero quis " \
|
||||
"imperdiet. Vivamus facilisis turpis nec rhoncus venenatis. Duis pulvinar tellus vel quam " \
|
||||
"maximus imperdiet. Mauris eget nibh orci. Duis ut cursus nibh. Nulla sed commodo elit. " \
|
||||
"Suspendisse ac eros lacinia, mattis turpis at, porttitor justo. Vivamus molestie " \
|
||||
"tincidunt libero. Etiam porttitor lacus odio, at lobortis tortor scelerisque nec. " \
|
||||
"Nullam non ante vel nisi ultrices consectetur. Maecenas massa felis, tempor eget " \
|
||||
"malesuada eget, pretium eu sapien. Vivamus dapibus ante erat, non faucibus orci sodales " \
|
||||
"sit amet. Cras magna felis, sodales eget magna sed, eleifend rutrum ligula. Vivamus interdum " \
|
||||
"enim enim, eu facilisis tortor dignissim quis. Ut metus nulla, mattis non lorem et, " \
|
||||
"elementum ultrices orci. Quisque eleifend, arcu vitae ullamcorper pulvinar, ipsum ex " \
|
||||
"sodales arcu, eget consectetur mauris metus ac tortor. Donec id sem felis. Maur"
|
||||
req.payload = PAYLOAD
|
||||
req.block1 = (6, 0, 1024)
|
||||
|
||||
expected = Response()
|
||||
|
@ -856,7 +879,6 @@ class Tests(unittest.TestCase):
|
|||
expected.code = defines.Codes.CHANGED.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.location_path = "big"
|
||||
|
||||
exchange7 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -941,75 +963,74 @@ class Tests(unittest.TestCase):
|
|||
|
||||
self._test_with_client([exchange1, exchange2, exchange3])
|
||||
|
||||
# def test_long_options(self):
|
||||
# """
|
||||
# Test processing of options with extended length
|
||||
# """
|
||||
# print("TEST_LONG_OPTIONS")
|
||||
#
|
||||
# path = "/storage/"
|
||||
# req = Request()
|
||||
# req.code = defines.Codes.GET.number
|
||||
# req.uri_path = path
|
||||
# req.type = defines.Types["CON"]
|
||||
# req._mid = self.current_mid
|
||||
# req.destination = self.server_address
|
||||
# option = Option()
|
||||
# # This option should be silently ignored by the server
|
||||
# # since it is not critical
|
||||
# option.number = defines.OptionRegistry.RM_MESSAGE_SWITCHING.number
|
||||
# option.value = "\1\1\1\1\0\0"
|
||||
# options = req.options
|
||||
# req.add_option(option)
|
||||
# req.payload = "test"
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["ACK"]
|
||||
# expected.code = defines.Codes.CONTENT.number
|
||||
# expected.token = None
|
||||
# expected.payload = None
|
||||
#
|
||||
# exchange1 = (req, expected)
|
||||
# self.current_mid += 1
|
||||
#
|
||||
# self._test_with_client([exchange1])
|
||||
#
|
||||
# # This option (244) should be silently ignored by the server
|
||||
# req = ("\x40\x01\x01\x01\xd6\xe7\x01\x01\x01\x01\x00\x00", self.server_address)
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["ACK"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.NOT_FOUND.number
|
||||
# expected.token = None
|
||||
# expected.payload = None
|
||||
#
|
||||
# exchange21 = (req, expected)
|
||||
# self.current_mid += 1
|
||||
#
|
||||
# # This option (245) should cause BAD REQUEST, as unrecognizable critical
|
||||
# req = ("\x40\x01\x01\x01\xd6\xe8\x01\x01\x01\x01\x00\x00", self.server_address)
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["RST"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
||||
#
|
||||
# exchange22 = (req, expected)
|
||||
# self.current_mid += 1
|
||||
#
|
||||
# # This option (65525) should cause BAD REQUEST, as unrecognizable critical
|
||||
# req = ("\x40\x01\x01\x01\xe6\xfe\xe8\x01\x01\x01\x01\x00\x00", self.server_address)
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["RST"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
||||
#
|
||||
# exchange23 = (req, expected)
|
||||
# self.current_mid += 1
|
||||
#
|
||||
# self._test_datagram([exchange21, exchange22, exchange23])
|
||||
def test_long_options(self):
|
||||
"""
|
||||
Test processing of options with extended length
|
||||
"""
|
||||
print("TEST_LONG_OPTIONS")
|
||||
path = "/storage/"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
req._mid = self.current_mid
|
||||
req.destination = self.server_address
|
||||
option = Option()
|
||||
# This option should be silently ignored by the server
|
||||
# since it is not critical
|
||||
option.number = defines.OptionRegistry.RM_MESSAGE_SWITCHING.number
|
||||
option.value = b'\1\1\1\1\0\0'
|
||||
req.add_option(option)
|
||||
req.payload = "test"
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["ACK"]
|
||||
expected.code = defines.Codes.CONTENT.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
|
||||
exchange1 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_with_client([exchange1])
|
||||
|
||||
# This option (244) should be silently ignored by the server
|
||||
req = (b'\x40\x01\x01\x01\xd6\xe7\x01\x01\x01\x01\x00\x00', self.server_address)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["ACK"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.NOT_FOUND.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
|
||||
exchange21 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
# This option (245) should cause BAD REQUEST, as unrecognizable critical
|
||||
req = (b'\x40\x01\x01\x01\xd6\xe8\x01\x01\x01\x01\x00\x00', self.server_address)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["RST"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.BAD_REQUEST.number
|
||||
|
||||
exchange22 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
# This option (65525) should cause BAD REQUEST, as unrecognizable critical
|
||||
req = (b'\x40\x01\x01\x01\xe6\xfe\xe8\x01\x01\x01\x01\x00\x00', self.server_address)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["RST"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.BAD_REQUEST.number
|
||||
|
||||
exchange23 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_datagram([exchange21, exchange22, exchange23])
|
||||
|
||||
def test_content_type(self):
|
||||
print("TEST_CONTENT_TYPE")
|
||||
|
@ -1138,7 +1159,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = "<value>0</value>"
|
||||
|
||||
print((expected.pretty_print()))
|
||||
print(expected.pretty_print())
|
||||
|
||||
exchange7 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -1198,9 +1219,8 @@ class Tests(unittest.TestCase):
|
|||
exchange10 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
||||
# self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5, exchange6, exchange7,
|
||||
# exchange8, exchange9, exchange10])
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5])
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4, exchange5,
|
||||
exchange6, exchange7, exchange8, exchange9, exchange10])
|
||||
|
||||
def test_ETAG(self):
|
||||
print("TEST_ETAG")
|
||||
|
@ -1238,7 +1258,6 @@ class Tests(unittest.TestCase):
|
|||
expected.code = defines.Codes.CHANGED.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.location_path = path
|
||||
expected.etag = "1"
|
||||
|
||||
exchange2 = (req, expected)
|
||||
|
@ -1301,7 +1320,7 @@ class Tests(unittest.TestCase):
|
|||
expected.code = defines.Codes.CREATED.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.location_path = path
|
||||
expected.location_path = "child"
|
||||
|
||||
exchange1 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -1376,7 +1395,7 @@ class Tests(unittest.TestCase):
|
|||
expected.type = defines.Types["ACK"]
|
||||
expected._mid = self.current_mid
|
||||
expected.code = defines.Codes.NOT_FOUND.number
|
||||
expected.token = "100"
|
||||
expected.token = 100
|
||||
expected.payload = None
|
||||
|
||||
exchange1 = (req, expected)
|
||||
|
@ -1436,60 +1455,60 @@ class Tests(unittest.TestCase):
|
|||
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
|
||||
# def test_invalid(self):
|
||||
# print("TEST_INVALID")
|
||||
#
|
||||
# # version
|
||||
# req = ("\x00\x01\x8c\xda", self.server_address)
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["RST"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
||||
#
|
||||
# exchange1 = (req, expected)
|
||||
#
|
||||
# # version
|
||||
# req = ("\x40", self.server_address)
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["RST"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
||||
#
|
||||
# exchange2 = (req, expected)
|
||||
#
|
||||
# # code
|
||||
# req = ("\x40\x05\x8c\xda", self.server_address)
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["RST"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
||||
#
|
||||
# exchange3 = (req, expected)
|
||||
#
|
||||
# # option
|
||||
# req = ("\x40\x01\x8c\xda\x94", self.server_address)
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["RST"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
||||
#
|
||||
# exchange4 = (req, expected)
|
||||
#
|
||||
# # payload marker
|
||||
# req = ("\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff", self.server_address)
|
||||
#
|
||||
# expected = Response()
|
||||
# expected.type = defines.Types["RST"]
|
||||
# expected._mid = None
|
||||
# expected.code = defines.Codes.BAD_REQUEST.number
|
||||
#
|
||||
# exchange5 = (req, expected)
|
||||
#
|
||||
# self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5])
|
||||
def test_invalid(self):
|
||||
print("TEST_INVALID")
|
||||
|
||||
# version
|
||||
req = (b'\x00\x01\x8c\xda', self.server_address)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["RST"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.BAD_REQUEST.number
|
||||
|
||||
exchange1 = (req, expected)
|
||||
|
||||
# version
|
||||
req = (b'\x40', self.server_address)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["RST"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.BAD_REQUEST.number
|
||||
|
||||
exchange2 = (req, expected)
|
||||
|
||||
# code
|
||||
req = (b'\x40\x05\x8c\xda', self.server_address)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["RST"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.BAD_REQUEST.number
|
||||
|
||||
exchange3 = (req, expected)
|
||||
|
||||
# option
|
||||
req = (b'\x40\x01\x8c\xda\x94', self.server_address)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["RST"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.BAD_REQUEST.number
|
||||
|
||||
exchange4 = (req, expected)
|
||||
|
||||
# payload marker
|
||||
req = (b'\x40\x02\x8c\xda\x75\x62\x61\x73\x69\x63\xff', self.server_address)
|
||||
|
||||
expected = Response()
|
||||
expected.type = defines.Types["RST"]
|
||||
expected._mid = None
|
||||
expected.code = defines.Codes.BAD_REQUEST.number
|
||||
|
||||
exchange5 = (req, expected)
|
||||
|
||||
self._test_datagram([exchange1, exchange2, exchange3, exchange4, exchange5])
|
||||
|
||||
def test_post_block_big_client(self):
|
||||
print("TEST_POST_BLOCK_BIG_CLIENT")
|
||||
|
@ -1571,6 +1590,6 @@ class Tests(unittest.TestCase):
|
|||
|
||||
self._test_with_client_observe([exchange1, exchange2])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from queue import Queue
|
||||
import random
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from coapclient import HelperClient
|
||||
from coapserver import CoAPServer
|
||||
from coapthon import defines
|
||||
|
@ -20,7 +23,7 @@ class Tests(unittest.TestCase):
|
|||
self.current_mid = random.randint(1, 1000)
|
||||
self.server_mid = random.randint(1000, 2000)
|
||||
self.server = CoAPServer("::1", 5683)
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(10,))
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(1,))
|
||||
self.server_thread.start()
|
||||
self.queue = Queue()
|
||||
|
||||
|
@ -89,6 +92,7 @@ class Tests(unittest.TestCase):
|
|||
def test_not_allowed(self):
|
||||
print("TEST_NOT_ALLOWED")
|
||||
path = "/void"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -103,7 +107,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange1 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -120,7 +123,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange2 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -137,7 +139,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange3 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -154,11 +155,10 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange4 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import threading
|
||||
import unittest
|
||||
from queue import Queue
|
||||
|
@ -18,7 +20,7 @@ class Tests(unittest.TestCase):
|
|||
self.current_mid = random.randint(1, 1000)
|
||||
self.server_mid = random.randint(1000, 2000)
|
||||
self.server = CoAPServer("127.0.0.1", 5683)
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(10,))
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(1,))
|
||||
self.server_thread.start()
|
||||
self.queue = Queue()
|
||||
|
||||
|
@ -61,6 +63,7 @@ class Tests(unittest.TestCase):
|
|||
def test_advanced(self):
|
||||
print("TEST_ADVANCED")
|
||||
path = "/advanced"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -76,7 +79,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange1 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -94,7 +96,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange2 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -112,7 +113,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange3 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -130,13 +130,14 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange4 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
|
||||
def test_advanced_separate(self):
|
||||
print("TEST_ADVANCED_SEPARATE")
|
||||
path = "/advancedSeparate"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -152,7 +153,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange1 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -170,7 +170,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange2 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -188,7 +187,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange3 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -206,9 +204,10 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange4 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from queue import Queue
|
||||
import random
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from coapclient import HelperClient
|
||||
from coapserver import CoAPServer
|
||||
from coapthon import defines
|
||||
from coapthon.messages.message import Message
|
||||
from coapthon.messages.option import Option
|
||||
from coapthon.messages.request import Request
|
||||
from coapthon.messages.response import Response
|
||||
|
@ -21,7 +23,7 @@ class Tests(unittest.TestCase):
|
|||
self.current_mid = random.randint(1, 1000)
|
||||
self.server_mid = random.randint(1000, 2000)
|
||||
self.server = CoAPServer("0.0.0.0", 5683, multicast=True)
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(10,))
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(1,))
|
||||
self.server_thread.start()
|
||||
self.queue = Queue()
|
||||
|
||||
|
@ -90,6 +92,7 @@ class Tests(unittest.TestCase):
|
|||
def test_not_allowed(self):
|
||||
print("TEST_NOT_ALLOWED")
|
||||
path = "/void"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -104,7 +107,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange1 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -121,7 +123,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange2 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -138,7 +139,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange3 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -155,11 +155,10 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange4 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from queue import Queue
|
||||
import random
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from coapclient import HelperClient
|
||||
from coapserver import CoAPServer
|
||||
from coapthon import defines
|
||||
from coapthon.messages.message import Message
|
||||
from coapthon.messages.option import Option
|
||||
from coapthon.messages.request import Request
|
||||
from coapthon.messages.response import Response
|
||||
|
@ -21,7 +23,7 @@ class Tests(unittest.TestCase):
|
|||
self.current_mid = random.randint(1, 1000)
|
||||
self.server_mid = random.randint(1000, 2000)
|
||||
self.server = CoAPServer("::1", 5683, multicast=True)
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(10,))
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(1,))
|
||||
self.server_thread.start()
|
||||
self.queue = Queue()
|
||||
|
||||
|
@ -90,6 +92,7 @@ class Tests(unittest.TestCase):
|
|||
def test_not_allowed(self):
|
||||
print("TEST_NOT_ALLOWED")
|
||||
path = "/void"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -104,7 +107,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange1 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -121,7 +123,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange2 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -138,7 +139,6 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange3 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
req = Request()
|
||||
|
@ -155,11 +155,10 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
|
||||
exchange4 = (req, expected)
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_with_client([exchange1, exchange2, exchange3, exchange4])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
|
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
|
||||
from coapthon import defines
|
||||
|
||||
|
|
56
plugtest.py
56
plugtest.py
|
@ -1,9 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from queue import Queue
|
||||
import random
|
||||
import socket
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from coapthon.messages.message import Message
|
||||
from coapclient import HelperClient
|
||||
from coapthon.messages.response import Response
|
||||
|
@ -23,7 +25,7 @@ class Tests(unittest.TestCase):
|
|||
self.current_mid = random.randint(1, 1000)
|
||||
self.server_mid = random.randint(1000, 2000)
|
||||
self.server = CoAPServerPlugTest("127.0.0.1", 5683, starting_mid=self.server_mid)
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(10,))
|
||||
self.server_thread = threading.Thread(target=self.server.listen, args=(1,))
|
||||
self.server_thread.start()
|
||||
self.queue = Queue()
|
||||
|
||||
|
@ -127,6 +129,7 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_link_01(self):
|
||||
print("TD_COAP_LINK_01")
|
||||
path = "/.well-known/core"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -147,6 +150,7 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_link_02(self):
|
||||
print("TD_COAP_LINK_02")
|
||||
path = "/.well-known/core"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -168,6 +172,7 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_01(self):
|
||||
print("TD_COAP_CORE_01")
|
||||
path = "/test"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -188,8 +193,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_02(self):
|
||||
print("TD_COAP_CORE_02")
|
||||
path = "/test_post"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.POST.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -204,7 +209,7 @@ class Tests(unittest.TestCase):
|
|||
expected.code = defines.Codes.CREATED.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.location_path = "/test_post"
|
||||
expected.location_path = "test_post"
|
||||
|
||||
self.current_mid += 1
|
||||
self._test_with_client([(req, expected)])
|
||||
|
@ -212,8 +217,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_03(self):
|
||||
print("TD_COAP_CORE_03")
|
||||
path = "/test"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.PUT.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -233,7 +238,6 @@ class Tests(unittest.TestCase):
|
|||
exchange1 = (req, expected)
|
||||
|
||||
req = Request()
|
||||
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -251,7 +255,6 @@ class Tests(unittest.TestCase):
|
|||
exchange2 = (req, expected)
|
||||
|
||||
req = Request()
|
||||
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -274,8 +277,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_04(self):
|
||||
print("TD_COAP_CORE_04")
|
||||
path = "/test"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.DELETE.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -295,8 +298,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_05(self):
|
||||
print("TD_COAP_CORE_05")
|
||||
path = "/test"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["NON"]
|
||||
|
@ -316,8 +319,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_06(self):
|
||||
print("TD_COAP_CORE_06")
|
||||
path = "/test_post"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.POST.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["NON"]
|
||||
|
@ -332,7 +335,7 @@ class Tests(unittest.TestCase):
|
|||
expected.code = defines.Codes.CREATED.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.location_path = "/test_post"
|
||||
expected.location_path = "test_post"
|
||||
|
||||
self.current_mid += 1
|
||||
self._test_with_client([(req, expected)])
|
||||
|
@ -340,8 +343,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_07(self):
|
||||
print("TD_COAP_CORE_07")
|
||||
path = "/test"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.PUT.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["NON"]
|
||||
|
@ -363,8 +366,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_08(self):
|
||||
print("TD_COAP_CORE_08")
|
||||
path = "/test"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.DELETE.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["NON"]
|
||||
|
@ -384,8 +387,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_09(self):
|
||||
print("TD_COAP_CORE_09")
|
||||
path = "/separate"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -412,8 +415,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_10(self):
|
||||
print("TD_COAP_CORE_10")
|
||||
path = "/test"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -435,8 +438,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_12(self):
|
||||
print("TD_COAP_CORE_12")
|
||||
path = "/seg1/seg2/seg3"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -455,8 +458,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_core_13(self):
|
||||
print("TD_COAP_CORE_13")
|
||||
path = "/query?first=1&second=2&third=3"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -476,8 +479,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_obs_01(self):
|
||||
print("TD_COAP_OBS_01")
|
||||
path = "/obs"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -508,8 +511,8 @@ class Tests(unittest.TestCase):
|
|||
def test_td_coap_obs_03(self):
|
||||
print("TD_COAP_OBS_03")
|
||||
path = "/obs"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -566,6 +569,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (0, 1, 1024)
|
||||
expected.size2 = 1990
|
||||
|
||||
exchange1 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -586,6 +590,7 @@ class Tests(unittest.TestCase):
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (1, 0, 1024)
|
||||
expected.size2 = 1990
|
||||
|
||||
exchange2 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -632,6 +637,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
when I first awoke, I could hardly tell it from the quilt, they so blended their hues together; and it was only by
|
||||
the sense of weight and pressure that I could tell that Queequeg was hugging"""
|
||||
expected.block2 = (1, 0, 1024)
|
||||
expected.size2 = 1990
|
||||
|
||||
exchange1 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -677,6 +683,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
when I first awoke, I could hardly tell it from the quilt, they so blended their hues together; and it was only by
|
||||
the sense of weight and pressure that I could tell that Queequeg was hugging"""
|
||||
expected.block2 = (1, 0, 1024)
|
||||
expected.size2 = 1990
|
||||
|
||||
exchange1 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -702,6 +709,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (0, 1, 1024)
|
||||
expected.size2 = 1990
|
||||
|
||||
exchange1 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -722,6 +730,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.block2 = (1, 0, 1024)
|
||||
expected.size2 = 1990
|
||||
|
||||
exchange2 = (req, expected)
|
||||
self.current_mid += 1
|
||||
|
@ -796,6 +805,7 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
def test_duplicate(self):
|
||||
print("TEST_DUPLICATE")
|
||||
path = "/test"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -810,11 +820,13 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
expected.token = None
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_plugtest([(req, expected), (req, expected)])
|
||||
|
||||
def test_duplicate_not_completed(self):
|
||||
print("TEST_DUPLICATE_NOT_COMPLETED")
|
||||
path = "/long"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -835,11 +847,13 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
expected2.token = None
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_plugtest([(req, None), (req, expected), (None, expected2)])
|
||||
|
||||
def test_no_response(self):
|
||||
print("TEST_NO_RESPONSE")
|
||||
path = "/long"
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.GET.number
|
||||
req.uri_path = path
|
||||
|
@ -860,13 +874,14 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
expected2.token = None
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_plugtest([(req, expected), (None, expected2), (None, expected2), (None, expected2)])
|
||||
|
||||
def test_edit_resource(self):
|
||||
print("TEST_EDIT_RESOURCE")
|
||||
path = "/obs"
|
||||
req = Request()
|
||||
|
||||
req = Request()
|
||||
req.code = defines.Codes.POST.number
|
||||
req.uri_path = path
|
||||
req.type = defines.Types["CON"]
|
||||
|
@ -880,10 +895,11 @@ I say, looked for all the world like a strip of that same patchwork quilt. Indee
|
|||
expected.code = defines.Codes.CHANGED.number
|
||||
expected.token = None
|
||||
expected.payload = None
|
||||
expected.location_path = "/obs"
|
||||
|
||||
self.current_mid += 1
|
||||
|
||||
self._test_with_client([(req, expected)])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import getopt
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from coapthon.server.coap import CoAP
|
||||
from plugtest_resources import TestResource, SeparateResource, ObservableResource, LargeResource, LargeUpdateResource, \
|
||||
LongResource
|
||||
from coapthon.server.coap import CoAP
|
||||
|
||||
__author__ = 'Giacomo Tanganelli'
|
||||
|
||||
|
@ -11,33 +11,15 @@ __author__ = 'Giacomo Tanganelli'
|
|||
class CoAPServerPlugTest(CoAP):
|
||||
def __init__(self, host, port, multicast=False, starting_mid=None):
|
||||
CoAP.__init__(self, (host, port), multicast, starting_mid)
|
||||
|
||||
# create logger
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# create console handler and set level to debug
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.DEBUG)
|
||||
|
||||
# create formatter
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
# add formatter to ch
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
# add ch to logger
|
||||
logger.addHandler(ch)
|
||||
|
||||
self.add_resource('test/', TestResource())
|
||||
self.add_resource('separate/', SeparateResource())
|
||||
self.add_resource('seg1/', TestResource())
|
||||
self.add_resource('seg1/seg2/', TestResource())
|
||||
self.add_resource('seg1/seg2/seg3/', TestResource())
|
||||
self.add_resource('query/', TestResource())
|
||||
self.add_resource("obs/", ObservableResource(coap_server=self))
|
||||
self.add_resource("large/", LargeResource(coap_server=self))
|
||||
self.add_resource("large-update/", LargeUpdateResource(coap_server=self))
|
||||
self.add_resource('obs/', ObservableResource(coap_server=self))
|
||||
self.add_resource('large/', LargeResource(coap_server=self))
|
||||
self.add_resource('large-update/', LargeUpdateResource(coap_server=self))
|
||||
self.add_resource('long/', LongResource())
|
||||
|
||||
|
||||
|
@ -66,9 +48,7 @@ def main(argv): # pragma: no cover
|
|||
try:
|
||||
server.listen(10)
|
||||
except KeyboardInterrupt:
|
||||
print("Server Shutdown")
|
||||
server.close()
|
||||
print("Exiting...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# coding=utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
|
||||
import datetime
|
||||
|
||||
from coapthon import defines
|
||||
from coapthon.resources.resource import Resource
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
Sphinx==1.2.2
|
||||
cachetools==2.0.0
|
||||
Sphinx>=1.2.2
|
||||
cachetools>=2.0.0
|
||||
|
||||
|
|
29
setup.py
29
setup.py
|
@ -1,16 +1,33 @@
|
|||
from distutils.core import setup
|
||||
import datetime
|
||||
|
||||
setup(
|
||||
name='CoAPthon3',
|
||||
version='1.0.1',
|
||||
packages=['coapthon', 'coapthon.caching', 'coapthon.layers', 'coapthon.client', 'coapthon.server', 'coapthon.messages',
|
||||
'coapthon.forward_proxy', 'coapthon.resources', 'coapthon.reverse_proxy'],
|
||||
url='https://github.com/Tanganelli/CoAPthon3',
|
||||
version='1.0.1+fb.' + datetime.datetime.now().strftime("%Y%m%d%H%M"),
|
||||
packages=[
|
||||
'coapthon',
|
||||
'coapthon.caching',
|
||||
'coapthon.client',
|
||||
'coapthon.forward_proxy',
|
||||
'coapthon.layers',
|
||||
'coapthon.messages',
|
||||
'coapthon.resources',
|
||||
'coapthon.reverse_proxy',
|
||||
'coapthon.server',
|
||||
],
|
||||
license='MIT License',
|
||||
author='Giacomo Tanganelli',
|
||||
author_email='giacomo.tanganelli@for.unipi.it',
|
||||
download_url='https://github.com/Tanganelli/CoAPthon3/archive/1.0.1.tar.gz',
|
||||
maintainer="Bjoern Freise",
|
||||
maintainer_email="mcfreis@gmx.net",
|
||||
url="https://github.com/mcfreis/CoAPthon3",
|
||||
description='CoAPthon is a python library to the CoAP protocol.',
|
||||
scripts=['coapserver.py', 'coapclient.py', 'exampleresources.py', 'coapforwardproxy.py', 'coapreverseproxy.py'],
|
||||
scripts=[
|
||||
'coapclient.py',
|
||||
'coapforwardproxy.py',
|
||||
'coapreverseproxy.py',
|
||||
'coapserver.py',
|
||||
'exampleresources.py',
|
||||
],
|
||||
requires=['sphinx', 'cachetools']
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue