Merge branch 'master' into fix-token

This commit is contained in:
Giacomo Tanganelli 2021-01-17 12:01:13 +01:00 committed by GitHub
commit 92c0258241
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1982 additions and 5955 deletions

1
.gitignore vendored
View file

@ -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
View 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

View file

@ -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)

View file

@ -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")

View file

@ -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

View file

@ -51,7 +51,7 @@ if __name__ == '__main__':
ping_cnt = 0 # global ping cnt
print('COAP ping script')
print(('COAP ping to: %s:%s...' % (host, port)))
print('COAP ping to: %s:%s...' % (host, port))
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -69,8 +69,8 @@ if __name__ == '__main__':
msg += struct.pack("B", ping_no)
try :
print(('[0x%08X] Send ping:' % (ping_cnt), [hex(ord(c)) for c in msg]))
#Set the whole string
print('[0x%08X] Send ping:' % (ping_cnt), [hex(ord(c)) for c in msg])
# Set the whole string
s.sendto(msg, (host, port))
s.settimeout(2 + sleep_sec)
@ -81,11 +81,10 @@ if __name__ == '__main__':
# We need to check if ping peyload counter is the same in reply
status = bytes(msg)[3] == bytes(reply)[3]
print(('[0x%08X] Recv ping:' % (ping_cnt), [hex(ord(c)) for c in reply], 'ok' if status else 'fail'))
print('[0x%08X] Recv ping:' % (ping_cnt), [hex(ord(c)) for c in reply], 'ok' if status else 'fail')
except socket.error as e:
print(('Error: socket.error: ', str(e)))
#print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
print('Error: socket.error: ', str(e))
sleep(3) # Waiting to recover ;)
except socket.timeout:
print("Error: closing socket")

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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):

View file

@ -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())
])))

View file

@ -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):
"""

View file

@ -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)

View file

@ -223,17 +223,26 @@ class HelperClient(object):
:param request: the request to send
:param callback: the callback function to invoke upon response
:param timeout: the timeout of the request
:param no_response: whether to await a response from the request
:return: the response
"""
if callback is not None:
thread = threading.Thread(target=self._thread_body, args=(request, callback))
thread.start()
else:
self.protocol.send_message(request)
self.protocol.send_message(request, no_response=no_response)
if no_response:
return
try:
response = self.queue.get(block=True, timeout=timeout)
while True:
response = self.queue.get(block=True, timeout=timeout)
if response is not None:
if response.mid == request.mid:
return response
if response.type == defines.Types["NON"]:
return response
else:
return response
except Empty:
#if timeout is set
response = None

View file

@ -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

View file

@ -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)

View file

@ -1,11 +1,9 @@
import logging.config
import logging
import random
import socket
import struct
import threading
import os
from coapthon import defines
from coapthon.layers.blocklayer import BlockLayer
from coapthon.layers.cachelayer import CacheLayer
@ -88,27 +86,26 @@ class CoAP(object):
# Allow multiple copies of this program on one machine
# (not strictly needed)
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._socket.bind((defines.ALL_COAP_NODES, self.server_address[1]))
self._socket.bind(('', self.server_address[1]))
mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY)
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
self._unicast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._unicast_socket.bind(self.server_address)
else:
# Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant
if not hasattr(socket, 'IPPROTO_IPV6'):
socket.IPPROTO_IPV6 = 41
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# Allow multiple copies of this program on one machine
# (not strictly needed)
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1]))
self._socket.bind(('', self.server_address[1]))
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
mreq = group_bin + struct.pack('@I', 0)
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._unicast_socket.bind(self.server_address)
else:
if addrinfo[0] == socket.AF_INET: # IPv4
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -140,7 +137,7 @@ class CoAP(object):
except socket.timeout:
continue
try:
#Start a new thread not to block other requests
# Start a new thread not to block other requests
args = ((data, client_address), )
t = threading.Thread(target=self.receive_datagram, args=args)
t.daemon = True
@ -159,7 +156,7 @@ class CoAP(object):
self.stopped.set()
for event in self.to_be_stopped:
event.set()
self._socket.close()
# self._socket.close()
def receive_datagram(self, args):
"""
@ -189,20 +186,20 @@ class CoAP(object):
rst.code = message
self.send_datagram(rst)
return
logger.debug("receive_datagram - " + str(message))
logger.info("receive_datagram - " + str(message))
if isinstance(message, Request):
transaction = self._messageLayer.receive_request(message)
if transaction.request.duplicated and transaction.completed:
logger.debug("message duplicated,transaction completed")
logger.debug("message duplicated, transaction completed")
transaction = self._observeLayer.send_response(transaction)
transaction = self._blockLayer.send_response(transaction)
transaction = self._messageLayer.send_response(transaction)
self.send_datagram(transaction.response)
return
elif transaction.request.duplicated and not transaction.completed:
logger.debug("message duplicated,transaction NOT completed")
logger.debug("message duplicated, transaction NOT completed")
self._send_ack(transaction)
return
@ -269,7 +266,7 @@ class CoAP(object):
"""
if not self.stopped.isSet():
host, port = message.destination
logger.debug("send_datagram - " + str(message))
logger.info("send_datagram - " + str(message))
serializer = Serializer()
message = serializer.serialize(message)

