mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-18 10:59:45 +00:00
In certain files it was necessary to make tests work from public mongoose repo, so this commit makes things consistent. PUBLISHED_FROM=694454d0ff007229c65d524a2a2beaf126420f15
598 lines
15 KiB
C
598 lines
15 KiB
C
/*
|
|
* Copyright (c) 2015 Cesanta Software Limited
|
|
* All rights reserved
|
|
* This software is dual-licensed: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation. For the terms of this
|
|
* license, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* You are free to use this software under the terms of the GNU General
|
|
* Public License, but WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* Alternatively, you can license this software under a commercial
|
|
* license, as set out in <https://www.cesanta.com/license>.
|
|
*/
|
|
|
|
#include "mg_internal.h"
|
|
#include "mg_coap.h"
|
|
|
|
#if MG_ENABLE_COAP
|
|
|
|
void mg_coap_free_options(struct mg_coap_message *cm) {
|
|
while (cm->options != NULL) {
|
|
struct mg_coap_option *next = cm->options->next;
|
|
MG_FREE(cm->options);
|
|
cm->options = next;
|
|
}
|
|
}
|
|
|
|
struct mg_coap_option *mg_coap_add_option(struct mg_coap_message *cm,
|
|
uint32_t number, char *value,
|
|
size_t len) {
|
|
struct mg_coap_option *new_option =
|
|
(struct mg_coap_option *) MG_CALLOC(1, sizeof(*new_option));
|
|
|
|
new_option->number = number;
|
|
new_option->value.p = value;
|
|
new_option->value.len = len;
|
|
|
|
if (cm->options == NULL) {
|
|
cm->options = cm->optiomg_tail = new_option;
|
|
} else {
|
|
/*
|
|
* A very simple attention to help clients to compose options:
|
|
* CoAP wants to see options ASC ordered.
|
|
* Could be change by using sort in coap_compose
|
|
*/
|
|
if (cm->optiomg_tail->number <= new_option->number) {
|
|
/* if option is already ordered just add it */
|
|
cm->optiomg_tail = cm->optiomg_tail->next = new_option;
|
|
} else {
|
|
/* looking for appropriate position */
|
|
struct mg_coap_option *current_opt = cm->options;
|
|
struct mg_coap_option *prev_opt = 0;
|
|
|
|
while (current_opt != NULL) {
|
|
if (current_opt->number > new_option->number) {
|
|
break;
|
|
}
|
|
prev_opt = current_opt;
|
|
current_opt = current_opt->next;
|
|
}
|
|
|
|
if (prev_opt != NULL) {
|
|
prev_opt->next = new_option;
|
|
new_option->next = current_opt;
|
|
} else {
|
|
/* insert new_option to the beginning */
|
|
new_option->next = cm->options;
|
|
cm->options = new_option;
|
|
}
|
|
}
|
|
}
|
|
|
|
return new_option;
|
|
}
|
|
|
|
/*
|
|
* Fills CoAP header in mg_coap_message.
|
|
*
|
|
* Helper function.
|
|
*/
|
|
static char *coap_parse_header(char *ptr, struct mbuf *io,
|
|
struct mg_coap_message *cm) {
|
|
if (io->len < sizeof(uint32_t)) {
|
|
cm->flags |= MG_COAP_NOT_ENOUGH_DATA;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Version (Ver): 2-bit unsigned integer. Indicates the CoAP version
|
|
* number. Implementations of this specification MUST set this field
|
|
* to 1 (01 binary). Other values are reserved for future versions.
|
|
* Messages with unknown version numbers MUST be silently ignored.
|
|
*/
|
|
if (((uint8_t) *ptr >> 6) != 1) {
|
|
cm->flags |= MG_COAP_IGNORE;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Type (T): 2-bit unsigned integer. Indicates if this message is of
|
|
* type Confirmable (0), Non-confirmable (1), Acknowledgement (2), or
|
|
* Reset (3).
|
|
*/
|
|
cm->msg_type = ((uint8_t) *ptr & 0x30) >> 4;
|
|
cm->flags |= MG_COAP_MSG_TYPE_FIELD;
|
|
|
|
/*
|
|
* Token Length (TKL): 4-bit unsigned integer. Indicates the length of
|
|
* the variable-length Token field (0-8 bytes). Lengths 9-15 are
|
|
* reserved, MUST NOT be sent, and MUST be processed as a message
|
|
* format error.
|
|
*/
|
|
cm->token.len = *ptr & 0x0F;
|
|
if (cm->token.len > 8) {
|
|
cm->flags |= MG_COAP_FORMAT_ERROR;
|
|
return NULL;
|
|
}
|
|
|
|
ptr++;
|
|
|
|
/*
|
|
* Code: 8-bit unsigned integer, split into a 3-bit class (most
|
|
* significant bits) and a 5-bit detail (least significant bits)
|
|
*/
|
|
cm->code_class = (uint8_t) *ptr >> 5;
|
|
cm->code_detail = *ptr & 0x1F;
|
|
cm->flags |= (MG_COAP_CODE_CLASS_FIELD | MG_COAP_CODE_DETAIL_FIELD);
|
|
|
|
ptr++;
|
|
|
|
/* Message ID: 16-bit unsigned integer in network byte order. */
|
|
cm->msg_id = (uint8_t) *ptr << 8 | (uint8_t) * (ptr + 1);
|
|
cm->flags |= MG_COAP_MSG_ID_FIELD;
|
|
|
|
ptr += 2;
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
* Fills token information in mg_coap_message.
|
|
*
|
|
* Helper function.
|
|
*/
|
|
static char *coap_get_token(char *ptr, struct mbuf *io,
|
|
struct mg_coap_message *cm) {
|
|
if (cm->token.len != 0) {
|
|
if (ptr + cm->token.len > io->buf + io->len) {
|
|
cm->flags |= MG_COAP_NOT_ENOUGH_DATA;
|
|
return NULL;
|
|
} else {
|
|
cm->token.p = ptr;
|
|
ptr += cm->token.len;
|
|
cm->flags |= MG_COAP_TOKEN_FIELD;
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
* Returns Option Delta or Length.
|
|
*
|
|
* Helper function.
|
|
*/
|
|
static int coap_get_ext_opt(char *ptr, struct mbuf *io, uint16_t *opt_info) {
|
|
int ret = 0;
|
|
|
|
if (*opt_info == 13) {
|
|
/*
|
|
* 13: An 8-bit unsigned integer follows the initial byte and
|
|
* indicates the Option Delta/Length minus 13.
|
|
*/
|
|
if (ptr < io->buf + io->len) {
|
|
*opt_info = (uint8_t) *ptr + 13;
|
|
ret = sizeof(uint8_t);
|
|
} else {
|
|
ret = -1; /* LCOV_EXCL_LINE */
|
|
}
|
|
} else if (*opt_info == 14) {
|
|
/*
|
|
* 14: A 16-bit unsigned integer in network byte order follows the
|
|
* initial byte and indicates the Option Delta/Length minus 269.
|
|
*/
|
|
if (ptr + sizeof(uint8_t) < io->buf + io->len) {
|
|
*opt_info = ((uint8_t) *ptr << 8 | (uint8_t) * (ptr + 1)) + 269;
|
|
ret = sizeof(uint16_t);
|
|
} else {
|
|
ret = -1; /* LCOV_EXCL_LINE */
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Fills options in mg_coap_message.
|
|
*
|
|
* Helper function.
|
|
*
|
|
* General options format:
|
|
* +---------------+---------------+
|
|
* | Option Delta | Option Length | 1 byte
|
|
* +---------------+---------------+
|
|
* \ Option Delta (extended) \ 0-2 bytes
|
|
* +-------------------------------+
|
|
* / Option Length (extended) \ 0-2 bytes
|
|
* +-------------------------------+
|
|
* \ Option Value \ 0 or more bytes
|
|
* +-------------------------------+
|
|
*/
|
|
static char *coap_get_options(char *ptr, struct mbuf *io,
|
|
struct mg_coap_message *cm) {
|
|
uint16_t prev_opt = 0;
|
|
|
|
if (ptr == io->buf + io->len) {
|
|
/* end of packet, ok */
|
|
return NULL;
|
|
}
|
|
|
|
/* 0xFF is payload marker */
|
|
while (ptr < io->buf + io->len && (uint8_t) *ptr != 0xFF) {
|
|
uint16_t option_delta, option_lenght;
|
|
int optinfo_len;
|
|
|
|
/* Option Delta: 4-bit unsigned integer */
|
|
option_delta = ((uint8_t) *ptr & 0xF0) >> 4;
|
|
/* Option Length: 4-bit unsigned integer */
|
|
option_lenght = *ptr & 0x0F;
|
|
|
|
if (option_delta == 15 || option_lenght == 15) {
|
|
/*
|
|
* 15: Reserved for future use. If the field is set to this value,
|
|
* it MUST be processed as a message format error
|
|
*/
|
|
cm->flags |= MG_COAP_FORMAT_ERROR;
|
|
break;
|
|
}
|
|
|
|
ptr++;
|
|
|
|
/* check for extended option delta */
|
|
optinfo_len = coap_get_ext_opt(ptr, io, &option_delta);
|
|
if (optinfo_len == -1) {
|
|
cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */
|
|
break; /* LCOV_EXCL_LINE */
|
|
}
|
|
|
|
ptr += optinfo_len;
|
|
|
|
/* check or extended option lenght */
|
|
optinfo_len = coap_get_ext_opt(ptr, io, &option_lenght);
|
|
if (optinfo_len == -1) {
|
|
cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */
|
|
break; /* LCOV_EXCL_LINE */
|
|
}
|
|
|
|
ptr += optinfo_len;
|
|
|
|
/*
|
|
* Instead of specifying the Option Number directly, the instances MUST
|
|
* appear in order of their Option Numbers and a delta encoding is used
|
|
* between them.
|
|
*/
|
|
option_delta += prev_opt;
|
|
|
|
mg_coap_add_option(cm, option_delta, ptr, option_lenght);
|
|
|
|
prev_opt = option_delta;
|
|
|
|
if (ptr + option_lenght > io->buf + io->len) {
|
|
cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */
|
|
break; /* LCOV_EXCL_LINE */
|
|
}
|
|
|
|
ptr += option_lenght;
|
|
}
|
|
|
|
if ((cm->flags & MG_COAP_ERROR) != 0) {
|
|
mg_coap_free_options(cm);
|
|
return NULL;
|
|
}
|
|
|
|
cm->flags |= MG_COAP_OPTIOMG_FIELD;
|
|
|
|
if (ptr == io->buf + io->len) {
|
|
/* end of packet, ok */
|
|
return NULL;
|
|
}
|
|
|
|
ptr++;
|
|
|
|
return ptr;
|
|
}
|
|
|
|
uint32_t mg_coap_parse(struct mbuf *io, struct mg_coap_message *cm) {
|
|
char *ptr;
|
|
|
|
memset(cm, 0, sizeof(*cm));
|
|
|
|
if ((ptr = coap_parse_header(io->buf, io, cm)) == NULL) {
|
|
return cm->flags;
|
|
}
|
|
|
|
if ((ptr = coap_get_token(ptr, io, cm)) == NULL) {
|
|
return cm->flags;
|
|
}
|
|
|
|
if ((ptr = coap_get_options(ptr, io, cm)) == NULL) {
|
|
return cm->flags;
|
|
}
|
|
|
|
/* the rest is payload */
|
|
cm->payload.len = io->len - (ptr - io->buf);
|
|
if (cm->payload.len != 0) {
|
|
cm->payload.p = ptr;
|
|
cm->flags |= MG_COAP_PAYLOAD_FIELD;
|
|
}
|
|
|
|
return cm->flags;
|
|
}
|
|
|
|
/*
|
|
* Calculates extended size of given Opt Number/Length in coap message.
|
|
*
|
|
* Helper function.
|
|
*/
|
|
static size_t coap_get_ext_opt_size(uint32_t value) {
|
|
int ret = 0;
|
|
|
|
if (value >= 13 && value <= 0xFF + 13) {
|
|
ret = sizeof(uint8_t);
|
|
} else if (value > 0xFF + 13 && value <= 0xFFFF + 269) {
|
|
ret = sizeof(uint16_t);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Splits given Opt Number/Length into base and ext values.
|
|
*
|
|
* Helper function.
|
|
*/
|
|
static int coap_split_opt(uint32_t value, uint8_t *base, uint16_t *ext) {
|
|
int ret = 0;
|
|
|
|
if (value < 13) {
|
|
*base = value;
|
|
} else if (value >= 13 && value <= 0xFF + 13) {
|
|
*base = 13;
|
|
*ext = value - 13;
|
|
ret = sizeof(uint8_t);
|
|
} else if (value > 0xFF + 13 && value <= 0xFFFF + 269) {
|
|
*base = 14;
|
|
*ext = value - 269;
|
|
ret = sizeof(uint16_t);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Puts uint16_t (in network order) into given char stream.
|
|
*
|
|
* Helper function.
|
|
*/
|
|
static char *coap_add_uint16(char *ptr, uint16_t val) {
|
|
*ptr = val >> 8;
|
|
ptr++;
|
|
*ptr = val & 0x00FF;
|
|
ptr++;
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
* Puts extended value of Opt Number/Length into given char stream.
|
|
*
|
|
* Helper function.
|
|
*/
|
|
static char *coap_add_opt_info(char *ptr, uint16_t val, size_t len) {
|
|
if (len == sizeof(uint8_t)) {
|
|
*ptr = (char) val;
|
|
ptr++;
|
|
} else if (len == sizeof(uint16_t)) {
|
|
ptr = coap_add_uint16(ptr, val);
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
* Verifies given mg_coap_message and calculates message size for it.
|
|
*
|
|
* Helper function.
|
|
*/
|
|
static uint32_t coap_calculate_packet_size(struct mg_coap_message *cm,
|
|
size_t *len) {
|
|
struct mg_coap_option *opt;
|
|
uint32_t prev_opt_number;
|
|
|
|
*len = 4; /* header */
|
|
if (cm->msg_type > MG_COAP_MSG_MAX) {
|
|
return MG_COAP_ERROR | MG_COAP_MSG_TYPE_FIELD;
|
|
}
|
|
if (cm->token.len > 8) {
|
|
return MG_COAP_ERROR | MG_COAP_TOKEN_FIELD;
|
|
}
|
|
if (cm->code_class > 7) {
|
|
return MG_COAP_ERROR | MG_COAP_CODE_CLASS_FIELD;
|
|
}
|
|
if (cm->code_detail > 31) {
|
|
return MG_COAP_ERROR | MG_COAP_CODE_DETAIL_FIELD;
|
|
}
|
|
|
|
*len += cm->token.len;
|
|
if (cm->payload.len != 0) {
|
|
*len += cm->payload.len + 1; /* ... + 1; add payload marker */
|
|
}
|
|
|
|
opt = cm->options;
|
|
prev_opt_number = 0;
|
|
while (opt != NULL) {
|
|
*len += 1; /* basic delta/length */
|
|
*len += coap_get_ext_opt_size(opt->number - prev_opt_number);
|
|
*len += coap_get_ext_opt_size((uint32_t) opt->value.len);
|
|
/*
|
|
* Current implementation performs check if
|
|
* option_number > previous option_number and produces an error
|
|
* TODO(alashkin): write design doc with limitations
|
|
* May be resorting is more suitable solution.
|
|
*/
|
|
if ((opt->next != NULL && opt->number > opt->next->number) ||
|
|
opt->value.len > 0xFFFF + 269 ||
|
|
opt->number - prev_opt_number > 0xFFFF + 269) {
|
|
return MG_COAP_ERROR | MG_COAP_OPTIOMG_FIELD;
|
|
}
|
|
*len += opt->value.len;
|
|
prev_opt_number = opt->number;
|
|
opt = opt->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io) {
|
|
struct mg_coap_option *opt;
|
|
uint32_t res, prev_opt_number;
|
|
size_t prev_io_len, packet_size;
|
|
char *ptr;
|
|
|
|
res = coap_calculate_packet_size(cm, &packet_size);
|
|
if (res != 0) {
|
|
return res;
|
|
}
|
|
|
|
/* saving previous lenght to handle non-empty mbuf */
|
|
prev_io_len = io->len;
|
|
if (mbuf_append(io, NULL, packet_size) == 0) return MG_COAP_ERROR;
|
|
ptr = io->buf + prev_io_len;
|
|
|
|
/*
|
|
* since cm is verified, it is possible to use bits shift operator
|
|
* without additional zeroing of unused bits
|
|
*/
|
|
|
|
/* ver: 2 bits, msg_type: 2 bits, toklen: 4 bits */
|
|
*ptr = (1 << 6) | (cm->msg_type << 4) | (uint8_t)(cm->token.len);
|
|
ptr++;
|
|
|
|
/* code class: 3 bits, code detail: 5 bits */
|
|
*ptr = (cm->code_class << 5) | (cm->code_detail);
|
|
ptr++;
|
|
|
|
ptr = coap_add_uint16(ptr, cm->msg_id);
|
|
|
|
if (cm->token.len != 0) {
|
|
memcpy(ptr, cm->token.p, cm->token.len);
|
|
ptr += cm->token.len;
|
|
}
|
|
|
|
opt = cm->options;
|
|
prev_opt_number = 0;
|
|
while (opt != NULL) {
|
|
uint8_t delta_base = 0, length_base = 0;
|
|
uint16_t delta_ext = 0, length_ext = 0;
|
|
|
|
size_t opt_delta_len =
|
|
coap_split_opt(opt->number - prev_opt_number, &delta_base, &delta_ext);
|
|
size_t opt_lenght_len =
|
|
coap_split_opt((uint32_t) opt->value.len, &length_base, &length_ext);
|
|
|
|
*ptr = (delta_base << 4) | length_base;
|
|
ptr++;
|
|
|
|
ptr = coap_add_opt_info(ptr, delta_ext, opt_delta_len);
|
|
ptr = coap_add_opt_info(ptr, length_ext, opt_lenght_len);
|
|
|
|
if (opt->value.len != 0) {
|
|
memcpy(ptr, opt->value.p, opt->value.len);
|
|
ptr += opt->value.len;
|
|
}
|
|
|
|
prev_opt_number = opt->number;
|
|
opt = opt->next;
|
|
}
|
|
|
|
if (cm->payload.len != 0) {
|
|
*ptr = (char) -1;
|
|
ptr++;
|
|
memcpy(ptr, cm->payload.p, cm->payload.len);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t mg_coap_send_message(struct mg_connection *nc,
|
|
struct mg_coap_message *cm) {
|
|
struct mbuf packet_out;
|
|
uint32_t compose_res;
|
|
|
|
mbuf_init(&packet_out, 0);
|
|
compose_res = mg_coap_compose(cm, &packet_out);
|
|
if (compose_res != 0) {
|
|
return compose_res; /* LCOV_EXCL_LINE */
|
|
}
|
|
|
|
mg_send(nc, packet_out.buf, (int) packet_out.len);
|
|
mbuf_free(&packet_out);
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t mg_coap_send_ack(struct mg_connection *nc, uint16_t msg_id) {
|
|
struct mg_coap_message cm;
|
|
memset(&cm, 0, sizeof(cm));
|
|
cm.msg_type = MG_COAP_MSG_ACK;
|
|
cm.msg_id = msg_id;
|
|
|
|
return mg_coap_send_message(nc, &cm);
|
|
}
|
|
|
|
static void coap_handler(struct mg_connection *nc, int ev,
|
|
void *ev_data MG_UD_ARG(void *user_data)) {
|
|
struct mbuf *io = &nc->recv_mbuf;
|
|
struct mg_coap_message cm;
|
|
uint32_t parse_res;
|
|
|
|
memset(&cm, 0, sizeof(cm));
|
|
|
|
nc->handler(nc, ev, ev_data MG_UD_ARG(user_data));
|
|
|
|
switch (ev) {
|
|
case MG_EV_RECV:
|
|
parse_res = mg_coap_parse(io, &cm);
|
|
if ((parse_res & MG_COAP_IGNORE) == 0) {
|
|
if ((cm.flags & MG_COAP_NOT_ENOUGH_DATA) != 0) {
|
|
/*
|
|
* Since we support UDP only
|
|
* MG_COAP_NOT_ENOUGH_DATA == MG_COAP_FORMAT_ERROR
|
|
*/
|
|
cm.flags |= MG_COAP_FORMAT_ERROR; /* LCOV_EXCL_LINE */
|
|
} /* LCOV_EXCL_LINE */
|
|
nc->handler(nc, MG_COAP_EVENT_BASE + cm.msg_type,
|
|
&cm MG_UD_ARG(user_data));
|
|
}
|
|
|
|
mg_coap_free_options(&cm);
|
|
mbuf_remove(io, io->len);
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
* Attach built-in CoAP event handler to the given connection.
|
|
*
|
|
* The user-defined event handler will receive following extra events:
|
|
*
|
|
* - MG_EV_COAP_CON
|
|
* - MG_EV_COAP_NOC
|
|
* - MG_EV_COAP_ACK
|
|
* - MG_EV_COAP_RST
|
|
*/
|
|
int mg_set_protocol_coap(struct mg_connection *nc) {
|
|
/* supports UDP only */
|
|
if ((nc->flags & MG_F_UDP) == 0) {
|
|
return -1;
|
|
}
|
|
|
|
nc->proto_handler = coap_handler;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* MG_ENABLE_COAP */
|