iot/zoap: Add support for block sized transfers

This will add basic support for sending bodies of data that exceed the
size of a single UDP packet.

Block-wise transfers are defined in the recently adopted RFC 7959[1].

The RFC defines four options, two negotiating the block transfer, and
two for informing the size of the transfer. Depending whether the packet
is a request or a response, each option is defined as
"control" (informative) or descriptive (describes what's in the
payload).

Change-Id: Ic71275558c4afed0298d20e8712f76d53904f89f
Signed-off-by: Vinicius Costa Gomes <vinicius.gomes@intel.com>
This commit is contained in:
Vinicius Costa Gomes 2016-09-14 11:39:15 -03:00 committed by Anas Nashif
commit b7833b9851
2 changed files with 304 additions and 1 deletions

View file

@ -1028,3 +1028,219 @@ void zoap_header_set_id(struct zoap_packet *pkt, uint16_t id)
sys_put_be16(id, &appdata[2]);
}
int zoap_block_transfer_init(struct zoap_block_context *ctx,
enum zoap_block_size block_size,
size_t total_size)
{
ctx->block_size = block_size;
ctx->total_size = total_size;
ctx->current = 0;
return 0;
}
#define GET_BLOCK_SIZE(v) (((v) & 0x7))
#define GET_MORE(v) (!!((v) & 0x08))
#define GET_NUM(v) ((v) >> 4)
#define SET_BLOCK_SIZE(v, b) (v |= ((b) & 0x07))
#define SET_MORE(v, m) ((v) |= (m) ? 0x08 : 0x00)
#define SET_NUM(v, n) ((v) |= ((n) << 4))
static bool is_request(struct zoap_packet *pkt)
{
uint8_t code = zoap_header_get_code(pkt);
return !(code & ~ZOAP_REQUEST_MASK);
}
int zoap_add_block1_option(struct zoap_packet *pkt,
struct zoap_block_context *ctx)
{
uint16_t bytes = zoap_block_size_to_bytes(ctx->block_size);
unsigned int val = 0;
int r;
if (is_request(pkt)) {
SET_BLOCK_SIZE(val, ctx->block_size);
SET_MORE(val, ctx->current + bytes < ctx->total_size);
SET_NUM(val, ctx->current / bytes);
} else {
SET_BLOCK_SIZE(val, ctx->block_size);
SET_NUM(val, ctx->current / bytes);
}
r = zoap_add_option_int(pkt, ZOAP_OPTION_BLOCK1, val);
return r;
}
int zoap_add_block2_option(struct zoap_packet *pkt,
struct zoap_block_context *ctx)
{
int r, val = 0;
uint16_t bytes = zoap_block_size_to_bytes(ctx->block_size);
if (is_request(pkt)) {
SET_BLOCK_SIZE(val, ctx->block_size);
SET_NUM(val, ctx->current / bytes);
} else {
SET_BLOCK_SIZE(val, ctx->block_size);
SET_MORE(val, ctx->current + bytes < ctx->total_size);
SET_NUM(val, ctx->current / bytes);
}
r = zoap_add_option_int(pkt, ZOAP_OPTION_BLOCK2, val);
return r;
}
int zoap_add_size1_option(struct zoap_packet *pkt,
struct zoap_block_context *ctx)
{
return zoap_add_option_int(pkt, ZOAP_OPTION_SIZE1, ctx->total_size);
}
int zoap_add_size2_option(struct zoap_packet *pkt,
struct zoap_block_context *ctx)
{
return zoap_add_option_int(pkt, ZOAP_OPTION_SIZE2, ctx->total_size);
}
struct block_transfer {
int num;
int block_size;
bool more;
};
static unsigned int get_block_option(struct zoap_packet *pkt, uint16_t code)
{
struct zoap_option option;
unsigned int val;
int count = 1;
count = zoap_find_options(pkt, code, &option, count);
if (count <= 0)
return 0;
val = zoap_option_value_to_int(&option);
return val;
}
static int update_descriptive_block(struct zoap_block_context *ctx,
int block, int size)
{
size_t new_current = GET_NUM(block) << (GET_BLOCK_SIZE(block) + 4);
if (block == 0) {
return -ENOENT;
}
if (size && ctx->total_size && ctx->total_size != size) {
return -EINVAL;
}
if (ctx->block_size > 0 && GET_BLOCK_SIZE(block) > ctx->block_size) {
return -EINVAL;
}
if (ctx->total_size && new_current > ctx->total_size) {
return -EINVAL;
}
if (size) {
ctx->total_size = size;
}
ctx->current = new_current;
ctx->block_size = GET_BLOCK_SIZE(block);
return 0;
}
static int update_control_block1(struct zoap_block_context *ctx,
int block, int size)
{
size_t new_current = GET_NUM(block) << (GET_BLOCK_SIZE(block) + 4);
if (block == 0) {
return 0;
}
if (new_current != ctx->current) {
return -EINVAL;
}
if (GET_BLOCK_SIZE(block) > ctx->block_size) {
return -EINVAL;
}
ctx->block_size = GET_BLOCK_SIZE(block);
ctx->total_size = size;
return 0;
}
static int update_control_block2(struct zoap_block_context *ctx,
int block, int size)
{
size_t new_current = GET_NUM(block) << (GET_BLOCK_SIZE(block) + 4);
if (block == 0) {
return 0;
}
if (GET_MORE(block)) {
return -EINVAL;
}
if (GET_NUM(block) > 0 && GET_BLOCK_SIZE(block) != ctx->block_size) {
return -EINVAL;
}
ctx->current = new_current;
ctx->block_size = GET_BLOCK_SIZE(block);
ctx->total_size = size;
return 0;
}
int zoap_update_from_block(struct zoap_packet *pkt,
struct zoap_block_context *ctx)
{
unsigned int block1, block2, size1, size2;
int r;
block1 = get_block_option(pkt, ZOAP_OPTION_BLOCK1);
block2 = get_block_option(pkt, ZOAP_OPTION_BLOCK2);
size1 = get_block_option(pkt, ZOAP_OPTION_SIZE1);
size2 = get_block_option(pkt, ZOAP_OPTION_SIZE2);
if (is_request(pkt)) {
r = update_control_block2(ctx, block2, size2);
if (r) {
return r;
}
return update_descriptive_block(ctx, block1, size1);
}
r = update_control_block1(ctx, block1, size1);
if (r) {
return r;
}
return update_descriptive_block(ctx, block2, size2);
}
size_t zoap_next_block(struct zoap_block_context *ctx)
{
if (ctx->current >= ctx->total_size) {
return 0;
}
ctx->current += zoap_block_size_to_bytes(ctx->block_size);
return ctx->current;
}