View file

@ -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):

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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")

View file

@ -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:

View file

@ -105,10 +105,12 @@ class ResourceLayer(object):
if resource.etag is not None:
transaction.response.etag = resource.etag
transaction.response.location_path = resource.path
if transaction.response.code == defines.Codes.CREATED.number:
# Only on CREATED according to RFC 7252 Chapter 5.8.2 POST
transaction.response.location_path = resource.path
if resource.location_query is not None and len(resource.location_query) > 0:
transaction.response.location_query = resource.location_query
if resource.location_query is not None and len(resource.location_query) > 0:
transaction.response.location_query = resource.location_query
transaction.response.payload = None
@ -229,7 +231,7 @@ class ResourceLayer(object):
lp = path
parent_resource = self._parent.root[imax]
if parent_resource.allow_children:
return self.add_resource(transaction, parent_resource, lp)
return self.add_resource(transaction, parent_resource, lp)
else:
transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
return transaction

View file

@ -1,5 +1,9 @@
from coapthon.utils import parse_blockwise
# -*- coding: utf-8 -*-
import binascii
from coapthon import defines
from coapthon import utils
from coapthon.messages.option import Option
__author__ = 'Giacomo Tanganelli'
@ -124,6 +128,7 @@ class Message(object):
return
if not isinstance(value, bytes):
value = bytes(value)
if len(value) > 256:
raise AttributeError
self._token = value
@ -454,7 +459,7 @@ class Message(object):
for e in etag:
option = Option()
option.number = defines.OptionRegistry.ETAG.number
if not isinstance(e, bytes):
if not isinstance(e, bytes):
e = bytes(e, "utf-8")
option.value = e
self.add_option(option)
@ -547,7 +552,7 @@ class Message(object):
value = None
for option in self.options:
if option.number == defines.OptionRegistry.BLOCK1.number:
value = parse_blockwise(option.value)
value = utils.parse_blockwise(option.value)
return value
@block1.setter
@ -599,7 +604,7 @@ class Message(object):
value = None
for option in self.options:
if option.number == defines.OptionRegistry.BLOCK2.number:
value = parse_blockwise(option.value)
value = utils.parse_blockwise(option.value)
return value
@block2.setter
@ -641,6 +646,44 @@ class Message(object):
"""
self.del_option_by_number(defines.OptionRegistry.BLOCK2.number)
@property
def size1(self):
value = None
for option in self.options:
if option.number == defines.OptionRegistry.SIZE1.number:
value = option.value if option.value is not None else 0
return value
@size1.setter
def size1(self, value):
option = Option()
option.number = defines.OptionRegistry.SIZE1.number
option.value = value
self.add_option(option)
@size1.deleter
def size1(self):
self.del_option_by_number(defines.OptionRegistry.SIZE1.number)
@property
def size2(self):
value = None
for option in self.options:
if option.number == defines.OptionRegistry.SIZE2.number:
value = option.value if option.value is not None else 0
return value
@size2.setter
def size2(self, value):
option = Option()
option.number = defines.OptionRegistry.SIZE2.number
option.value = value
self.add_option(option)
@size2.deleter
def size2(self):
self.del_option_by_number(defines.OptionRegistry.SIZE2.number)
@property
def line_print(self):
"""
@ -653,11 +696,16 @@ class Message(object):
if self._code is None:
self._code = defines.Codes.EMPTY.number
token = binascii.hexlify(self._token).decode("utf-8") if self._token is not None else str(None)
msg = "From {source}, To {destination}, {type}-{mid}, {code}-{token}, ["\
.format(source=self._source, destination=self._destination, type=inv_types[self._type], mid=self._mid,
code=defines.Codes.LIST[self._code].name, token=self._token)
code=defines.Codes.LIST[self._code].name, token=token)
for opt in self._options:
msg += "{name}: {value}, ".format(name=opt.name, value=opt.value)
if 'Block' in opt.name:
msg += "{name}: {value}, ".format(name=opt.name, value=utils.parse_blockwise(opt.value))
else:
msg += "{name}: {value}, ".format(name=opt.name, value=opt.value)
msg += "]"
if self.payload is not None:
if isinstance(self.payload, dict):
@ -685,9 +733,9 @@ class Message(object):
msg += "MID: " + str(self._mid) + "\n"
if self._code is None:
self._code = 0
token = binascii.hexlify(self._token).decode("utf-8") if self._token is not None else str(None)
msg += "Code: " + str(defines.Codes.LIST[self._code].name) + "\n"
msg += "Token: " + str(self._token) + "\n"
msg += "Token: " + token + "\n"
for opt in self._options:
msg += str(opt)
msg += "Payload: " + "\n"

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -1,6 +1,7 @@
import logging
import struct
import ctypes
from coapthon.messages.request import Request
from coapthon.messages.response import Response
from coapthon.messages.option import Option
@ -67,6 +68,7 @@ class Serializer(object):
# the first 4 bits of the byte represent the option delta
# delta = self._reader.read(4).uint
num, option_length, pos = Serializer.read_option_value_len_from_byte(next_byte, pos, values)
logger.debug("option value (delta): %d len: %d", num, option_length)
current_option += num
# read option
try:
@ -78,8 +80,7 @@ class Serializer(object):
else:
# If the non-critical option is unknown
# (vendor-specific, proprietary) - just skip it
#log.err("unrecognized option %d" % current_option)
pass
logger.warning("unrecognized option %d", current_option)
else:
if option_length == 0:
value = None
@ -110,13 +111,17 @@ class Serializer(object):
raise AttributeError("Packet length %s, pos %s" % (length_packet, pos))
message.payload = ""
payload = values[pos:]
try:
if message.payload_type == defines.Content_types["application/octet-stream"]:
message.payload = payload
else:
if hasattr(message, 'payload_type') and message.payload_type in [
defines.Content_types["application/octet-stream"],
defines.Content_types["application/exi"],
defines.Content_types["application/cbor"]
]:
message.payload = payload
else:
try:
message.payload = payload.decode("utf-8")
except AttributeError:
message.payload = payload.decode("utf-8")
except AttributeError:
message.payload = payload
pos += len(payload)
return message
@ -124,6 +129,9 @@ class Serializer(object):
return defines.Codes.BAD_REQUEST.number
except struct.error:
return defines.Codes.BAD_REQUEST.number
except UnicodeDecodeError as e:
logger.debug(e)
return defines.Codes.BAD_REQUEST.number
@staticmethod
def serialize(message):
@ -137,7 +145,7 @@ class Serializer(object):
"""
fmt = "!BBH"
if message.token is None or message.token == "":
if message.token is None:
tkl = 0
else:
tkl = len(message.token)
@ -195,7 +203,6 @@ class Serializer(object):
elif opt_type == defines.STRING:
fmt += str(len(bytes(option.value, "utf-8"))) + "s"
values.append(bytes(option.value, "utf-8"))
else: # OPAQUE
for b in option.value:
fmt += "B"
@ -220,9 +227,6 @@ class Serializer(object):
else:
fmt += str(len(bytes(payload, "utf-8"))) + "s"
values.append(bytes(payload, "utf-8"))
# for b in str(payload):
# fmt += "c"
# values.append(bytes(b, "utf-8"))
datagram = None
if values[1] is None:
@ -260,28 +264,6 @@ class Serializer(object):
"""
return defines.RESPONSE_CODE_LOWER_BOUND <= code <= defines.RESPONSE_CODE_UPPER_BOUND
@staticmethod
def read_option_value_from_nibble(nibble, pos, values):
"""
Calculates the value used in the extended option fields.
:param nibble: the 4-bit option header value.
:return: the value calculated from the nibble and the extended option value.
"""
if nibble <= 12:
return nibble, pos
elif nibble == 13:
tmp = struct.unpack("!B", values[pos].to_bytes(1, "big"))[0] + 13
pos += 1
return tmp, pos
elif nibble == 14:
s = struct.Struct("!H")
tmp = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269
pos += 2
return tmp, pos
else:
raise AttributeError("Unsupported option nibble " + str(nibble))
@staticmethod
def read_option_value_len_from_byte(byte, pos, values):
"""
@ -301,7 +283,7 @@ class Serializer(object):
pos += 1
elif h_nibble == 14:
s = struct.Struct("!H")
value = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269
value = s.unpack_from(values[pos:pos+2])[0] + 269
pos += 2
else:
raise AttributeError("Unsupported option number nibble " + str(h_nibble))
@ -312,7 +294,8 @@ class Serializer(object):
length = struct.unpack("!B", values[pos].to_bytes(1, "big"))[0] + 13
pos += 1
elif l_nibble == 14:
length = s.unpack_from(values[pos:].to_bytes(2, "big"))[0] + 269
s = struct.Struct("!H")
length = s.unpack_from(values[pos:pos+2])[0] + 269
pos += 2
else:
raise AttributeError("Unsupported option length nibble " + str(l_nibble))
@ -321,7 +304,7 @@ class Serializer(object):
@staticmethod
def convert_to_raw(number, value, length):
"""
Get the value of an option as a ByteArray.
Get the value of an option as bytes.
:param number: the option number
:param value: the option value
@ -348,11 +331,11 @@ class Serializer(object):
if isinstance(value, str):
value = str(value)
if isinstance(value, str):
return bytearray(value, "utf-8")
return bytes(value, "utf-8")
elif isinstance(value, int):
return value
else:
return bytearray(value)
return bytes(value)
@staticmethod
def as_sorted_list(options):

