CoAP/coapthon/layers/blocklayer.py
Matthieu BRIEDA 264167e3d1
Update Blocklayer to deal with Block1 option
On the last block answer, the Client still sent another empty message, it didn't take into account the fact that the previous block was sent with m=0. This commit fixed that for me.
2018-03-14 17:24:57 +01:00

309 lines
12 KiB
Python

import logging
from coapthon import defines
from coapthon.messages.request import Request
from coapthon.messages.response import Response
logger = logging.getLogger(__name__)
__author__ = 'Giacomo Tanganelli'
class BlockItem(object):
def __init__(self, byte, num, m, size, payload=None, content_type=None):
"""
Data structure to store Block parameters
:param byte: the last byte exchanged
:param num: the num field of the block option
:param m: the M bit of the block option
:param size: the size field of the block option
:param payload: the overall payload received in all blocks
:param content_type: the content-type of the payload
"""
self.byte = byte
self.num = num
self.m = m
self.size = size
self.payload = payload
self.content_type = content_type
class BlockLayer(object):
"""
Handle the Blockwise options. Hides all the exchange to both servers and clients.
"""
def __init__(self):
self._block1_sent = {}
self._block2_sent = {}
self._block1_receive = {}
self._block2_receive = {}
def receive_request(self, transaction):
"""
Handles the Blocks option in a incoming request.
:type transaction: Transaction
:param transaction: the transaction that owns the request
:rtype : Transaction
:return: the edited transaction
"""
if transaction.request.block2 is not None:
host, port = transaction.request.source
key_token = hash(str(host) + str(port) + str(transaction.request.token))
num, m, size = transaction.request.block2
if key_token in self._block2_receive:
self._block2_receive[key_token].num = num
self._block2_receive[key_token].size = size
self._block2_receive[key_token].m = m
del transaction.request.block2
else:
# early negotiation
byte = 0
self._block2_receive[key_token] = BlockItem(byte, num, m, size)
del transaction.request.block2
elif transaction.request.block1 is not None:
# POST or PUT
host, port = transaction.request.source
key_token = hash(str(host) + str(port) + str(transaction.request.token))
num, m, size = transaction.request.block1
if key_token in self._block1_receive:
content_type = transaction.request.content_type
if num != self._block1_receive[key_token].num \
or content_type != self._block1_receive[key_token].content_type:
# Error Incomplete
return self.incomplete(transaction)
self._block1_receive[key_token].payload += transaction.request.payload
else:
# first block
if num != 0:
# Error Incomplete
return self.incomplete(transaction)
content_type = transaction.request.content_type
self._block1_receive[key_token] = BlockItem(size, num, m, size, transaction.request.payload,
content_type)
if m == 0:
transaction.request.payload = self._block1_receive[key_token].payload
# end of blockwise
del transaction.request.block1
transaction.block_transfer = False
del self._block1_receive[key_token]
return transaction
else:
# Continue
transaction.block_transfer = True
transaction.response = Response()
transaction.response.destination = transaction.request.source
transaction.response.token = transaction.request.token
transaction.response.code = defines.Codes.CONTINUE.number
transaction.response.block1 = (num, m, size)
num += 1
byte = size
self._block1_receive[key_token].byte = byte
self._block1_receive[key_token].num = num
self._block1_receive[key_token].size = size
self._block1_receive[key_token].m = m
return transaction
def receive_response(self, transaction):
"""
Handles the Blocks option in a incoming response.
:type transaction: Transaction
:param transaction: the transaction that owns the response
:rtype : Transaction
:return: the edited transaction
"""
host, port = transaction.response.source
key_token = hash(str(host) + str(port) + str(transaction.response.token))
if key_token in self._block1_sent and transaction.response.block1 is not None:
item = self._block1_sent[key_token]
transaction.block_transfer = True
if item.m == 0:
transaction.block_transfer = False
del transaction.request.block1
return transaction
n_num, n_m, n_size = transaction.response.block1
if n_num != item.num: # pragma: no cover
logger.warning("Blockwise num acknowledged error, expected " + str(item.num) + " received " +
str(n_num))
return None
if n_size < item.size:
logger.debug("Scale down size, was " + str(item.size) + " become " + str(n_size))
item.size = n_size
request = transaction.request
del request.mid
del request.block1
request.payload = item.payload[item.byte: item.byte+item.size]
item.num += 1
item.byte += item.size
if len(item.payload) <= item.byte:
item.m = 0
else:
item.m = 1
request.block1 = (item.num, item.m, item.size)
elif transaction.response.block2 is not None:
num, m, size = transaction.response.block2
if m == 1:
transaction.block_transfer = True
if key_token in self._block2_sent:
item = self._block2_sent[key_token]
if num != item.num: # pragma: no cover
logger.error("Receive unwanted block")
return self.error(transaction, defines.Codes.REQUEST_ENTITY_INCOMPLETE.number)
if item.content_type is None:
item.content_type = transaction.response.content_type
if item.content_type != transaction.response.content_type: # pragma: no cover
logger.error("Content-type Error")
return self.error(transaction, defines.Codes.UNSUPPORTED_CONTENT_FORMAT.number)
item.byte += size
item.num = num + 1
item.size = size
item.m = m
item.payload += transaction.response.payload
else:
item = BlockItem(size, num + 1, m, size, transaction.response.payload,
transaction.response.content_type)
self._block2_sent[key_token] = item
request = transaction.request
del request.mid
del request.block2
request.block2 = (item.num, 0, item.size)
else:
transaction.block_transfer = False
if key_token in self._block2_sent:
if self._block2_sent[key_token].content_type != transaction.response.content_type: # pragma: no cover
logger.error("Content-type Error")
return self.error(transaction, defines.Codes.UNSUPPORTED_CONTENT_FORMAT.number)
transaction.response.payload = self._block2_sent[key_token].payload + transaction.response.payload
del self._block2_sent[key_token]
else:
transaction.block_transfer = False
return transaction
def receive_empty(self, empty, transaction):
"""
Dummy function. Used to do not broke the layered architecture.
:type empty: Message
:param empty: the received empty message
:type transaction: Transaction
:param transaction: the transaction that owns the empty message
:rtype : Transaction
:return: the transaction
"""
return transaction
def send_response(self, transaction):
"""
Handles the Blocks option in a outgoing response.
:type transaction: Transaction
:param transaction: the transaction that owns the response
:rtype : Transaction
:return: the edited transaction
"""
host, port = transaction.request.source
key_token = hash(str(host) + str(port) + str(transaction.request.token))
if (key_token in self._block2_receive and transaction.response.payload is not None) or \
(transaction.response.payload is not None and len(transaction.response.payload) > defines.MAX_PAYLOAD):
if key_token in self._block2_receive:
byte = self._block2_receive[key_token].byte
size = self._block2_receive[key_token].size
num = self._block2_receive[key_token].num
else:
byte = 0
num = 0
size = defines.MAX_PAYLOAD
m = 1
self._block2_receive[key_token] = BlockItem(byte, num, m, size)
if len(transaction.response.payload) > (byte + size):
m = 1
else:
m = 0
transaction.response.payload = transaction.response.payload[byte:byte + size]
del transaction.response.block2
transaction.response.block2 = (num, m, size)
self._block2_receive[key_token].byte += size
self._block2_receive[key_token].num += 1
if m == 0:
del self._block2_receive[key_token]
return transaction
def send_request(self, request):
"""
Handles the Blocks option in a outgoing request.
:type request: Request
:param request: the outgoing request
:return: the edited request
"""
assert isinstance(request, Request)
if request.block1 or (request.payload is not None and len(request.payload) > defines.MAX_PAYLOAD):
host, port = request.destination
key_token = hash(str(host) + str(port) + str(request.token))
if request.block1:
num, m, size = request.block1
else:
num = 0
m = 1
size = defines.MAX_PAYLOAD
self._block1_sent[key_token] = BlockItem(size, num, m, size, request.payload, request.content_type)
request.payload = request.payload[0:size]
del request.block1
request.block1 = (num, m, size)
elif request.block2:
host, port = request.destination
key_token = hash(str(host) + str(port) + str(request.token))
num, m, size = request.block2
item = BlockItem(size, num, m, size, "", None)
self._block2_sent[key_token] = item
return request
return request
@staticmethod
def incomplete(transaction):
"""
Notifies incomplete blockwise exchange.
:type transaction: Transaction
:param transaction: the transaction that owns the response
:rtype : Transaction
:return: the edited transaction
"""
transaction.block_transfer = True
transaction.response = Response()
transaction.response.destination = transaction.request.source
transaction.response.token = transaction.request.token
transaction.response.code = defines.Codes.REQUEST_ENTITY_INCOMPLETE.number
return transaction
@staticmethod
def error(transaction, code): # pragma: no cover
"""
Notifies generic error on blockwise exchange.
:type transaction: Transaction
:param transaction: the transaction that owns the response
:rtype : Transaction
:return: the edited transaction
"""
transaction.block_transfer = True
transaction.response = Response()
transaction.response.destination = transaction.request.source
transaction.response.type = defines.Types["RST"]
transaction.response.token = transaction.request.token
transaction.response.code = code
return transaction