View file

@ -54,8 +54,12 @@ enum zoap_option_num {
ZOAP_OPTION_URI_QUERY = 15,
ZOAP_OPTION_ACCEPT = 17,
ZOAP_OPTION_LOCATION_QUERY = 20,
ZOAP_OPTION_BLOCK2 = 23,
ZOAP_OPTION_BLOCK1 = 27,
ZOAP_OPTION_SIZE2 = 28,
ZOAP_OPTION_PROXY_URI = 35,
ZOAP_OPTION_PROXY_SCHEME = 39
ZOAP_OPTION_PROXY_SCHEME = 39,
ZOAP_OPTION_SIZE1 = 60,
};
/**
@ -127,6 +131,7 @@ enum zoap_response_code {
ZOAP_RESPONSE_CODE_NOT_FOUND = zoap_make_response_code(4, 4),
ZOAP_RESPONSE_CODE_NOT_ALLOWED = zoap_make_response_code(4, 5),
ZOAP_RESPONSE_CODE_NOT_ACCEPTABLE = zoap_make_response_code(4, 6),
ZOAP_RESPONSE_CODE_INCOMPLETE = zoap_make_response_code(4, 8),
ZOAP_RESPONSE_CODE_PRECONDITION_FAILED = zoap_make_response_code(4, 12),
ZOAP_RESPONSE_CODE_REQUEST_TOO_LARGE = zoap_make_response_code(4, 13),
ZOAP_RESPONSE_CODE_INTERNAL_ERROR = zoap_make_response_code(5, 0),
@ -402,6 +407,88 @@ int zoap_add_option_int(struct zoap_packet *pkt, uint16_t code,
int zoap_find_options(const struct zoap_packet *pkt, uint16_t code,
struct zoap_option *options, uint16_t veclen);
/**
* Represents the size of each block that will be transferred using
* block-wise transfers [RFC7959]:
*
* Each entry maps directly to the value that is used in the wire.
*
* https://tools.ietf.org/html/rfc7959
*/
enum zoap_block_size {
ZOAP_BLOCK_16,
ZOAP_BLOCK_32,
ZOAP_BLOCK_64,
ZOAP_BLOCK_128,
ZOAP_BLOCK_256,
ZOAP_BLOCK_512,
ZOAP_BLOCK_1024,
};
/**
* Helper for converting the enumeration to the size expressed in bytes.
*/
static inline uint16_t zoap_block_size_to_bytes(
enum zoap_block_size block_size)
{
return (1 << (block_size + 4));
}
/**
* Represents the current state of a block-wise transaction.
*/
struct zoap_block_context {
size_t total_size;
size_t current;
enum zoap_block_size block_size;
};
/**
* Initializes the context of a block-wise transfer.
*/
int zoap_block_transfer_init(struct zoap_block_context *ctx,
enum zoap_block_size block_size,
size_t total_size);
/**
* Add BLOCK1 option to the packet.
*/
int zoap_add_block1_option(struct zoap_packet *pkt,
struct zoap_block_context *ctx);
/**
* Add BLOCK2 option to the packet.
*/
int zoap_add_block2_option(struct zoap_packet *pkt,
struct zoap_block_context *ctx);
/**
* Add SIZE1 option to the packet.
*/
int zoap_add_size1_option(struct zoap_packet *pkt,
struct zoap_block_context *ctx);
/**
* Add SIZE2 option to the packet.
*/
int zoap_add_size2_option(struct zoap_packet *pkt,
struct zoap_block_context *ctx);
/**
* Retrieves BLOCK{1,2} and SIZE{1,2} from @a pkt and updates
* @a ctx accordingly.
*
* Returns an error if the packet contains invalid options.
*/
int zoap_update_from_block(struct zoap_packet *pkt,
struct zoap_block_context *ctx);
/**
* Updates @a ctx so after this is called the current entry
* indicates the correct offset in the body of data being
* transferred.
*/
size_t zoap_next_block(struct zoap_block_context *ctx);
/**
* Returns the version present in a CoAP packet.
*/