View file

@ -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

View file

@ -1,9 +1,9 @@
import logging.config
import os
import logging
import random
import socket
import struct
import threading
import collections
from coapthon import defines
from coapthon.layers.blocklayer import BlockLayer
@ -16,18 +16,13 @@ from coapthon.messages.request import Request
from coapthon.messages.response import Response
from coapthon.resources.resource import Resource
from coapthon.serializer import Serializer
from coapthon.utils import Tree, create_logging
import collections
from coapthon.utils import Tree
__author__ = 'Giacomo Tanganelli'
if not os.path.isfile("logging.conf"):
create_logging()
logger = logging.getLogger(__name__)
logging.config.fileConfig("logging.conf", disable_existing_loggers=False)
class CoAP(object):
@ -88,20 +83,21 @@ class CoAP(object):
mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY)
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
else:
# Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant
if not hasattr(socket, 'IPPROTO_IPV6'):
socket.IPPROTO_IPV6 = 41
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# Allow multiple copies of this program on one machine
# (not strictly needed)
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1]))
self._socket.bind(('', self.server_address[1]))
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
mreq = group_bin + struct.pack('@I', 0)
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._unicast_socket.bind(self.server_address)
else:
if addrinfo[0] == socket.AF_INET: # IPv4
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -154,7 +150,7 @@ class CoAP(object):
self.send_datagram(rst)
continue
logger.debug("receive_datagram - " + str(message))
logger.info("receive_datagram - " + str(message))
if isinstance(message, Request):
transaction = self._messageLayer.receive_request(message)
if transaction.request.duplicated and transaction.completed:
@ -246,7 +242,7 @@ class CoAP(object):
"""
if not self.stopped.isSet():
host, port = message.destination
logger.debug("send_datagram - " + str(message))
logger.info("send_datagram - " + str(message))
serializer = Serializer()
message = serializer.serialize(message)
self._socket.sendto(message, (host, port))
@ -382,10 +378,11 @@ class CoAP(object):
ack = Message()
ack.type = defines.Types['ACK']
# TODO handle mutex on transaction
if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]:
ack = self._messageLayer.send_empty(transaction, transaction.request, ack)
self.send_datagram(ack)
with transaction:
if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]:
ack = self._messageLayer.send_empty(transaction, transaction.request, ack)
if ack.type is not None and ack.mid is not None:
self.send_datagram(ack)
def notify(self, resource):
"""

