[B] send_ack in transaction lock and only if type nad message id are valid
This commit is contained in:
parent
5bebe167a9
commit
b13f8a5496
14 changed files with 5 additions and 4155 deletions
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from cachetools import LRUCache
|
|
||||||
from coapthon.caching.coapcache import CoapCache
|
|
||||||
|
|
||||||
__author__ = 'Emilio Vallati'
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class CoapLRUCache(CoapCache):
|
|
||||||
def __init__(self, max_dim):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param max_dim:
|
|
||||||
"""
|
|
||||||
self.cache = LRUCache(maxsize=max_dim)
|
|
||||||
|
|
||||||
def update(self, key, element):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param key:
|
|
||||||
:param element:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
logger.debug("updating cache, key: %s, element: %s", \
|
|
||||||
key.hashkey, element)
|
|
||||||
self.cache.update([(key.hashkey, element)])
|
|
||||||
|
|
||||||
def get(self, key):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param key:
|
|
||||||
:return: CacheElement
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
response = self.cache[key.hashkey]
|
|
||||||
except KeyError:
|
|
||||||
logger.debug("problem here", exc_info=1)
|
|
||||||
response = None
|
|
||||||
return response
|
|
||||||
|
|
||||||
def is_full(self):
|
|
||||||
"""
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if self.cache.currsize == self.cache.maxsize:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_empty(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.cache.currsize == 0:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
msg = []
|
|
||||||
for e in self.cache.values():
|
|
||||||
msg.append(str(e))
|
|
||||||
return "Cache Size: {sz}\n" + "\n".join(msg)
|
|
||||||
|
|
||||||
def debug_print(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return ("size = %s\n%s" % (
|
|
||||||
self.cache.currsize,
|
|
||||||
'\n'.join([
|
|
||||||
( "element.max age %s\n"\
|
|
||||||
"element.uri %s\n"\
|
|
||||||
"element.freshness %s" ) % (
|
|
||||||
element.max_age,
|
|
||||||
element.uri,
|
|
||||||
element.freshness )
|
|
||||||
for key, element
|
|
||||||
in list(self.cache.items())
|
|
||||||
])))
|
|
|
@ -1,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)
|
|
|
@ -1,236 +0,0 @@
|
||||||
import random
|
|
||||||
from multiprocessing import Queue
|
|
||||||
from Queue import Empty
|
|
||||||
import threading
|
|
||||||
from coapthon.messages.message import Message
|
|
||||||
from coapthon import defines
|
|
||||||
from coapthon.client.coap import CoAP
|
|
||||||
from coapthon.messages.request import Request
|
|
||||||
from coapthon.utils import generate_random_token
|
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
|
||||||
|
|
||||||
|
|
||||||
class HelperClient(object):
|
|
||||||
"""
|
|
||||||
Helper Client class to perform requests to remote servers in a simplified way.
|
|
||||||
"""
|
|
||||||
def __init__(self, server, sock=None, cb_ignore_read_exception=None, cb_ignore_write_exception=None):
|
|
||||||
"""
|
|
||||||
Initialize a client to perform request to a server.
|
|
||||||
|
|
||||||
:param server: the remote CoAP server
|
|
||||||
:param sock: if a socket has been created externally, it can be used directly
|
|
||||||
:param cb_ignore_read_exception: Callback function to handle exception raised during the socket read operation
|
|
||||||
:param cb_ignore_write_exception: Callback function to handle exception raised during the socket write operation
|
|
||||||
"""
|
|
||||||
self.server = server
|
|
||||||
self.protocol = CoAP(self.server, random.randint(1, 65535), self._wait_response, sock=sock,
|
|
||||||
cb_ignore_read_exception=cb_ignore_read_exception, cb_ignore_write_exception=cb_ignore_write_exception)
|
|
||||||
self.queue = Queue()
|
|
||||||
|
|
||||||
def _wait_response(self, message):
|
|
||||||
"""
|
|
||||||
Private function to get responses from the server.
|
|
||||||
|
|
||||||
:param message: the received message
|
|
||||||
"""
|
|
||||||
if message is None or message.code != defines.Codes.CONTINUE.number:
|
|
||||||
self.queue.put(message)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""
|
|
||||||
Stop the client.
|
|
||||||
"""
|
|
||||||
self.protocol.close()
|
|
||||||
self.queue.put(None)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Close the client.
|
|
||||||
"""
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
def _thread_body(self, request, callback):
|
|
||||||
"""
|
|
||||||
Private function. Send a request, wait for response and call the callback function.
|
|
||||||
|
|
||||||
:param request: the request to send
|
|
||||||
:param callback: the callback function
|
|
||||||
"""
|
|
||||||
self.protocol.send_message(request)
|
|
||||||
while not self.protocol.stopped.isSet():
|
|
||||||
response = self.queue.get(block=True)
|
|
||||||
callback(response)
|
|
||||||
|
|
||||||
def cancel_observing(self, response, send_rst): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Delete observing on the remote server.
|
|
||||||
|
|
||||||
:param response: the last received response
|
|
||||||
:param send_rst: if explicitly send RST message
|
|
||||||
:type send_rst: bool
|
|
||||||
"""
|
|
||||||
if send_rst:
|
|
||||||
message = Message()
|
|
||||||
message.destination = self.server
|
|
||||||
message.code = defines.Codes.EMPTY.number
|
|
||||||
message.type = defines.Types["RST"]
|
|
||||||
message.token = response.token
|
|
||||||
message.mid = response.mid
|
|
||||||
self.protocol.send_message(message)
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
def get(self, path, callback=None, timeout=None, **kwargs): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Perform a GET on a certain path.
|
|
||||||
|
|
||||||
:param path: the path
|
|
||||||
:param callback: the callback function to invoke upon response
|
|
||||||
:param timeout: the timeout of the request
|
|
||||||
:return: the response
|
|
||||||
"""
|
|
||||||
request = self.mk_request(defines.Codes.GET, path)
|
|
||||||
request.token = generate_random_token(2)
|
|
||||||
|
|
||||||
for k, v in kwargs.iteritems():
|
|
||||||
if hasattr(request, k):
|
|
||||||
setattr(request, k, v)
|
|
||||||
|
|
||||||
return self.send_request(request, callback, timeout)
|
|
||||||
|
|
||||||
def observe(self, path, callback, timeout=None, **kwargs): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Perform a GET with observe on a certain path.
|
|
||||||
|
|
||||||
:param path: the path
|
|
||||||
:param callback: the callback function to invoke upon notifications
|
|
||||||
:param timeout: the timeout of the request
|
|
||||||
:return: the response to the observe request
|
|
||||||
"""
|
|
||||||
request = self.mk_request(defines.Codes.GET, path)
|
|
||||||
request.observe = 0
|
|
||||||
|
|
||||||
for k, v in kwargs.iteritems():
|
|
||||||
if hasattr(request, k):
|
|
||||||
setattr(request, k, v)
|
|
||||||
|
|
||||||
return self.send_request(request, callback, timeout)
|
|
||||||
|
|
||||||
def delete(self, path, callback=None, timeout=None, **kwargs): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Perform a DELETE on a certain path.
|
|
||||||
|
|
||||||
:param path: the path
|
|
||||||
:param callback: the callback function to invoke upon response
|
|
||||||
:param timeout: the timeout of the request
|
|
||||||
:return: the response
|
|
||||||
"""
|
|
||||||
request = self.mk_request(defines.Codes.DELETE, path)
|
|
||||||
|
|
||||||
for k, v in kwargs.iteritems():
|
|
||||||
if hasattr(request, k):
|
|
||||||
setattr(request, k, v)
|
|
||||||
|
|
||||||
return self.send_request(request, callback, timeout)
|
|
||||||
|
|
||||||
def post(self, path, payload, callback=None, timeout=None, **kwargs): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Perform a POST on a certain path.
|
|
||||||
|
|
||||||
:param path: the path
|
|
||||||
:param payload: the request payload
|
|
||||||
:param callback: the callback function to invoke upon response
|
|
||||||
:param timeout: the timeout of the request
|
|
||||||
:return: the response
|
|
||||||
"""
|
|
||||||
request = self.mk_request(defines.Codes.POST, path)
|
|
||||||
request.token = generate_random_token(2)
|
|
||||||
request.payload = payload
|
|
||||||
|
|
||||||
for k, v in kwargs.iteritems():
|
|
||||||
if hasattr(request, k):
|
|
||||||
setattr(request, k, v)
|
|
||||||
|
|
||||||
return self.send_request(request, callback, timeout)
|
|
||||||
|
|
||||||
def put(self, path, payload, callback=None, timeout=None, **kwargs): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Perform a PUT on a certain path.
|
|
||||||
|
|
||||||
:param path: the path
|
|
||||||
:param payload: the request payload
|
|
||||||
:param callback: the callback function to invoke upon response
|
|
||||||
:param timeout: the timeout of the request
|
|
||||||
:return: the response
|
|
||||||
"""
|
|
||||||
request = self.mk_request(defines.Codes.PUT, path)
|
|
||||||
request.token = generate_random_token(2)
|
|
||||||
request.payload = payload
|
|
||||||
|
|
||||||
for k, v in kwargs.iteritems():
|
|
||||||
if hasattr(request, k):
|
|
||||||
setattr(request, k, v)
|
|
||||||
|
|
||||||
return self.send_request(request, callback, timeout)
|
|
||||||
|
|
||||||
def discover(self, callback=None, timeout=None, **kwargs): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Perform a Discover request on the server.
|
|
||||||
|
|
||||||
:param callback: the callback function to invoke upon response
|
|
||||||
:param timeout: the timeout of the request
|
|
||||||
:return: the response
|
|
||||||
"""
|
|
||||||
request = self.mk_request(defines.Codes.GET, defines.DISCOVERY_URL)
|
|
||||||
|
|
||||||
for k, v in kwargs.iteritems():
|
|
||||||
if hasattr(request, k):
|
|
||||||
setattr(request, k, v)
|
|
||||||
|
|
||||||
return self.send_request(request, callback, timeout)
|
|
||||||
|
|
||||||
def send_request(self, request, callback=None, timeout=None): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Send a request to the remote server.
|
|
||||||
|
|
||||||
:param request: the request to send
|
|
||||||
:param callback: the callback function to invoke upon response
|
|
||||||
:param timeout: the timeout of the request
|
|
||||||
:return: the response
|
|
||||||
"""
|
|
||||||
if callback is not None:
|
|
||||||
thread = threading.Thread(target=self._thread_body, args=(request, callback))
|
|
||||||
thread.start()
|
|
||||||
else:
|
|
||||||
self.protocol.send_message(request)
|
|
||||||
try:
|
|
||||||
response = self.queue.get(block=True, timeout=timeout)
|
|
||||||
except Empty:
|
|
||||||
#if timeout is set
|
|
||||||
response = None
|
|
||||||
return response
|
|
||||||
|
|
||||||
def send_empty(self, empty): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Send empty message.
|
|
||||||
|
|
||||||
:param empty: the empty message
|
|
||||||
"""
|
|
||||||
self.protocol.send_message(empty)
|
|
||||||
|
|
||||||
def mk_request(self, method, path):
|
|
||||||
"""
|
|
||||||
Create a request.
|
|
||||||
|
|
||||||
:param method: the CoAP method
|
|
||||||
:param path: the path of the request
|
|
||||||
:return: the request
|
|
||||||
"""
|
|
||||||
request = Request()
|
|
||||||
request.destination = self.server
|
|
||||||
request.code = method.number
|
|
||||||
request.uri_path = path
|
|
||||||
return request
|
|
||||||
|
|
||||||
|
|
|
@ -1,283 +0,0 @@
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
|
||||||
from coapthon.client.helperclient import HelperClient
|
|
||||||
from coapthon.utils import parse_uri
|
|
||||||
from coapthon.defines import Codes, DEFAULT_HC_PATH, HC_PROXY_DEFAULT_PORT, COAP_DEFAULT_PORT, LOCALHOST, BAD_REQUEST, \
|
|
||||||
NOT_IMPLEMENTED, CoAP_HTTP
|
|
||||||
from coapthon.defines import COAP_PREFACE
|
|
||||||
from urlparse import urlparse
|
|
||||||
|
|
||||||
__author__ = "Marco Ieni, Davide Foti"
|
|
||||||
__email__ = "marcoieni94@gmail.com, davidefoti.uni@gmail.com"
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
hc_path = DEFAULT_HC_PATH
|
|
||||||
|
|
||||||
""" the class that realizes the HTTP-CoAP Proxy """
|
|
||||||
|
|
||||||
|
|
||||||
class HCProxy:
|
|
||||||
"""
|
|
||||||
This program implements an HTTP-CoAP Proxy without using external libraries.
|
|
||||||
It is assumed that URI is formatted like this:
|
|
||||||
http://hc_proxy_ip:proxy_port/hc/coap://server_coap_ip:server_coap_port/resource
|
|
||||||
You can run this program passing the parameters from the command line or you can use the HCProxy class in your own
|
|
||||||
project.
|
|
||||||
"""
|
|
||||||
def __init__(self, path=DEFAULT_HC_PATH, hc_port=HC_PROXY_DEFAULT_PORT, ip=LOCALHOST,
|
|
||||||
coap_port=COAP_DEFAULT_PORT):
|
|
||||||
"""
|
|
||||||
Initialize the HC proxy.
|
|
||||||
|
|
||||||
:param path: the path of the hc_proxy server
|
|
||||||
:param hc_port: the port of the hc_proxy server
|
|
||||||
:param ip: the ip of the hc_proxy server
|
|
||||||
:param coap_port: the coap server port you want to reach
|
|
||||||
"""
|
|
||||||
global hc_path
|
|
||||||
hc_path = HCProxy.get_formatted_path(path)
|
|
||||||
self.hc_port = hc_port
|
|
||||||
self.ip = ip
|
|
||||||
self.coap_port = coap_port
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""
|
|
||||||
Start the proxy.
|
|
||||||
"""
|
|
||||||
server_address = (self.ip, self.hc_port)
|
|
||||||
hc_proxy = HTTPServer(server_address, HCProxyHandler)
|
|
||||||
logger.info('Starting HTTP-CoAP Proxy...')
|
|
||||||
hc_proxy.serve_forever() # the server listen to http://ip:hc_port/path
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_formatted_path(path):
|
|
||||||
"""
|
|
||||||
Uniform the path string
|
|
||||||
|
|
||||||
:param path: the path
|
|
||||||
:return: the uniform path
|
|
||||||
"""
|
|
||||||
if path[0] != '/':
|
|
||||||
path = '/' + path
|
|
||||||
if path[-1] != '/':
|
|
||||||
path = '{0}/'.format(path)
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
class CoapUri: # this class takes the URI from the HTTP URI
|
|
||||||
""" Class that can manage and inbox the CoAP URI """
|
|
||||||
def __init__(self, coap_uri):
|
|
||||||
self.uri = coap_uri
|
|
||||||
self.host, self.port, self.path = parse_uri(coap_uri)
|
|
||||||
|
|
||||||
def get_uri_as_list(self):
|
|
||||||
"""
|
|
||||||
Split the uri into <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
|
|
||||||
|
|
||||||
:return: the split uri
|
|
||||||
"""
|
|
||||||
return urlparse(self.uri)
|
|
||||||
|
|
||||||
def get_payload(self):
|
|
||||||
"""
|
|
||||||
Return the query string of the uri.
|
|
||||||
|
|
||||||
:return: the query string as a list
|
|
||||||
"""
|
|
||||||
temp = self.get_uri_as_list()
|
|
||||||
query_string = temp[4]
|
|
||||||
if query_string == "":
|
|
||||||
return None # Bad request error code
|
|
||||||
query_string_as_list = str.split(query_string, "=")
|
|
||||||
return query_string_as_list[1]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.uri
|
|
||||||
|
|
||||||
|
|
||||||
class HCProxyHandler(BaseHTTPRequestHandler):
|
|
||||||
""" It maps the requests from HTTP to CoAP """
|
|
||||||
coap_uri = None
|
|
||||||
client = None
|
|
||||||
|
|
||||||
def set_coap_uri(self):
|
|
||||||
"""
|
|
||||||
Create a CoAP Uri
|
|
||||||
"""
|
|
||||||
self.coap_uri = CoapUri(self.path[len(hc_path):])
|
|
||||||
|
|
||||||
def do_initial_operations(self):
|
|
||||||
"""
|
|
||||||
Setup the client for interact with remote server
|
|
||||||
"""
|
|
||||||
if not self.request_hc_path_corresponds():
|
|
||||||
# the http URI of the request is not the same of the one specified by the admin for the hc proxy,
|
|
||||||
# so I do not answer
|
|
||||||
# For example the admin setup the http proxy URI like: "http://127.0.0.1:8080:/my_hc_path/" and the URI of
|
|
||||||
# the requests asks for "http://127.0.0.1:8080:/another_hc_path/"
|
|
||||||
return
|
|
||||||
self.set_coap_uri()
|
|
||||||
self.client = HelperClient(server=(self.coap_uri.host, self.coap_uri.port))
|
|
||||||
|
|
||||||
def do_GET(self):
|
|
||||||
"""
|
|
||||||
Perform a GET request
|
|
||||||
"""
|
|
||||||
self.do_initial_operations()
|
|
||||||
coap_response = self.client.get(self.coap_uri.path)
|
|
||||||
self.client.stop()
|
|
||||||
logger.info("Server response: %s", coap_response.pretty_print())
|
|
||||||
self.set_http_response(coap_response)
|
|
||||||
|
|
||||||
def do_HEAD(self):
|
|
||||||
"""
|
|
||||||
Perform a HEAD request
|
|
||||||
"""
|
|
||||||
self.do_initial_operations()
|
|
||||||
# the HEAD method is not present in CoAP, so we treat it
|
|
||||||
# like if it was a GET and then we exclude the body from the response
|
|
||||||
# with send_body=False we say that we do not need the body, because it is a HEAD request
|
|
||||||
coap_response = self.client.get(self.coap_uri.path)
|
|
||||||
self.client.stop()
|
|
||||||
logger.info("Server response: %s", coap_response.pretty_print())
|
|
||||||
self.set_http_header(coap_response)
|
|
||||||
|
|
||||||
def do_POST(self):
|
|
||||||
"""
|
|
||||||
Perform a POST request
|
|
||||||
"""
|
|
||||||
# Doesn't do anything with posted data
|
|
||||||
# print "uri: ", self.client_address, self.path
|
|
||||||
self.do_initial_operations()
|
|
||||||
payload = self.coap_uri.get_payload()
|
|
||||||
if payload is None:
|
|
||||||
logger.error("BAD POST REQUEST")
|
|
||||||
self.send_error(BAD_REQUEST)
|
|
||||||
return
|
|
||||||
coap_response = self.client.post(self.coap_uri.path, payload)
|
|
||||||
self.client.stop()
|
|
||||||
logger.info("Server response: %s", coap_response.pretty_print())
|
|
||||||
self.set_http_response(coap_response)
|
|
||||||
|
|
||||||
def do_PUT(self):
|
|
||||||
"""
|
|
||||||
Perform a PUT request
|
|
||||||
"""
|
|
||||||
self.do_initial_operations()
|
|
||||||
payload = self.coap_uri.get_payload()
|
|
||||||
if payload is None:
|
|
||||||
logger.error("BAD PUT REQUEST")
|
|
||||||
self.send_error(BAD_REQUEST)
|
|
||||||
return
|
|
||||||
logger.debug(payload)
|
|
||||||
coap_response = self.client.put(self.coap_uri.path, payload)
|
|
||||||
self.client.stop()
|
|
||||||
logger.debug("Server response: %s", coap_response.pretty_print())
|
|
||||||
self.set_http_response(coap_response)
|
|
||||||
|
|
||||||
def do_DELETE(self):
|
|
||||||
"""
|
|
||||||
Perform a DELETE request
|
|
||||||
"""
|
|
||||||
self.do_initial_operations()
|
|
||||||
coap_response = self.client.delete(self.coap_uri.path)
|
|
||||||
self.client.stop()
|
|
||||||
logger.debug("Server response: %s", coap_response.pretty_print())
|
|
||||||
self.set_http_response(coap_response)
|
|
||||||
|
|
||||||
def do_CONNECT(self):
|
|
||||||
"""
|
|
||||||
Perform a CONNECT request. Reply with error, not implemented in CoAP
|
|
||||||
"""
|
|
||||||
self.send_error(NOT_IMPLEMENTED)
|
|
||||||
|
|
||||||
def do_OPTIONS(self):
|
|
||||||
"""
|
|
||||||
Perform a OPTIONS request. Reply with error, not implemented in CoAP
|
|
||||||
"""
|
|
||||||
self.send_error(NOT_IMPLEMENTED)
|
|
||||||
|
|
||||||
def do_TRACE(self):
|
|
||||||
"""
|
|
||||||
Perform a TRACE request. Reply with error, not implemented in CoAP
|
|
||||||
"""
|
|
||||||
self.send_error(NOT_IMPLEMENTED)
|
|
||||||
|
|
||||||
def request_hc_path_corresponds(self):
|
|
||||||
"""
|
|
||||||
Tells if the hc path of the request corresponds to that specified by the admin
|
|
||||||
|
|
||||||
:return: a boolean that says if it corresponds or not
|
|
||||||
"""
|
|
||||||
uri_path = self.path.split(COAP_PREFACE)
|
|
||||||
request_hc_path = uri_path[0]
|
|
||||||
logger.debug("HCPATH: %s", hc_path)
|
|
||||||
# print HC_PATH
|
|
||||||
logger.debug("URI: %s", request_hc_path)
|
|
||||||
if hc_path != request_hc_path:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def set_http_header(self, coap_response):
|
|
||||||
"""
|
|
||||||
Sets http headers.
|
|
||||||
|
|
||||||
:param coap_response: the coap response
|
|
||||||
"""
|
|
||||||
logger.debug(
|
|
||||||
("Server: %s\n"\
|
|
||||||
"codice risposta: %s\n"\
|
|
||||||
"PROXED: %s\n"\
|
|
||||||
"payload risposta: %s"),
|
|
||||||
coap_response.source,
|
|
||||||
coap_response.code,
|
|
||||||
CoAP_HTTP[Codes.LIST[coap_response.code].name],
|
|
||||||
coap_response.payload)
|
|
||||||
self.send_response(int(CoAP_HTTP[Codes.LIST[coap_response.code].name]))
|
|
||||||
self.send_header('Content-type', 'text/html')
|
|
||||||
self.end_headers()
|
|
||||||
|
|
||||||
def set_http_body(self, coap_response):
|
|
||||||
"""
|
|
||||||
Set http body.
|
|
||||||
|
|
||||||
:param coap_response: the coap response
|
|
||||||
"""
|
|
||||||
if coap_response.payload is not None:
|
|
||||||
body = "<html><body><h1>", coap_response.payload, "</h1></body></html>"
|
|
||||||
self.wfile.write("".join(body))
|
|
||||||
else:
|
|
||||||
self.wfile.write("<html><body><h1>None</h1></body></html>")
|
|
||||||
|
|
||||||
def set_http_response(self, coap_response):
|
|
||||||
"""
|
|
||||||
Set http response.
|
|
||||||
|
|
||||||
:param coap_response: the coap response
|
|
||||||
"""
|
|
||||||
self.set_http_header(coap_response)
|
|
||||||
self.set_http_body(coap_response)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def get_command_line_args():
|
|
||||||
parser = argparse.ArgumentParser(description='Run the HTTP-CoAP Proxy.')
|
|
||||||
parser.add_argument('-p', dest='path', default=DEFAULT_HC_PATH,
|
|
||||||
help='the path of the hc_proxy server')
|
|
||||||
parser.add_argument('-hp', dest='hc_port', default=HC_PROXY_DEFAULT_PORT,
|
|
||||||
help='the port of the hc_proxy server')
|
|
||||||
parser.add_argument('-ip', dest='ip', default=LOCALHOST,
|
|
||||||
help='the ip of the hc_proxy server')
|
|
||||||
parser.add_argument('-cp', dest='coap_port', default=COAP_DEFAULT_PORT,
|
|
||||||
help='the coap server port you want to reach')
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
args = get_command_line_args()
|
|
||||||
hc_proxy = HCProxy(args.path, int(args.hc_port), args.ip, args.coap_port)
|
|
||||||
hc_proxy.run()
|
|
|
@ -1,314 +0,0 @@
|
||||||
import logging
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
from coapthon.messages.message import Message
|
|
||||||
from coapthon import defines
|
|
||||||
from coapthon.messages.request import Request
|
|
||||||
from coapthon.transaction import Transaction
|
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def str_append_hash(*args):
|
|
||||||
""" Convert each argument to a lower case string, appended, then hash """
|
|
||||||
ret_hash = ""
|
|
||||||
for i in args:
|
|
||||||
ret_hash += str(i).lower()
|
|
||||||
|
|
||||||
return hash(ret_hash)
|
|
||||||
|
|
||||||
|
|
||||||
class MessageLayer(object):
|
|
||||||
"""
|
|
||||||
Handles matching between messages (Message ID) and request/response (Token)
|
|
||||||
"""
|
|
||||||
def __init__(self, starting_mid):
|
|
||||||
"""
|
|
||||||
Set the layer internal structure.
|
|
||||||
|
|
||||||
:param starting_mid: the first mid used to send messages.
|
|
||||||
"""
|
|
||||||
self._transactions = {}
|
|
||||||
self._transactions_token = {}
|
|
||||||
if starting_mid is not None:
|
|
||||||
self._current_mid = starting_mid
|
|
||||||
else:
|
|
||||||
self._current_mid = random.randint(1, 1000)
|
|
||||||
|
|
||||||
def fetch_mid(self):
|
|
||||||
"""
|
|
||||||
Gets the next valid MID.
|
|
||||||
|
|
||||||
:return: the mid to use
|
|
||||||
"""
|
|
||||||
current_mid = self._current_mid
|
|
||||||
self._current_mid += 1
|
|
||||||
self._current_mid %= 65535
|
|
||||||
return current_mid
|
|
||||||
|
|
||||||
def purge(self):
|
|
||||||
for k in self._transactions.keys():
|
|
||||||
now = time.time()
|
|
||||||
transaction = self._transactions[k]
|
|
||||||
if transaction.timestamp + defines.EXCHANGE_LIFETIME < now:
|
|
||||||
logger.debug("Delete transaction")
|
|
||||||
del self._transactions[k]
|
|
||||||
for k in self._transactions_token.keys():
|
|
||||||
now = time.time()
|
|
||||||
transaction = self._transactions_token[k]
|
|
||||||
if transaction.timestamp + defines.EXCHANGE_LIFETIME < now:
|
|
||||||
logger.debug("Delete transaction")
|
|
||||||
del self._transactions_token[k]
|
|
||||||
|
|
||||||
def receive_request(self, request):
|
|
||||||
"""
|
|
||||||
Handle duplicates and store received messages.
|
|
||||||
|
|
||||||
:type request: Request
|
|
||||||
:param request: the incoming request
|
|
||||||
:rtype : Transaction
|
|
||||||
:return: the edited transaction
|
|
||||||
"""
|
|
||||||
logger.debug("receive_request - " + str(request))
|
|
||||||
try:
|
|
||||||
host, port = request.source
|
|
||||||
except AttributeError:
|
|
||||||
return
|
|
||||||
key_mid = str_append_hash(host, port, request.mid)
|
|
||||||
key_token = str_append_hash(host, port, request.token)
|
|
||||||
|
|
||||||
if key_mid in self._transactions.keys():
|
|
||||||
# Duplicated
|
|
||||||
self._transactions[key_mid].request.duplicated = True
|
|
||||||
transaction = self._transactions[key_mid]
|
|
||||||
else:
|
|
||||||
request.timestamp = time.time()
|
|
||||||
transaction = Transaction(request=request, timestamp=request.timestamp)
|
|
||||||
with transaction:
|
|
||||||
self._transactions[key_mid] = transaction
|
|
||||||
self._transactions_token[key_token] = transaction
|
|
||||||
return transaction
|
|
||||||
|
|
||||||
def receive_response(self, response):
|
|
||||||
"""
|
|
||||||
Pair responses with requests.
|
|
||||||
|
|
||||||
:type response: Response
|
|
||||||
:param response: the received response
|
|
||||||
:rtype : Transaction
|
|
||||||
:return: the transaction to which the response belongs to
|
|
||||||
"""
|
|
||||||
logger.debug("receive_response - " + str(response))
|
|
||||||
try:
|
|
||||||
host, port = response.source
|
|
||||||
except AttributeError:
|
|
||||||
return
|
|
||||||
key_mid = str_append_hash(host, port, response.mid)
|
|
||||||
key_mid_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.mid)
|
|
||||||
key_token = str_append_hash(host, port, response.token)
|
|
||||||
key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, response.token)
|
|
||||||
if key_mid in self._transactions.keys():
|
|
||||||
transaction = self._transactions[key_mid]
|
|
||||||
if response.token != transaction.request.token:
|
|
||||||
logger.warning("Tokens does not match - response message " + str(host) + ":" + str(port))
|
|
||||||
return None, False
|
|
||||||
elif key_token in self._transactions_token:
|
|
||||||
transaction = self._transactions_token[key_token]
|
|
||||||
elif key_mid_multicast in self._transactions.keys():
|
|
||||||
transaction = self._transactions[key_mid_multicast]
|
|
||||||
elif key_token_multicast in self._transactions_token:
|
|
||||||
transaction = self._transactions_token[key_token_multicast]
|
|
||||||
if response.token != transaction.request.token:
|
|
||||||
logger.warning("Tokens does not match - response message " + str(host) + ":" + str(port))
|
|
||||||
return None, False
|
|
||||||
else:
|
|
||||||
logger.warning("Un-Matched incoming response message " + str(host) + ":" + str(port))
|
|
||||||
return None, False
|
|
||||||
send_ack = False
|
|
||||||
if response.type == defines.Types["CON"]:
|
|
||||||
send_ack = True
|
|
||||||
|
|
||||||
transaction.request.acknowledged = True
|
|
||||||
transaction.completed = True
|
|
||||||
transaction.response = response
|
|
||||||
if transaction.retransmit_stop is not None:
|
|
||||||
transaction.retransmit_stop.set()
|
|
||||||
return transaction, send_ack
|
|
||||||
|
|
||||||
def receive_empty(self, message):
|
|
||||||
"""
|
|
||||||
Pair ACKs with requests.
|
|
||||||
|
|
||||||
:type message: Message
|
|
||||||
:param message: the received message
|
|
||||||
:rtype : Transaction
|
|
||||||
:return: the transaction to which the message belongs to
|
|
||||||
"""
|
|
||||||
logger.debug("receive_empty - " + str(message))
|
|
||||||
try:
|
|
||||||
host, port = message.source
|
|
||||||
except AttributeError:
|
|
||||||
return
|
|
||||||
key_mid = str_append_hash(host, port, message.mid)
|
|
||||||
key_mid_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.mid)
|
|
||||||
key_token = str_append_hash(host, port, message.token)
|
|
||||||
key_token_multicast = str_append_hash(defines.ALL_COAP_NODES, port, message.token)
|
|
||||||
if key_mid in self._transactions.keys():
|
|
||||||
transaction = self._transactions[key_mid]
|
|
||||||
elif key_token in self._transactions_token:
|
|
||||||
transaction = self._transactions_token[key_token]
|
|
||||||
elif key_mid_multicast in self._transactions.keys():
|
|
||||||
transaction = self._transactions[key_mid_multicast]
|
|
||||||
elif key_token_multicast in self._transactions_token:
|
|
||||||
transaction = self._transactions_token[key_token_multicast]
|
|
||||||
else:
|
|
||||||
logger.warning("Un-Matched incoming empty message " + str(host) + ":" + str(port))
|
|
||||||
return None
|
|
||||||
|
|
||||||
if message.type == defines.Types["ACK"]:
|
|
||||||
if not transaction.request.acknowledged:
|
|
||||||
transaction.request.acknowledged = True
|
|
||||||
elif (transaction.response is not None) and (not transaction.response.acknowledged):
|
|
||||||
transaction.response.acknowledged = True
|
|
||||||
elif message.type == defines.Types["RST"]:
|
|
||||||
if not transaction.request.acknowledged:
|
|
||||||
transaction.request.rejected = True
|
|
||||||
elif not transaction.response.acknowledged:
|
|
||||||
transaction.response.rejected = True
|
|
||||||
elif message.type == defines.Types["CON"]:
|
|
||||||
#implicit ACK (might have been lost)
|
|
||||||
logger.debug("Implicit ACK on received CON for waiting transaction")
|
|
||||||
transaction.request.acknowledged = True
|
|
||||||
else:
|
|
||||||
logger.warning("Unhandled message type...")
|
|
||||||
|
|
||||||
if transaction.retransmit_stop is not None:
|
|
||||||
transaction.retransmit_stop.set()
|
|
||||||
|
|
||||||
return transaction
|
|
||||||
|
|
||||||
def send_request(self, request):
|
|
||||||
"""
|
|
||||||
Create the transaction and fill it with the outgoing request.
|
|
||||||
|
|
||||||
:type request: Request
|
|
||||||
:param request: the request to send
|
|
||||||
:rtype : Transaction
|
|
||||||
:return: the created transaction
|
|
||||||
"""
|
|
||||||
logger.debug("send_request - " + str(request))
|
|
||||||
assert isinstance(request, Request)
|
|
||||||
try:
|
|
||||||
host, port = request.destination
|
|
||||||
except AttributeError:
|
|
||||||
return
|
|
||||||
|
|
||||||
request.timestamp = time.time()
|
|
||||||
transaction = Transaction(request=request, timestamp=request.timestamp)
|
|
||||||
if transaction.request.type is None:
|
|
||||||
transaction.request.type = defines.Types["CON"]
|
|
||||||
|
|
||||||
if transaction.request.mid is None:
|
|
||||||
transaction.request.mid = self.fetch_mid()
|
|
||||||
|
|
||||||
key_mid = str_append_hash(host, port, request.mid)
|
|
||||||
self._transactions[key_mid] = transaction
|
|
||||||
|
|
||||||
key_token = str_append_hash(host, port, request.token)
|
|
||||||
self._transactions_token[key_token] = transaction
|
|
||||||
|
|
||||||
return self._transactions[key_mid]
|
|
||||||
|
|
||||||
def send_response(self, transaction):
|
|
||||||
"""
|
|
||||||
Set the type, the token and eventually the MID for the outgoing response
|
|
||||||
|
|
||||||
:type transaction: Transaction
|
|
||||||
:param transaction: the transaction that owns the response
|
|
||||||
:rtype : Transaction
|
|
||||||
:return: the edited transaction
|
|
||||||
"""
|
|
||||||
logger.debug("send_response - " + str(transaction.response))
|
|
||||||
if transaction.response.type is None:
|
|
||||||
if transaction.request.type == defines.Types["CON"] and not transaction.request.acknowledged:
|
|
||||||
transaction.response.type = defines.Types["ACK"]
|
|
||||||
transaction.response.mid = transaction.request.mid
|
|
||||||
transaction.response.acknowledged = True
|
|
||||||
transaction.completed = True
|
|
||||||
elif transaction.request.type == defines.Types["NON"]:
|
|
||||||
transaction.response.type = defines.Types["NON"]
|
|
||||||
else:
|
|
||||||
transaction.response.type = defines.Types["CON"]
|
|
||||||
transaction.response.token = transaction.request.token
|
|
||||||
|
|
||||||
if transaction.response.mid is None:
|
|
||||||
transaction.response.mid = self.fetch_mid()
|
|
||||||
try:
|
|
||||||
host, port = transaction.response.destination
|
|
||||||
except AttributeError:
|
|
||||||
return
|
|
||||||
key_mid = str_append_hash(host, port, transaction.response.mid)
|
|
||||||
self._transactions[key_mid] = transaction
|
|
||||||
|
|
||||||
transaction.request.acknowledged = True
|
|
||||||
return transaction
|
|
||||||
|
|
||||||
def send_empty(self, transaction, related, message):
|
|
||||||
"""
|
|
||||||
Manage ACK or RST related to a transaction. Sets if the transaction has been acknowledged or rejected.
|
|
||||||
|
|
||||||
:param transaction: the transaction
|
|
||||||
:param related: if the ACK/RST message is related to the request or the response. Must be equal to
|
|
||||||
transaction.request or to transaction.response or None
|
|
||||||
:type message: Message
|
|
||||||
:param message: the ACK or RST message to send
|
|
||||||
"""
|
|
||||||
logger.debug("send_empty - " + str(message))
|
|
||||||
if transaction is None:
|
|
||||||
try:
|
|
||||||
host, port = message.destination
|
|
||||||
except AttributeError:
|
|
||||||
return
|
|
||||||
key_mid = str_append_hash(host, port, message.mid)
|
|
||||||
key_token = str_append_hash(host, port, message.token)
|
|
||||||
if key_mid in self._transactions:
|
|
||||||
transaction = self._transactions[key_mid]
|
|
||||||
related = transaction.response
|
|
||||||
elif key_token in self._transactions_token:
|
|
||||||
transaction = self._transactions_token[key_token]
|
|
||||||
related = transaction.response
|
|
||||||
else:
|
|
||||||
return message
|
|
||||||
|
|
||||||
if message.type == defines.Types["ACK"]:
|
|
||||||
if transaction.request == related:
|
|
||||||
transaction.request.acknowledged = True
|
|
||||||
transaction.completed = True
|
|
||||||
message.mid = transaction.request.mid
|
|
||||||
message.code = 0
|
|
||||||
message.destination = transaction.request.source
|
|
||||||
elif transaction.response == related:
|
|
||||||
transaction.response.acknowledged = True
|
|
||||||
transaction.completed = True
|
|
||||||
message.mid = transaction.response.mid
|
|
||||||
message.code = 0
|
|
||||||
message.token = transaction.response.token
|
|
||||||
message.destination = transaction.response.source
|
|
||||||
|
|
||||||
elif message.type == defines.Types["RST"]:
|
|
||||||
if transaction.request == related:
|
|
||||||
transaction.request.rejected = True
|
|
||||||
message._mid = transaction.request.mid
|
|
||||||
message.code = 0
|
|
||||||
message.token = transaction.request.token
|
|
||||||
message.destination = transaction.request.source
|
|
||||||
elif transaction.response == related:
|
|
||||||
transaction.response.rejected = True
|
|
||||||
transaction.completed = True
|
|
||||||
message._mid = transaction.response.mid
|
|
||||||
message.code = 0
|
|
||||||
message.token = transaction.response.token
|
|
||||||
message.destination = transaction.response.source
|
|
||||||
return message
|
|
|
@ -1,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")
|
|
||||||
|
|
|
@ -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
|
|
|
@ -1,260 +0,0 @@
|
||||||
from coapthon import defines
|
|
||||||
from coapthon.messages.message import Message
|
|
||||||
from coapthon.messages.option import Option
|
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
|
||||||
|
|
||||||
|
|
||||||
class Request(Message):
|
|
||||||
"""
|
|
||||||
Class to handle the Requests.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
"""
|
|
||||||
Initialize a Request message.
|
|
||||||
|
|
||||||
"""
|
|
||||||
super(Request, self).__init__()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def uri_path(self):
|
|
||||||
"""
|
|
||||||
Return the Uri-Path of a request
|
|
||||||
|
|
||||||
:rtype : String
|
|
||||||
:return: the Uri-Path
|
|
||||||
"""
|
|
||||||
value = []
|
|
||||||
for option in self.options:
|
|
||||||
if option.number == defines.OptionRegistry.URI_PATH.number:
|
|
||||||
value.append(str(option.value) + '/')
|
|
||||||
value = "".join(value)
|
|
||||||
value = value[:-1]
|
|
||||||
return value
|
|
||||||
|
|
||||||
@uri_path.setter
|
|
||||||
def uri_path(self, path):
|
|
||||||
"""
|
|
||||||
Set the Uri-Path of a request.
|
|
||||||
|
|
||||||
:param path: the Uri-Path
|
|
||||||
"""
|
|
||||||
path = path.strip("/")
|
|
||||||
tmp = path.split("?")
|
|
||||||
path = tmp[0]
|
|
||||||
paths = path.split("/")
|
|
||||||
for p in paths:
|
|
||||||
option = Option()
|
|
||||||
option.number = defines.OptionRegistry.URI_PATH.number
|
|
||||||
option.value = p
|
|
||||||
self.add_option(option)
|
|
||||||
if len(tmp) > 1:
|
|
||||||
query = tmp[1]
|
|
||||||
self.uri_query = query
|
|
||||||
|
|
||||||
@uri_path.deleter
|
|
||||||
def uri_path(self):
|
|
||||||
"""
|
|
||||||
Delete the Uri-Path of a request.
|
|
||||||
"""
|
|
||||||
self.del_option_by_number(defines.OptionRegistry.URI_PATH.number)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def uri_query(self):
|
|
||||||
"""
|
|
||||||
Get the Uri-Query of a request.
|
|
||||||
|
|
||||||
:return: the Uri-Query
|
|
||||||
:rtype : String
|
|
||||||
:return: the Uri-Query string
|
|
||||||
"""
|
|
||||||
value = []
|
|
||||||
for option in self.options:
|
|
||||||
if option.number == defines.OptionRegistry.URI_QUERY.number:
|
|
||||||
value.append(str(option.value))
|
|
||||||
return "&".join(value)
|
|
||||||
|
|
||||||
@uri_query.setter
|
|
||||||
def uri_query(self, value):
|
|
||||||
"""
|
|
||||||
Adds a query.
|
|
||||||
|
|
||||||
:param value: the query
|
|
||||||
"""
|
|
||||||
del self.uri_query
|
|
||||||
queries = value.split("&")
|
|
||||||
for q in queries:
|
|
||||||
option = Option()
|
|
||||||
option.number = defines.OptionRegistry.URI_QUERY.number
|
|
||||||
option.value = str(q)
|
|
||||||
self.add_option(option)
|
|
||||||
|
|
||||||
@uri_query.deleter
|
|
||||||
def uri_query(self):
|
|
||||||
"""
|
|
||||||
Delete a query.
|
|
||||||
"""
|
|
||||||
self.del_option_by_number(defines.OptionRegistry.URI_QUERY.number)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def accept(self):
|
|
||||||
"""
|
|
||||||
Get the Accept option of a request.
|
|
||||||
|
|
||||||
:return: the Accept value or None if not specified by the request
|
|
||||||
:rtype : String
|
|
||||||
"""
|
|
||||||
for option in self.options:
|
|
||||||
if option.number == defines.OptionRegistry.ACCEPT.number:
|
|
||||||
return option.value
|
|
||||||
return None
|
|
||||||
|
|
||||||
@accept.setter
|
|
||||||
def accept(self, value):
|
|
||||||
"""
|
|
||||||
Add an Accept option to a request.
|
|
||||||
|
|
||||||
:param value: the Accept value
|
|
||||||
"""
|
|
||||||
if value in defines.Content_types.values():
|
|
||||||
option = Option()
|
|
||||||
option.number = defines.OptionRegistry.ACCEPT.number
|
|
||||||
option.value = value
|
|
||||||
self.add_option(option)
|
|
||||||
|
|
||||||
@accept.deleter
|
|
||||||
def accept(self):
|
|
||||||
"""
|
|
||||||
Delete the Accept options of a request.
|
|
||||||
"""
|
|
||||||
self.del_option_by_number(defines.OptionRegistry.ACCEPT.number)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def if_match(self):
|
|
||||||
"""
|
|
||||||
Get the If-Match option of a request.
|
|
||||||
|
|
||||||
:return: the If-Match values or [] if not specified by the request
|
|
||||||
:rtype : list
|
|
||||||
"""
|
|
||||||
value = []
|
|
||||||
for option in self.options:
|
|
||||||
if option.number == defines.OptionRegistry.IF_MATCH.number:
|
|
||||||
value.append(option.value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
@if_match.setter
|
|
||||||
def if_match(self, values):
|
|
||||||
"""
|
|
||||||
Set the If-Match option of a request.
|
|
||||||
|
|
||||||
:param values: the If-Match values
|
|
||||||
:type values : list
|
|
||||||
"""
|
|
||||||
assert isinstance(values, list)
|
|
||||||
for v in values:
|
|
||||||
option = Option()
|
|
||||||
option.number = defines.OptionRegistry.IF_MATCH.number
|
|
||||||
option.value = v
|
|
||||||
self.add_option(option)
|
|
||||||
|
|
||||||
@if_match.deleter
|
|
||||||
def if_match(self):
|
|
||||||
"""
|
|
||||||
Delete the If-Match options of a request.
|
|
||||||
"""
|
|
||||||
self.del_option_by_number(defines.OptionRegistry.IF_MATCH.number)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def if_none_match(self):
|
|
||||||
"""
|
|
||||||
Get the if-none-match option of a request.
|
|
||||||
|
|
||||||
:return: True, if if-none-match is present
|
|
||||||
:rtype : bool
|
|
||||||
"""
|
|
||||||
for option in self.options:
|
|
||||||
if option.number == defines.OptionRegistry.IF_NONE_MATCH.number:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def add_if_none_match(self):
|
|
||||||
"""
|
|
||||||
Add the if-none-match option to the request.
|
|
||||||
"""
|
|
||||||
option = Option()
|
|
||||||
option.number = defines.OptionRegistry.IF_NONE_MATCH.number
|
|
||||||
option.value = None
|
|
||||||
self.add_option(option)
|
|
||||||
|
|
||||||
@if_none_match.deleter
|
|
||||||
def if_none_match(self):
|
|
||||||
"""
|
|
||||||
Delete the if-none-match option in the request.
|
|
||||||
"""
|
|
||||||
self.del_option_by_number(defines.OptionRegistry.IF_NONE_MATCH.number)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def proxy_uri(self):
|
|
||||||
"""
|
|
||||||
Get the Proxy-Uri option of a request.
|
|
||||||
|
|
||||||
:return: the Proxy-Uri values or None if not specified by the request
|
|
||||||
:rtype : String
|
|
||||||
"""
|
|
||||||
for option in self.options:
|
|
||||||
if option.number == defines.OptionRegistry.PROXY_URI.number:
|
|
||||||
return option.value
|
|
||||||
return None
|
|
||||||
|
|
||||||
@proxy_uri.setter
|
|
||||||
def proxy_uri(self, value):
|
|
||||||
"""
|
|
||||||
Set the Proxy-Uri option of a request.
|
|
||||||
|
|
||||||
:param value: the Proxy-Uri value
|
|
||||||
"""
|
|
||||||
option = Option()
|
|
||||||
option.number = defines.OptionRegistry.PROXY_URI.number
|
|
||||||
option.value = str(value)
|
|
||||||
self.add_option(option)
|
|
||||||
|
|
||||||
@proxy_uri.deleter
|
|
||||||
def proxy_uri(self):
|
|
||||||
"""
|
|
||||||
Delete the Proxy-Uri option of a request.
|
|
||||||
"""
|
|
||||||
self.del_option_by_number(defines.OptionRegistry.PROXY_URI.number)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def proxy_schema(self):
|
|
||||||
"""
|
|
||||||
Get the Proxy-Schema option of a request.
|
|
||||||
|
|
||||||
:return: the Proxy-Schema values or None if not specified by the request
|
|
||||||
:rtype : String
|
|
||||||
"""
|
|
||||||
for option in self.options:
|
|
||||||
if option.number == defines.OptionRegistry.PROXY_SCHEME.number:
|
|
||||||
return option.value
|
|
||||||
return None
|
|
||||||
|
|
||||||
@proxy_schema.setter
|
|
||||||
def proxy_schema(self, value):
|
|
||||||
"""
|
|
||||||
Set the Proxy-Schema option of a request.
|
|
||||||
|
|
||||||
:param value: the Proxy-Schema value
|
|
||||||
"""
|
|
||||||
option = Option()
|
|
||||||
option.number = defines.OptionRegistry.PROXY_SCHEME.number
|
|
||||||
option.value = str(value)
|
|
||||||
self.add_option(option)
|
|
||||||
|
|
||||||
@proxy_schema.deleter
|
|
||||||
def proxy_schema(self):
|
|
||||||
"""
|
|
||||||
Delete the Proxy-Schema option of a request.
|
|
||||||
"""
|
|
||||||
self.del_option_by_number(defines.OptionRegistry.PROXY_SCHEME.number)
|
|
||||||
|
|
|
@ -1,514 +0,0 @@
|
||||||
from coapthon import defines
|
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
|
||||||
|
|
||||||
|
|
||||||
class Resource(object):
|
|
||||||
"""
|
|
||||||
The Resource class. Represents the base class for all resources.
|
|
||||||
"""
|
|
||||||
def __init__(self, name, coap_server=None, visible=True, observable=True, allow_children=True):
|
|
||||||
"""
|
|
||||||
Initialize a new Resource.
|
|
||||||
|
|
||||||
:param name: the name of the resource.
|
|
||||||
:param coap_server: the server that own the resource
|
|
||||||
:param visible: if the resource is visible
|
|
||||||
:param observable: if the resource is observable
|
|
||||||
:param allow_children: if the resource could has children
|
|
||||||
"""
|
|
||||||
# The attributes of this resource.
|
|
||||||
self._attributes = {}
|
|
||||||
|
|
||||||
# The resource name.
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
# The resource path.
|
|
||||||
self.path = None
|
|
||||||
|
|
||||||
# Indicates whether this resource is visible to clients.
|
|
||||||
self._visible = visible
|
|
||||||
|
|
||||||
# Indicates whether this resource is observable by clients.
|
|
||||||
self._observable = observable
|
|
||||||
if self._observable:
|
|
||||||
self._attributes["obs"] = ""
|
|
||||||
|
|
||||||
self._allow_children = allow_children
|
|
||||||
|
|
||||||
self._observe_count = 1
|
|
||||||
|
|
||||||
self._payload = {}
|
|
||||||
|
|
||||||
self._content_type = None
|
|
||||||
|
|
||||||
self._etag = []
|
|
||||||
|
|
||||||
self._location_query = []
|
|
||||||
|
|
||||||
self._max_age = None
|
|
||||||
|
|
||||||
self._coap_server = coap_server
|
|
||||||
|
|
||||||
self._deleted = False
|
|
||||||
|
|
||||||
self._changed = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def deleted(self):
|
|
||||||
"""
|
|
||||||
Check if the resource has been deleted. For observing purpose.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
:return: True, if deleted
|
|
||||||
"""
|
|
||||||
return self._deleted
|
|
||||||
|
|
||||||
@deleted.setter
|
|
||||||
def deleted(self, b):
|
|
||||||
"""
|
|
||||||
Set the deleted parameter. For observing purpose.
|
|
||||||
|
|
||||||
:type b: bool
|
|
||||||
:param b: True, if deleted
|
|
||||||
"""
|
|
||||||
self._deleted = b
|
|
||||||
|
|
||||||
@property
|
|
||||||
def changed(self):
|
|
||||||
"""
|
|
||||||
Check if the resource has been changed. For observing purpose.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
:return: True, if changed
|
|
||||||
"""
|
|
||||||
return self._changed
|
|
||||||
|
|
||||||
@changed.setter
|
|
||||||
def changed(self, b):
|
|
||||||
"""
|
|
||||||
Set the changed parameter. For observing purpose.
|
|
||||||
|
|
||||||
:type b: bool
|
|
||||||
:param b: True, if changed
|
|
||||||
"""
|
|
||||||
self._changed = b
|
|
||||||
|
|
||||||
@property
|
|
||||||
def etag(self):
|
|
||||||
"""
|
|
||||||
Get the last valid ETag of the resource.
|
|
||||||
|
|
||||||
:return: the last ETag value or None if the resource doesn't have any ETag
|
|
||||||
"""
|
|
||||||
if self._etag:
|
|
||||||
return self._etag[-1]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@etag.setter
|
|
||||||
def etag(self, etag):
|
|
||||||
"""
|
|
||||||
Set the ETag of the resource.
|
|
||||||
|
|
||||||
:param etag: the ETag
|
|
||||||
"""
|
|
||||||
self._etag.append(etag)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def location_query(self):
|
|
||||||
"""
|
|
||||||
Get the Location-Query of a resource.
|
|
||||||
|
|
||||||
:return: the Location-Query
|
|
||||||
"""
|
|
||||||
return self._location_query
|
|
||||||
|
|
||||||
@location_query.setter
|
|
||||||
def location_query(self, lq):
|
|
||||||
"""
|
|
||||||
Set the Location-Query.
|
|
||||||
|
|
||||||
:param lq: the Location-Query
|
|
||||||
"""
|
|
||||||
self._location_query = lq
|
|
||||||
|
|
||||||
@location_query.deleter
|
|
||||||
def location_query(self):
|
|
||||||
"""
|
|
||||||
Delete the Location-Query.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.location_query = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_age(self):
|
|
||||||
"""
|
|
||||||
Get the Max-Age.
|
|
||||||
|
|
||||||
:return: the Max-Age
|
|
||||||
"""
|
|
||||||
return self._max_age
|
|
||||||
|
|
||||||
@max_age.setter
|
|
||||||
def max_age(self, ma):
|
|
||||||
"""
|
|
||||||
Set the Max-Age.
|
|
||||||
|
|
||||||
:param ma: the Max-Age
|
|
||||||
"""
|
|
||||||
self._max_age = ma
|
|
||||||
|
|
||||||
@property
|
|
||||||
def payload(self):
|
|
||||||
"""
|
|
||||||
Get the payload of the resource according to the content type specified by required_content_type or
|
|
||||||
"text/plain" by default.
|
|
||||||
|
|
||||||
:return: the payload.
|
|
||||||
"""
|
|
||||||
if self._content_type is not None:
|
|
||||||
try:
|
|
||||||
return self._payload[self._content_type]
|
|
||||||
except KeyError:
|
|
||||||
raise KeyError("Content-Type not available")
|
|
||||||
else:
|
|
||||||
|
|
||||||
if defines.Content_types["text/plain"] in self._payload:
|
|
||||||
return self._payload[defines.Content_types["text/plain"]]
|
|
||||||
else:
|
|
||||||
val = self._payload.keys()
|
|
||||||
return val[0], self._payload[val[0]]
|
|
||||||
|
|
||||||
@payload.setter
|
|
||||||
def payload(self, p):
|
|
||||||
"""
|
|
||||||
Set the payload of the resource.
|
|
||||||
|
|
||||||
:param p: the new payload
|
|
||||||
"""
|
|
||||||
if isinstance(p, tuple):
|
|
||||||
k = p[0]
|
|
||||||
v = p[1]
|
|
||||||
self.actual_content_type = k
|
|
||||||
self._payload[k] = v
|
|
||||||
else:
|
|
||||||
self._payload = {defines.Content_types["text/plain"]: p}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attributes(self):
|
|
||||||
"""
|
|
||||||
Get the CoRE Link Format attribute of the resource.
|
|
||||||
|
|
||||||
:return: the attribute of the resource
|
|
||||||
"""
|
|
||||||
return self._attributes
|
|
||||||
|
|
||||||
@attributes.setter
|
|
||||||
def attributes(self, att):
|
|
||||||
# TODO assert
|
|
||||||
"""
|
|
||||||
Set the CoRE Link Format attribute of the resource.
|
|
||||||
|
|
||||||
:param att: the attributes
|
|
||||||
"""
|
|
||||||
self._attributes = att
|
|
||||||
|
|
||||||
@property
|
|
||||||
def visible(self):
|
|
||||||
"""
|
|
||||||
Get if the resource is visible.
|
|
||||||
|
|
||||||
:return: True, if visible
|
|
||||||
"""
|
|
||||||
return self._visible
|
|
||||||
|
|
||||||
@property
|
|
||||||
def observable(self):
|
|
||||||
"""
|
|
||||||
Get if the resource is observable.
|
|
||||||
|
|
||||||
:return: True, if observable
|
|
||||||
"""
|
|
||||||
return self._observable
|
|
||||||
|
|
||||||
@property
|
|
||||||
def allow_children(self):
|
|
||||||
"""
|
|
||||||
Get if the resource allow children.
|
|
||||||
|
|
||||||
:return: True, if allow children
|
|
||||||
"""
|
|
||||||
return self._allow_children
|
|
||||||
|
|
||||||
@property
|
|
||||||
def observe_count(self):
|
|
||||||
"""
|
|
||||||
Get the Observe counter.
|
|
||||||
|
|
||||||
:return: the Observe counter value
|
|
||||||
"""
|
|
||||||
return self._observe_count
|
|
||||||
|
|
||||||
@observe_count.setter
|
|
||||||
def observe_count(self, v):
|
|
||||||
"""
|
|
||||||
Set the Observe counter.
|
|
||||||
|
|
||||||
:param v: the Observe counter value
|
|
||||||
"""
|
|
||||||
assert isinstance(v, int)
|
|
||||||
self._observe_count = (v % 65000)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def actual_content_type(self):
|
|
||||||
"""
|
|
||||||
Get the actual required Content-Type.
|
|
||||||
|
|
||||||
:return: the actual required Content-Type.
|
|
||||||
"""
|
|
||||||
return self._content_type
|
|
||||||
|
|
||||||
@actual_content_type.setter
|
|
||||||
def actual_content_type(self, act):
|
|
||||||
"""
|
|
||||||
Set the actual required Content-Type.
|
|
||||||
|
|
||||||
:param act: the actual required Content-Type.
|
|
||||||
"""
|
|
||||||
self._content_type = act
|
|
||||||
|
|
||||||
@property
|
|
||||||
def content_type(self):
|
|
||||||
"""
|
|
||||||
Get the CoRE Link Format ct attribute of the resource.
|
|
||||||
|
|
||||||
:return: the CoRE Link Format ct attribute
|
|
||||||
"""
|
|
||||||
value = ""
|
|
||||||
lst = self._attributes.get("ct")
|
|
||||||
if lst is not None and len(lst) > 0:
|
|
||||||
value = "ct="
|
|
||||||
for v in lst:
|
|
||||||
value += str(v) + " "
|
|
||||||
if len(value) > 0:
|
|
||||||
value = value[:-1]
|
|
||||||
return value
|
|
||||||
|
|
||||||
@content_type.setter
|
|
||||||
def content_type(self, lst):
|
|
||||||
"""
|
|
||||||
Set the CoRE Link Format ct attribute of the resource.
|
|
||||||
|
|
||||||
:param lst: the list of CoRE Link Format ct attribute of the resource
|
|
||||||
"""
|
|
||||||
value = []
|
|
||||||
if isinstance(lst, str):
|
|
||||||
ct = defines.Content_types[lst]
|
|
||||||
self.add_content_type(ct)
|
|
||||||
elif isinstance(lst, list):
|
|
||||||
for ct in lst:
|
|
||||||
self.add_content_type(ct)
|
|
||||||
|
|
||||||
def add_content_type(self, ct):
|
|
||||||
"""
|
|
||||||
Add a CoRE Link Format ct attribute to the resource.
|
|
||||||
|
|
||||||
:param ct: the CoRE Link Format ct attribute
|
|
||||||
"""
|
|
||||||
lst = self._attributes.get("ct")
|
|
||||||
if lst is None:
|
|
||||||
lst = []
|
|
||||||
if isinstance(ct, str):
|
|
||||||
ct = defines.Content_types[ct]
|
|
||||||
lst.append(ct)
|
|
||||||
self._attributes["ct"] = lst
|
|
||||||
|
|
||||||
@property
|
|
||||||
def resource_type(self):
|
|
||||||
"""
|
|
||||||
Get the CoRE Link Format rt attribute of the resource.
|
|
||||||
|
|
||||||
:return: the CoRE Link Format rt attribute
|
|
||||||
"""
|
|
||||||
value = "rt="
|
|
||||||
lst = self._attributes.get("rt")
|
|
||||||
if lst is None:
|
|
||||||
value = ""
|
|
||||||
else:
|
|
||||||
value += "\"" + str(lst) + "\""
|
|
||||||
return value
|
|
||||||
|
|
||||||
@resource_type.setter
|
|
||||||
def resource_type(self, rt):
|
|
||||||
"""
|
|
||||||
Set the CoRE Link Format rt attribute of the resource.
|
|
||||||
|
|
||||||
:param rt: the CoRE Link Format rt attribute
|
|
||||||
"""
|
|
||||||
if not isinstance(rt, str):
|
|
||||||
rt = str(rt)
|
|
||||||
self._attributes["rt"] = rt
|
|
||||||
|
|
||||||
@property
|
|
||||||
def interface_type(self):
|
|
||||||
"""
|
|
||||||
Get the CoRE Link Format if attribute of the resource.
|
|
||||||
|
|
||||||
:return: the CoRE Link Format if attribute
|
|
||||||
"""
|
|
||||||
value = "if="
|
|
||||||
lst = self._attributes.get("if")
|
|
||||||
if lst is None:
|
|
||||||
value = ""
|
|
||||||
else:
|
|
||||||
value += "\"" + str(lst) + "\""
|
|
||||||
return value
|
|
||||||
|
|
||||||
@interface_type.setter
|
|
||||||
def interface_type(self, ift):
|
|
||||||
"""
|
|
||||||
Set the CoRE Link Format if attribute of the resource.
|
|
||||||
|
|
||||||
:param ift: the CoRE Link Format if attribute
|
|
||||||
"""
|
|
||||||
if not isinstance(ift, str):
|
|
||||||
ift = str(ift)
|
|
||||||
self._attributes["if"] = ift
|
|
||||||
|
|
||||||
@property
|
|
||||||
def maximum_size_estimated(self):
|
|
||||||
"""
|
|
||||||
Get the CoRE Link Format sz attribute of the resource.
|
|
||||||
|
|
||||||
:return: the CoRE Link Format sz attribute
|
|
||||||
"""
|
|
||||||
value = "sz="
|
|
||||||
lst = self._attributes.get("sz")
|
|
||||||
if lst is None:
|
|
||||||
value = ""
|
|
||||||
else:
|
|
||||||
value += "\"" + str(lst) + "\""
|
|
||||||
return value
|
|
||||||
|
|
||||||
@maximum_size_estimated.setter
|
|
||||||
def maximum_size_estimated(self, sz):
|
|
||||||
"""
|
|
||||||
Set the CoRE Link Format sz attribute of the resource.
|
|
||||||
|
|
||||||
:param sz: the CoRE Link Format sz attribute
|
|
||||||
"""
|
|
||||||
if not isinstance(sz, str):
|
|
||||||
sz = str(sz)
|
|
||||||
self._attributes["sz"] = sz
|
|
||||||
|
|
||||||
@property
|
|
||||||
def observing(self):
|
|
||||||
"""
|
|
||||||
Get the CoRE Link Format obs attribute of the resource.
|
|
||||||
|
|
||||||
:return: the CoRE Link Format obs attribute
|
|
||||||
"""
|
|
||||||
if self._observable:
|
|
||||||
return "obs"
|
|
||||||
|
|
||||||
def init_resource(self, request, res):
|
|
||||||
"""
|
|
||||||
Helper function to initialize a new resource.
|
|
||||||
|
|
||||||
:param request: the request that generate the new resource
|
|
||||||
:param res: the resource
|
|
||||||
:return: the edited resource
|
|
||||||
"""
|
|
||||||
res.location_query = request.uri_query
|
|
||||||
res.payload = (request.content_type, request.payload)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def edit_resource(self, request):
|
|
||||||
"""
|
|
||||||
Helper function to edit a resource
|
|
||||||
|
|
||||||
:param request: the request that edit the resource
|
|
||||||
"""
|
|
||||||
self.location_query = request.uri_query
|
|
||||||
self.payload = (request.content_type, request.payload)
|
|
||||||
|
|
||||||
def render_GET(self, request):
|
|
||||||
"""
|
|
||||||
Method to be redefined to render a GET request on the resource.
|
|
||||||
|
|
||||||
:param request: the request
|
|
||||||
:return: the resource
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def render_GET_advanced(self, request, response):
|
|
||||||
"""
|
|
||||||
Method to be redefined to render a GET request on the resource.
|
|
||||||
|
|
||||||
:param response: the partially filled response
|
|
||||||
:param request: the request
|
|
||||||
:return: a tuple with (the resource, the response)
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def render_PUT(self, request):
|
|
||||||
"""
|
|
||||||
Method to be redefined to render a PUTT request on the resource.
|
|
||||||
|
|
||||||
:param request: the request
|
|
||||||
:return: the resource
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def render_PUT_advanced(self, request, response):
|
|
||||||
"""
|
|
||||||
Method to be redefined to render a PUTT request on the resource.
|
|
||||||
|
|
||||||
:param response: the partially filled response
|
|
||||||
:param request: the request
|
|
||||||
:return: a tuple with (the resource, the response)
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def render_POST(self, request):
|
|
||||||
"""
|
|
||||||
Method to be redefined to render a POST request on the resource.
|
|
||||||
|
|
||||||
:param request: the request
|
|
||||||
:return: the resource
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def render_POST_advanced(self, request, response):
|
|
||||||
"""
|
|
||||||
Method to be redefined to render a POST request on the resource.
|
|
||||||
|
|
||||||
:param response: the partially filled response
|
|
||||||
:param request: the request
|
|
||||||
:return: a tuple with (the resource, the response)
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def render_DELETE(self, request):
|
|
||||||
"""
|
|
||||||
Method to be redefined to render a DELETE request on the resource.
|
|
||||||
|
|
||||||
:param request: the request
|
|
||||||
:return: a boolean
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def render_DELETE_advanced(self, request, response):
|
|
||||||
"""
|
|
||||||
Method to be redefined to render a DELETE request on the resource.
|
|
||||||
|
|
||||||
:param response: the partially filled response
|
|
||||||
:param request: the request
|
|
||||||
:return: a tuple with a boolean and the response
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,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
|
|
|
@ -378,9 +378,10 @@ class CoAP(object):
|
||||||
|
|
||||||
ack = Message()
|
ack = Message()
|
||||||
ack.type = defines.Types['ACK']
|
ack.type = defines.Types['ACK']
|
||||||
# TODO handle mutex on transaction
|
with transaction:
|
||||||
if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]:
|
if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]:
|
||||||
ack = self._messageLayer.send_empty(transaction, transaction.request, ack)
|
ack = self._messageLayer.send_empty(transaction, transaction.request, ack)
|
||||||
|
if ack.type is not None and ack.mid is not None:
|
||||||
self.send_datagram(ack)
|
self.send_datagram(ack)
|
||||||
|
|
||||||
def notify(self, resource):
|
def notify(self, resource):
|
||||||
|
|
|
@ -1,421 +0,0 @@
|
||||||
import logging.config
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from coapthon import defines
|
|
||||||
from coapthon.layers.blocklayer import BlockLayer
|
|
||||||
from coapthon.layers.messagelayer import MessageLayer
|
|
||||||
from coapthon.layers.observelayer import ObserveLayer
|
|
||||||
from coapthon.layers.requestlayer import RequestLayer
|
|
||||||
from coapthon.layers.resourcelayer import ResourceLayer
|
|
||||||
from coapthon.messages.message import Message
|
|
||||||
from coapthon.messages.request import Request
|
|
||||||
from coapthon.messages.response import Response
|
|
||||||
from coapthon.resources.resource import Resource
|
|
||||||
from coapthon.serializer import Serializer
|
|
||||||
from coapthon.utils import Tree
|
|
||||||
from coapthon.utils import create_logging
|
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Giacomo Tanganelli'
|
|
||||||
|
|
||||||
|
|
||||||
if not os.path.isfile("logging.conf"):
|
|
||||||
create_logging()
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logging.config.fileConfig("logging.conf", disable_existing_loggers=False)
|
|
||||||
|
|
||||||
|
|
||||||
class CoAP(object):
|
|
||||||
"""
|
|
||||||
Implementation of the CoAP server
|
|
||||||
"""
|
|
||||||
def __init__(self, server_address, multicast=False, starting_mid=None, sock=None, cb_ignore_listen_exception=None):
|
|
||||||
"""
|
|
||||||
Initialize the server.
|
|
||||||
|
|
||||||
:param server_address: Server address for incoming connections
|
|
||||||
:param multicast: if the ip is a multicast address
|
|
||||||
:param starting_mid: used for testing purposes
|
|
||||||
:param sock: if a socket has been created externally, it can be used directly
|
|
||||||
:param cb_ignore_listen_exception: Callback function to handle exception raised during the socket listen operation
|
|
||||||
"""
|
|
||||||
self.stopped = threading.Event()
|
|
||||||
self.stopped.clear()
|
|
||||||
self.to_be_stopped = []
|
|
||||||
self.purge = threading.Thread(target=self.purge)
|
|
||||||
self.purge.start()
|
|
||||||
|
|
||||||
self._messageLayer = MessageLayer(starting_mid)
|
|
||||||
self._blockLayer = BlockLayer()
|
|
||||||
self._observeLayer = ObserveLayer()
|
|
||||||
self._requestLayer = RequestLayer(self)
|
|
||||||
self.resourceLayer = ResourceLayer(self)
|
|
||||||
|
|
||||||
# Resource directory
|
|
||||||
root = Resource('root', self, visible=False, observable=False, allow_children=False)
|
|
||||||
root.path = '/'
|
|
||||||
self.root = Tree()
|
|
||||||
self.root["/"] = root
|
|
||||||
self._serializer = None
|
|
||||||
|
|
||||||
self.server_address = server_address
|
|
||||||
self.multicast = multicast
|
|
||||||
self._cb_ignore_listen_exception = cb_ignore_listen_exception
|
|
||||||
|
|
||||||
addrinfo = socket.getaddrinfo(self.server_address[0], None)[0]
|
|
||||||
|
|
||||||
if sock is not None:
|
|
||||||
|
|
||||||
# Use given socket, could be a DTLS socket
|
|
||||||
self._socket = sock
|
|
||||||
|
|
||||||
elif self.multicast: # pragma: no cover
|
|
||||||
|
|
||||||
# Create a socket
|
|
||||||
# self._socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
|
|
||||||
# self._socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
|
|
||||||
|
|
||||||
# Join group
|
|
||||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
|
||||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
||||||
|
|
||||||
# Allow multiple copies of this program on one machine
|
|
||||||
# (not strictly needed)
|
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
self._socket.bind((defines.ALL_COAP_NODES, self.server_address[1]))
|
|
||||||
mreq = struct.pack("4sl", socket.inet_aton(defines.ALL_COAP_NODES), socket.INADDR_ANY)
|
|
||||||
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
|
||||||
self._unicast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
self._unicast_socket.bind(self.server_address)
|
|
||||||
else:
|
|
||||||
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
||||||
|
|
||||||
# Allow multiple copies of this program on one machine
|
|
||||||
# (not strictly needed)
|
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
self._socket.bind((defines.ALL_COAP_NODES_IPV6, self.server_address[1]))
|
|
||||||
|
|
||||||
addrinfo_multicast = socket.getaddrinfo(defines.ALL_COAP_NODES_IPV6, 5683)[0]
|
|
||||||
group_bin = socket.inet_pton(socket.AF_INET6, addrinfo_multicast[4][0])
|
|
||||||
mreq = group_bin + struct.pack('@I', 0)
|
|
||||||
self._socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
|
||||||
self._unicast_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
|
||||||
self._unicast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
self._unicast_socket.bind(self.server_address)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
|
||||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
else:
|
|
||||||
self._socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
|
||||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
|
|
||||||
self._socket.bind(self.server_address)
|
|
||||||
|
|
||||||
def purge(self):
|
|
||||||
"""
|
|
||||||
Clean old transactions
|
|
||||||
|
|
||||||
"""
|
|
||||||
while not self.stopped.isSet():
|
|
||||||
self.stopped.wait(timeout=defines.EXCHANGE_LIFETIME)
|
|
||||||
self._messageLayer.purge()
|
|
||||||
|
|
||||||
def listen(self, timeout=10):
|
|
||||||
"""
|
|
||||||
Listen for incoming messages. Timeout is used to check if the server must be switched off.
|
|
||||||
|
|
||||||
:param timeout: Socket Timeout in seconds
|
|
||||||
"""
|
|
||||||
self._socket.settimeout(float(timeout))
|
|
||||||
while not self.stopped.isSet():
|
|
||||||
try:
|
|
||||||
data, client_address = self._socket.recvfrom(4096)
|
|
||||||
if len(client_address) > 2:
|
|
||||||
client_address = (client_address[0], client_address[1])
|
|
||||||
except socket.timeout:
|
|
||||||
continue
|
|
||||||
except Exception as e:
|
|
||||||
if self._cb_ignore_listen_exception is not None and callable(self._cb_ignore_listen_exception):
|
|
||||||
if self._cb_ignore_listen_exception(e, self):
|
|
||||||
continue
|
|
||||||
raise
|
|
||||||
try:
|
|
||||||
serializer = Serializer()
|
|
||||||
message = serializer.deserialize(data, client_address)
|
|
||||||
if isinstance(message, int):
|
|
||||||
logger.error("receive_datagram - BAD REQUEST")
|
|
||||||
|
|
||||||
rst = Message()
|
|
||||||
rst.destination = client_address
|
|
||||||
rst.type = defines.Types["RST"]
|
|
||||||
rst.code = message
|
|
||||||
rst.mid = self._messageLayer.fetch_mid()
|
|
||||||
self.send_datagram(rst)
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.debug("receive_datagram - " + str(message))
|
|
||||||
if isinstance(message, Request):
|
|
||||||
transaction = self._messageLayer.receive_request(message)
|
|
||||||
if transaction.request.duplicated and transaction.completed:
|
|
||||||
logger.debug("message duplicated, transaction completed")
|
|
||||||
if transaction.response is not None:
|
|
||||||
self.send_datagram(transaction.response)
|
|
||||||
continue
|
|
||||||
elif transaction.request.duplicated and not transaction.completed:
|
|
||||||
logger.debug("message duplicated, transaction NOT completed")
|
|
||||||
self._send_ack(transaction)
|
|
||||||
continue
|
|
||||||
args = (transaction, )
|
|
||||||
t = threading.Thread(target=self.receive_request, args=args)
|
|
||||||
t.start()
|
|
||||||
# self.receive_datagram(data, client_address)
|
|
||||||
elif isinstance(message, Response):
|
|
||||||
logger.error("Received response from %s", message.source)
|
|
||||||
|
|
||||||
else: # is Message
|
|
||||||
transaction = self._messageLayer.receive_empty(message)
|
|
||||||
if transaction is not None:
|
|
||||||
with transaction:
|
|
||||||
self._blockLayer.receive_empty(message, transaction)
|
|
||||||
self._observeLayer.receive_empty(message, transaction)
|
|
||||||
|
|
||||||
except RuntimeError:
|
|
||||||
logger.exception("Exception with Executor")
|
|
||||||
self._socket.close()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Stop the server.
|
|
||||||
|
|
||||||
"""
|
|
||||||
logger.info("Stop server")
|
|
||||||
self.stopped.set()
|
|
||||||
for event in self.to_be_stopped:
|
|
||||||
event.set()
|
|
||||||
|
|
||||||
def receive_request(self, transaction):
|
|
||||||
"""
|
|
||||||
Handle requests coming from the udp socket.
|
|
||||||
|
|
||||||
:param transaction: the transaction created to manage the request
|
|
||||||
"""
|
|
||||||
|
|
||||||
with transaction:
|
|
||||||
|
|
||||||
transaction.separate_timer = self._start_separate_timer(transaction)
|
|
||||||
|
|
||||||
self._blockLayer.receive_request(transaction)
|
|
||||||
|
|
||||||
if transaction.block_transfer:
|
|
||||||
self._stop_separate_timer(transaction.separate_timer)
|
|
||||||
self._messageLayer.send_response(transaction)
|
|
||||||
self.send_datagram(transaction.response)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._observeLayer.receive_request(transaction)
|
|
||||||
|
|
||||||
self._requestLayer.receive_request(transaction)
|
|
||||||
|
|
||||||
if transaction.resource is not None and transaction.resource.changed:
|
|
||||||
self.notify(transaction.resource)
|
|
||||||
transaction.resource.changed = False
|
|
||||||
elif transaction.resource is not None and transaction.resource.deleted:
|
|
||||||
self.notify(transaction.resource)
|
|
||||||
transaction.resource.deleted = False
|
|
||||||
|
|
||||||
self._observeLayer.send_response(transaction)
|
|
||||||
|
|
||||||
self._blockLayer.send_response(transaction)
|
|
||||||
|
|
||||||
self._stop_separate_timer(transaction.separate_timer)
|
|
||||||
|
|
||||||
self._messageLayer.send_response(transaction)
|
|
||||||
|
|
||||||
if transaction.response is not None:
|
|
||||||
if transaction.response.type == defines.Types["CON"]:
|
|
||||||
self._start_retransmission(transaction, transaction.response)
|
|
||||||
self.send_datagram(transaction.response)
|
|
||||||
|
|
||||||
def send_datagram(self, message):
|
|
||||||
"""
|
|
||||||
Send a message through the udp socket.
|
|
||||||
|
|
||||||
:type message: Message
|
|
||||||
:param message: the message to send
|
|
||||||
"""
|
|
||||||
if not self.stopped.isSet():
|
|
||||||
host, port = message.destination
|
|
||||||
logger.debug("send_datagram - " + str(message))
|
|
||||||
serializer = Serializer()
|
|
||||||
message = serializer.serialize(message)
|
|
||||||
if self.multicast:
|
|
||||||
self._unicast_socket.sendto(message, (host, port))
|
|
||||||
else:
|
|
||||||
self._socket.sendto(message, (host, port))
|
|
||||||
|
|
||||||
def add_resource(self, path, resource):
|
|
||||||
"""
|
|
||||||
Helper function to add resources to the resource directory during server initialization.
|
|
||||||
|
|
||||||
:param path: the path for the new created resource
|
|
||||||
:type resource: Resource
|
|
||||||
:param resource: the resource to be added
|
|
||||||
"""
|
|
||||||
|
|
||||||
assert isinstance(resource, Resource)
|
|
||||||
path = path.strip("/")
|
|
||||||
paths = path.split("/")
|
|
||||||
actual_path = ""
|
|
||||||
i = 0
|
|
||||||
for p in paths:
|
|
||||||
i += 1
|
|
||||||
actual_path += "/" + p
|
|
||||||
try:
|
|
||||||
res = self.root[actual_path]
|
|
||||||
except KeyError:
|
|
||||||
res = None
|
|
||||||
if res is None:
|
|
||||||
if len(paths) != i:
|
|
||||||
return False
|
|
||||||
resource.path = actual_path
|
|
||||||
self.root[actual_path] = resource
|
|
||||||
return True
|
|
||||||
|
|
||||||
def remove_resource(self, path):
|
|
||||||
"""
|
|
||||||
Helper function to remove resources.
|
|
||||||
|
|
||||||
:param path: the path for the unwanted resource
|
|
||||||
:rtype : the removed object
|
|
||||||
"""
|
|
||||||
|
|
||||||
path = path.strip("/")
|
|
||||||
paths = path.split("/")
|
|
||||||
actual_path = ""
|
|
||||||
i = 0
|
|
||||||
for p in paths:
|
|
||||||
i += 1
|
|
||||||
actual_path += "/" + p
|
|
||||||
try:
|
|
||||||
res = self.root[actual_path]
|
|
||||||
except KeyError:
|
|
||||||
res = None
|
|
||||||
if res is not None:
|
|
||||||
del(self.root[actual_path])
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _start_retransmission(self, transaction, message):
|
|
||||||
"""
|
|
||||||
Start the retransmission task.
|
|
||||||
|
|
||||||
:type transaction: Transaction
|
|
||||||
:param transaction: the transaction that owns the message that needs retransmission
|
|
||||||
:type message: Message
|
|
||||||
:param message: the message that needs the retransmission task
|
|
||||||
"""
|
|
||||||
with transaction:
|
|
||||||
if message.type == defines.Types['CON']:
|
|
||||||
future_time = random.uniform(defines.ACK_TIMEOUT, (defines.ACK_TIMEOUT * defines.ACK_RANDOM_FACTOR))
|
|
||||||
transaction.retransmit_thread = threading.Thread(target=self._retransmit,
|
|
||||||
args=(transaction, message, future_time, 0))
|
|
||||||
transaction.retransmit_stop = threading.Event()
|
|
||||||
self.to_be_stopped.append(transaction.retransmit_stop)
|
|
||||||
transaction.retransmit_thread.start()
|
|
||||||
|
|
||||||
def _retransmit(self, transaction, message, future_time, retransmit_count):
|
|
||||||
"""
|
|
||||||
Thread function to retransmit the message in the future
|
|
||||||
|
|
||||||
:param transaction: the transaction that owns the message that needs retransmission
|
|
||||||
:param message: the message that needs the retransmission task
|
|
||||||
:param future_time: the amount of time to wait before a new attempt
|
|
||||||
:param retransmit_count: the number of retransmissions
|
|
||||||
"""
|
|
||||||
with transaction:
|
|
||||||
while retransmit_count < defines.MAX_RETRANSMIT and (not message.acknowledged and not message.rejected) \
|
|
||||||
and not self.stopped.isSet():
|
|
||||||
if transaction.retransmit_stop is not None:
|
|
||||||
transaction.retransmit_stop.wait(timeout=future_time)
|
|
||||||
if not message.acknowledged and not message.rejected and not self.stopped.isSet():
|
|
||||||
retransmit_count += 1
|
|
||||||
future_time *= 2
|
|
||||||
self.send_datagram(message)
|
|
||||||
|
|
||||||
if message.acknowledged or message.rejected:
|
|
||||||
message.timeouted = False
|
|
||||||
else:
|
|
||||||
logger.warning("Give up on message {message}".format(message=message.line_print))
|
|
||||||
message.timeouted = True
|
|
||||||
if message.observe is not None:
|
|
||||||
self._observeLayer.remove_subscriber(message)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.to_be_stopped.remove(transaction.retransmit_stop)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
transaction.retransmit_stop = None
|
|
||||||
transaction.retransmit_thread = None
|
|
||||||
|
|
||||||
def _start_separate_timer(self, transaction):
|
|
||||||
"""
|
|
||||||
Start a thread to handle separate mode.
|
|
||||||
|
|
||||||
:type transaction: Transaction
|
|
||||||
:param transaction: the transaction that is in processing
|
|
||||||
:rtype : the Timer object
|
|
||||||
"""
|
|
||||||
t = threading.Timer(defines.ACK_TIMEOUT, self._send_ack, (transaction,))
|
|
||||||
t.start()
|
|
||||||
return t
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _stop_separate_timer(timer):
|
|
||||||
"""
|
|
||||||
Stop the separate Thread if an answer has been already provided to the client.
|
|
||||||
|
|
||||||
:param timer: The Timer object
|
|
||||||
"""
|
|
||||||
timer.cancel()
|
|
||||||
|
|
||||||
def _send_ack(self, transaction):
|
|
||||||
"""
|
|
||||||
Sends an ACK message for the request.
|
|
||||||
|
|
||||||
:param transaction: the transaction that owns the request
|
|
||||||
"""
|
|
||||||
|
|
||||||
ack = Message()
|
|
||||||
ack.type = defines.Types['ACK']
|
|
||||||
# TODO handle mutex on transaction
|
|
||||||
if not transaction.request.acknowledged and transaction.request.type == defines.Types["CON"]:
|
|
||||||
ack = self._messageLayer.send_empty(transaction, transaction.request, ack)
|
|
||||||
self.send_datagram(ack)
|
|
||||||
|
|
||||||
def notify(self, resource):
|
|
||||||
"""
|
|
||||||
Notifies the observers of a certain resource.
|
|
||||||
|
|
||||||
:param resource: the resource
|
|
||||||
"""
|
|
||||||
observers = self._observeLayer.notify(resource)
|
|
||||||
logger.debug("Notify")
|
|
||||||
for transaction in observers:
|
|
||||||
with transaction:
|
|
||||||
transaction.response = None
|
|
||||||
transaction = self._requestLayer.receive_request(transaction)
|
|
||||||
transaction = self._observeLayer.send_response(transaction)
|
|
||||||
transaction = self._blockLayer.send_response(transaction)
|
|
||||||
transaction = self._messageLayer.send_response(transaction)
|
|
||||||
if transaction.response is not None:
|
|
||||||
if transaction.response.type == defines.Types["CON"]:
|
|
||||||
self._start_retransmission(transaction, transaction.response)
|
|
||||||
|
|
||||||
self.send_datagram(transaction.response)
|
|
|
@ -1,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]
|
|
Loading…
Reference in a new issue