View file

@ -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)

View file

@ -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

View file

@ -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]

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -1,3 +1,5 @@
#!/usr/bin/env python
import time
from coapthon import defines

View file

@ -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()

View file

@ -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__":

View file

@ -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

View file

@ -1,3 +1,3 @@
Sphinx==1.2.2
cachetools==2.0.0
Sphinx>=1.2.2
cachetools>=2.0.0

View file

@ -1,16 +1,33 @@
from distutils.core import setup
import datetime
setup(
name='CoAPthon3',
version='1.0.1',
packages=['coapthon', 'coapthon.caching', 'coapthon.layers', 'coapthon.client', 'coapthon.server', 'coapthon.messages',
'coapthon.forward_proxy', 'coapthon.resources', 'coapthon.reverse_proxy'],
url='https://github.com/Tanganelli/CoAPthon3',
version='1.0.1+fb.' + datetime.datetime.now().strftime("%Y%m%d%H%M"),
packages=[
'coapthon',
'coapthon.caching',
'coapthon.client',
'coapthon.forward_proxy',
'coapthon.layers',
'coapthon.messages',
'coapthon.resources',
'coapthon.reverse_proxy',
'coapthon.server',
],
license='MIT License',
author='Giacomo Tanganelli',
author_email='giacomo.tanganelli@for.unipi.it',
download_url='https://github.com/Tanganelli/CoAPthon3/archive/1.0.1.tar.gz',
description='CoAPthon is a python library to the CoAP protocol. ',
scripts=['coapserver.py', 'coapclient.py', 'exampleresources.py', 'coapforwardproxy.py', 'coapreverseproxy.py'],
maintainer="Bjoern Freise",
maintainer_email="mcfreis@gmx.net",
url="https://github.com/mcfreis/CoAPthon3",
description='CoAPthon is a python library to the CoAP protocol.',
scripts=[
'coapclient.py',
'coapforwardproxy.py',
'coapreverseproxy.py',
'coapserver.py',
'exampleresources.py',
],
requires=['sphinx', 'cachetools']
)