/* * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_coap, CONFIG_COAP_LOG_LEVEL); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define COAP_PATH_ELEM_DELIM '/' #define COAP_PATH_ELEM_QUERY '?' #define COAP_PATH_ELEM_AMP '&' /* Values as per RFC 7252, section-3.1. * * Option Delta/Length: 4-bit unsigned integer. A value between 0 and * 12 indicates the Option Delta/Length. Three values are reserved for * special constructs: * 13: An 8-bit unsigned integer precedes the Option Value and indicates * the Option Delta/Length minus 13. * 14: A 16-bit unsigned integer in network byte order precedes the * Option Value and indicates the Option Delta/Length minus 269. * 15: Reserved for future use. */ #define COAP_OPTION_NO_EXT 12 /* Option's Delta/Length without extended data */ #define COAP_OPTION_EXT_13 13 #define COAP_OPTION_EXT_14 14 #define COAP_OPTION_EXT_15 15 #define COAP_OPTION_EXT_269 269 /* CoAP Payload Marker */ #define COAP_MARKER 0xFF #define BASIC_HEADER_SIZE 4 /* The CoAP message ID that is incremented each time coap_next_id() is called. */ static uint16_t message_id; static struct coap_transmission_parameters coap_transmission_params = { .max_retransmission = CONFIG_COAP_MAX_RETRANSMIT, .ack_timeout = CONFIG_COAP_INIT_ACK_TIMEOUT_MS, .coap_backoff_percent = CONFIG_COAP_BACKOFF_PERCENT }; static int insert_option(struct coap_packet *cpkt, uint16_t code, const uint8_t *value, uint16_t len); static inline void encode_u8(struct coap_packet *cpkt, uint16_t offset, uint8_t data) { cpkt->data[offset] = data; ++cpkt->offset; } static inline void encode_be16(struct coap_packet *cpkt, uint16_t offset, uint16_t data) { cpkt->data[offset] = data >> 8; cpkt->data[offset + 1] = (uint8_t)data; cpkt->offset += 2; } static inline void encode_buffer(struct coap_packet *cpkt, uint16_t offset, const uint8_t *data, uint16_t len) { memcpy(cpkt->data + offset, data, len); cpkt->offset += len; } static bool enough_space(struct coap_packet *cpkt, const uint16_t bytes_to_add) { return (cpkt != NULL) && (cpkt->max_len - cpkt->offset >= bytes_to_add); } static inline bool append_u8(struct coap_packet *cpkt, uint8_t data) { if (!enough_space(cpkt, 1)) { return false; } encode_u8(cpkt, cpkt->offset, data); return true; } static inline bool insert_u8(struct coap_packet *cpkt, uint8_t data, uint16_t offset) { if (!enough_space(cpkt, 1)) { return false; } memmove(&cpkt->data[offset + 1], &cpkt->data[offset], cpkt->offset - offset); encode_u8(cpkt, offset, data); return true; } static inline bool append_be16(struct coap_packet *cpkt, uint16_t data) { if (!enough_space(cpkt, 2)) { return false; } encode_be16(cpkt, cpkt->offset, data); return true; } static inline bool insert_be16(struct coap_packet *cpkt, uint16_t data, size_t offset) { if (!enough_space(cpkt, 2)) { return false; } memmove(&cpkt->data[offset + 2], &cpkt->data[offset], cpkt->offset - offset); encode_be16(cpkt, cpkt->offset, data); return true; } static inline bool append(struct coap_packet *cpkt, const uint8_t *data, uint16_t len) { if (data == NULL || !enough_space(cpkt, len)) { return false; } encode_buffer(cpkt, cpkt->offset, data, len); return true; } static inline bool insert(struct coap_packet *cpkt, const uint8_t *data, uint16_t len, size_t offset) { if (data == NULL || !enough_space(cpkt, len)) { return false; } memmove(&cpkt->data[offset + len], &cpkt->data[offset], cpkt->offset - offset); encode_buffer(cpkt, offset, data, len); return true; } int coap_packet_init(struct coap_packet *cpkt, uint8_t *data, uint16_t max_len, uint8_t ver, uint8_t type, uint8_t token_len, const uint8_t *token, uint8_t code, uint16_t id) { uint8_t hdr; bool res; if (!cpkt || !data || !max_len) { return -EINVAL; } memset(cpkt, 0, sizeof(*cpkt)); cpkt->data = data; cpkt->offset = 0U; cpkt->max_len = max_len; cpkt->delta = 0U; hdr = (ver & 0x3) << 6; hdr |= (type & 0x3) << 4; hdr |= token_len & 0xF; res = append_u8(cpkt, hdr); if (!res) { return -EINVAL; } res = append_u8(cpkt, code); if (!res) { return -EINVAL; } res = append_be16(cpkt, id); if (!res) { return -EINVAL; } if (token && token_len) { res = append(cpkt, token, token_len); if (!res) { return -EINVAL; } } /* Header length : (version + type + tkl) + code + id + [token] */ cpkt->hdr_len = 1 + 1 + 2 + token_len; return 0; } int coap_ack_init(struct coap_packet *cpkt, const struct coap_packet *req, uint8_t *data, uint16_t max_len, uint8_t code) { uint16_t id; uint8_t ver; uint8_t tkl; uint8_t token[COAP_TOKEN_MAX_LEN]; ver = coap_header_get_version(req); id = coap_header_get_id(req); tkl = code ? coap_header_get_token(req, token) : 0; return coap_packet_init(cpkt, data, max_len, ver, COAP_TYPE_ACK, tkl, token, code, id); } static void option_header_set_delta(uint8_t *opt, uint8_t delta) { *opt = (delta & 0xF) << 4; } static void option_header_set_len(uint8_t *opt, uint8_t len) { *opt |= (len & 0xF); } static uint8_t encode_extended_option(uint16_t num, uint8_t *opt, uint16_t *ext) { if (num < COAP_OPTION_EXT_13) { *opt = num; *ext = 0U; return 0; } else if (num < COAP_OPTION_EXT_269) { *opt = COAP_OPTION_EXT_13; *ext = num - COAP_OPTION_EXT_13; return 1; } *opt = COAP_OPTION_EXT_14; *ext = num - COAP_OPTION_EXT_269; return 2; } /* Insert an option at position `offset`. This is not adjusting the code delta of the * option that follows the inserted one! */ static int encode_option(struct coap_packet *cpkt, uint16_t code, const uint8_t *value, uint16_t len, size_t offset) { uint16_t delta_ext; /* Extended delta */ uint16_t len_ext; /* Extended length */ uint8_t opt; /* delta | len */ uint8_t opt_delta; uint8_t opt_len; uint8_t delta_size; uint8_t len_size; bool res; delta_size = encode_extended_option(code, &opt_delta, &delta_ext); len_size = encode_extended_option(len, &opt_len, &len_ext); option_header_set_delta(&opt, opt_delta); option_header_set_len(&opt, opt_len); res = insert_u8(cpkt, opt, offset); ++offset; if (!res) { return -EINVAL; } if (delta_size == 1U) { res = insert_u8(cpkt, (uint8_t)delta_ext, offset); ++offset; if (!res) { return -EINVAL; } } else if (delta_size == 2U) { res = insert_be16(cpkt, delta_ext, offset); offset += 2; if (!res) { return -EINVAL; } } if (len_size == 1U) { res = insert_u8(cpkt, (uint8_t)len_ext, offset); ++offset; if (!res) { return -EINVAL; } } else if (len_size == 2U) { res = insert_be16(cpkt, len_ext, offset); offset += 2; if (!res) { return -EINVAL; } } if (len && value) { res = insert(cpkt, value, len, offset); /* no need to update local offset */ if (!res) { return -EINVAL; } } return (1 + delta_size + len_size + len); } int coap_packet_append_option(struct coap_packet *cpkt, uint16_t code, const uint8_t *value, uint16_t len) { int r; if (!cpkt) { return -EINVAL; } if (len && !value) { return -EINVAL; } if (code < cpkt->delta) { NET_DBG("Option is not added in ascending order"); return insert_option(cpkt, code, value, len); } /* Calculate delta, if this option is not the first one */ if (cpkt->opt_len) { code = (code == cpkt->delta) ? 0 : code - cpkt->delta; } r = encode_option(cpkt, code, value, len, cpkt->hdr_len + cpkt->opt_len); if (r < 0) { return -EINVAL; } cpkt->opt_len += r; cpkt->delta += code; return 0; } int coap_append_option_int(struct coap_packet *cpkt, uint16_t code, unsigned int val) { uint8_t data[4], len; if (val == 0U) { data[0] = 0U; len = 0U; } else if (val < 0xFF) { data[0] = (uint8_t) val; len = 1U; } else if (val < 0xFFFF) { sys_put_be16(val, data); len = 2U; } else if (val < 0xFFFFFF) { sys_put_be16(val, &data[1]); data[0] = val >> 16; len = 3U; } else { sys_put_be32(val, data); len = 4U; } return coap_packet_append_option(cpkt, code, data, len); } unsigned int coap_option_value_to_int(const struct coap_option *option) { switch (option->len) { case 0: return 0; case 1: return option->value[0]; case 2: return (option->value[1] << 0) | (option->value[0] << 8); case 3: return (option->value[2] << 0) | (option->value[1] << 8) | (option->value[0] << 16); case 4: return (option->value[3] << 0) | (option->value[2] << 8) | (option->value[1] << 16) | (option->value[0] << 24); default: return 0; } return 0; } int coap_packet_append_payload_marker(struct coap_packet *cpkt) { return append_u8(cpkt, COAP_MARKER) ? 0 : -EINVAL; } int coap_packet_append_payload(struct coap_packet *cpkt, const uint8_t *payload, uint16_t payload_len) { return append(cpkt, payload, payload_len) ? 0 : -EINVAL; } uint8_t *coap_next_token(void) { static uint8_t token[COAP_TOKEN_MAX_LEN]; sys_rand_get(token, COAP_TOKEN_MAX_LEN); return token; } static uint8_t option_header_get_delta(uint8_t opt) { return (opt & 0xF0) >> 4; } static uint8_t option_header_get_len(uint8_t opt) { return opt & 0x0F; } static int read_u8(uint8_t *data, uint16_t offset, uint16_t *pos, uint16_t max_len, uint8_t *value) { if (max_len - offset < 1) { return -EINVAL; } *value = data[offset++]; *pos = offset; return max_len - offset; } static int read_be16(uint8_t *data, uint16_t offset, uint16_t *pos, uint16_t max_len, uint16_t *value) { if (max_len - offset < 2) { return -EINVAL; } *value = data[offset++] << 8; *value |= data[offset++]; *pos = offset; return max_len - offset; } static int read(uint8_t *data, uint16_t offset, uint16_t *pos, uint16_t max_len, uint16_t len, uint8_t *value) { if (max_len - offset < len) { return -EINVAL; } memcpy(value, data + offset, len); offset += len; *pos = offset; return max_len - offset; } static int decode_delta(uint8_t *data, uint16_t offset, uint16_t *pos, uint16_t max_len, uint16_t opt, uint16_t *opt_ext, uint16_t *hdr_len) { int ret = 0; if (opt == COAP_OPTION_EXT_13) { uint8_t val; *hdr_len = 1U; ret = read_u8(data, offset, pos, max_len, &val); if (ret < 0) { return -EINVAL; } opt = val + COAP_OPTION_EXT_13; } else if (opt == COAP_OPTION_EXT_14) { uint16_t val; *hdr_len = 2U; ret = read_be16(data, offset, pos, max_len, &val); if (ret < 0) { return -EINVAL; } opt = val + COAP_OPTION_EXT_269; } else if (opt == COAP_OPTION_EXT_15) { return -EINVAL; } *opt_ext = opt; return ret; } static int parse_option(uint8_t *data, uint16_t offset, uint16_t *pos, uint16_t max_len, uint16_t *opt_delta, uint16_t *opt_len, struct coap_option *option) { uint16_t hdr_len; uint16_t delta; uint16_t len; uint8_t opt; int r; r = read_u8(data, offset, pos, max_len, &opt); if (r < 0) { return r; } /* This indicates that options have ended */ if (opt == COAP_MARKER) { /* packet w/ marker but no payload is malformed */ return r > 0 ? 0 : -EINVAL; } *opt_len += 1U; delta = option_header_get_delta(opt); len = option_header_get_len(opt); /* r == 0 means no more data to read from fragment, but delta * field shows that packet should contain more data, it must * be a malformed packet. */ if (r == 0 && delta > COAP_OPTION_NO_EXT) { return -EINVAL; } if (delta > COAP_OPTION_NO_EXT) { /* In case 'delta' doesn't fit the option fixed header. */ r = decode_delta(data, *pos, pos, max_len, delta, &delta, &hdr_len); if ((r < 0) || (r == 0 && len > COAP_OPTION_NO_EXT)) { return -EINVAL; } if (u16_add_overflow(*opt_len, hdr_len, opt_len)) { return -EINVAL; } } if (len > COAP_OPTION_NO_EXT) { /* In case 'len' doesn't fit the option fixed header. */ r = decode_delta(data, *pos, pos, max_len, len, &len, &hdr_len); if (r < 0) { return -EINVAL; } if (u16_add_overflow(*opt_len, hdr_len, opt_len)) { return -EINVAL; } } if (u16_add_overflow(*opt_delta, delta, opt_delta) || u16_add_overflow(*opt_len, len, opt_len)) { return -EINVAL; } if (r == 0 && len != 0U) { /* r == 0 means no more data to read from fragment, but len * field shows that packet should contain more data, it must * be a malformed packet. */ return -EINVAL; } if (option) { /* * Make sure the option data will fit into the value field of * coap_option. * NOTE: To expand the size of the value field set: * CONFIG_COAP_EXTENDED_OPTIONS_LEN=y * CONFIG_COAP_EXTENDED_OPTIONS_LEN_VALUE= */ if (len > sizeof(option->value)) { NET_ERR("%u is > sizeof(coap_option->value)(%zu)!", len, sizeof(option->value)); return -EINVAL; } option->delta = *opt_delta; option->len = len; r = read(data, *pos, pos, max_len, len, &option->value[0]); if (r < 0) { return -EINVAL; } } else { if (u16_add_overflow(*pos, len, pos)) { return -EINVAL; } r = max_len - *pos; } return r; } /* Remove the raw data of an option. Also adjusting offsets. * But not adjusting code delta of the option after the removed one. */ static void remove_option_data(struct coap_packet *cpkt, const uint16_t to_offset, const uint16_t from_offset) { const uint16_t move_size = from_offset - to_offset; memmove(cpkt->data + to_offset, cpkt->data + from_offset, cpkt->offset - from_offset); cpkt->opt_len -= move_size; cpkt->offset -= move_size; } /* Remove an option (that is not the last one). * Also adjusting the code delta of the option following the removed one. */ static int remove_middle_option(struct coap_packet *cpkt, uint16_t offset, uint16_t opt_delta, const uint16_t previous_offset, const uint16_t previous_code) { int r; struct coap_option option; uint16_t opt_len = 0; /* get the option after the removed one */ r = parse_option(cpkt->data, offset, &offset, cpkt->hdr_len + cpkt->opt_len, &opt_delta, &opt_len, &option); if (r < 0) { return -EILSEQ; } /* clear requested option and the one after (delta changed) */ remove_option_data(cpkt, previous_offset, offset); /* reinsert option that comes after the removed option (with adjusted delta) */ r = encode_option(cpkt, option.delta - previous_code, option.value, option.len, previous_offset); if (r < 0) { return -EINVAL; } cpkt->opt_len += r; return 0; } int coap_packet_remove_option(struct coap_packet *cpkt, uint16_t code) { uint16_t offset = 0; uint16_t opt_delta = 0; uint16_t opt_len = 0; uint16_t previous_offset = 0; uint16_t previous_code = 0; struct coap_option option; int r; if (!cpkt) { return -EINVAL; } if (cpkt->opt_len == 0) { return 0; } if (code > cpkt->delta) { return 0; } offset = cpkt->hdr_len; previous_offset = cpkt->hdr_len; /* Find the requested option */ while (offset < cpkt->hdr_len + cpkt->opt_len) { r = parse_option(cpkt->data, offset, &offset, cpkt->hdr_len + cpkt->opt_len, &opt_delta, &opt_len, &option); if (r < 0) { return -EILSEQ; } if (opt_delta == code) { break; } if (opt_delta > code) { return 0; } previous_code = opt_delta; previous_offset = offset; } /* Check if the found option is the last option */ if (cpkt->opt_len > opt_len) { /* not last option */ r = remove_middle_option(cpkt, offset, opt_delta, previous_offset, previous_code); if (r < 0) { return r; } } else { /* last option */ remove_option_data(cpkt, previous_offset, cpkt->hdr_len + cpkt->opt_len); cpkt->delta = previous_code; } return 0; } int coap_packet_parse(struct coap_packet *cpkt, uint8_t *data, uint16_t len, struct coap_option *options, uint8_t opt_num) { uint16_t opt_len; uint16_t offset; uint16_t delta; uint8_t num; uint8_t tkl; int ret; if (!cpkt || !data) { return -EINVAL; } if (len < BASIC_HEADER_SIZE) { return -EINVAL; } if (options) { memset(options, 0, opt_num * sizeof(struct coap_option)); } cpkt->data = data; cpkt->offset = len; cpkt->max_len = len; cpkt->opt_len = 0U; cpkt->hdr_len = 0U; cpkt->delta = 0U; /* Token lengths 9-15 are reserved. */ tkl = cpkt->data[0] & 0x0f; if (tkl > 8) { return -EBADMSG; } cpkt->hdr_len = BASIC_HEADER_SIZE + tkl; if (cpkt->hdr_len > len) { return -EBADMSG; } if (cpkt->hdr_len == len) { return 0; } offset = cpkt->hdr_len; opt_len = 0U; delta = 0U; num = 0U; while (1) { struct coap_option *option; option = num < opt_num ? &options[num++] : NULL; ret = parse_option(cpkt->data, offset, &offset, cpkt->max_len, &delta, &opt_len, option); if (ret < 0) { return -EILSEQ; } else if (ret == 0) { break; } } cpkt->opt_len = opt_len; cpkt->delta = delta; return 0; } int coap_packet_set_path(struct coap_packet *cpkt, const char *path) { int ret = 0; int path_start, path_end; int path_length; bool contains_query = false; int i; path_start = 0; path_end = 0; path_length = strlen(path); for (i = 0; i < path_length; i++) { path_end = i; if (path[i] == COAP_PATH_ELEM_DELIM) { /* Guard for preceding delimiters */ if (path_start < path_end) { ret = coap_packet_append_option(cpkt, COAP_OPTION_URI_PATH, path + path_start, path_end - path_start); if (ret < 0) { LOG_ERR("Failed to append path to CoAP message"); goto out; } } /* Check if there is a new path after delimiter, * if not, point to the end of string to not add * new option after this */ if (path_length > i + 1) { path_start = i + 1; } else { path_start = path_length; } } else if (path[i] == COAP_PATH_ELEM_QUERY) { /* Guard for preceding delimiters */ if (path_start < path_end) { ret = coap_packet_append_option(cpkt, COAP_OPTION_URI_PATH, path + path_start, path_end - path_start); if (ret < 0) { LOG_ERR("Failed to append path to CoAP message"); goto out; } } /* Rest of the path is query */ contains_query = true; if (path_length > i + 1) { path_start = i + 1; } else { path_start = path_length; } break; } } if (contains_query) { for (i = path_start; i < path_length; i++) { path_end = i; if (path[i] == COAP_PATH_ELEM_AMP || path[i] == COAP_PATH_ELEM_QUERY) { /* Guard for preceding delimiters */ if (path_start < path_end) { ret = coap_packet_append_option(cpkt, COAP_OPTION_URI_QUERY, path + path_start, path_end - path_start); if (ret < 0) { LOG_ERR("Failed to append path to CoAP message"); goto out; } } /* Check if there is a new query option after delimiter, * if not, point to the end of string to not add * new option after this */ if (path_length > i + 1) { path_start = i + 1; } else { path_start = path_length; } } } } if (path_start < path_length) { if (contains_query) { ret = coap_packet_append_option(cpkt, COAP_OPTION_URI_QUERY, path + path_start, path_end - path_start + 1); } else { ret = coap_packet_append_option(cpkt, COAP_OPTION_URI_PATH, path + path_start, path_end - path_start + 1); } if (ret < 0) { LOG_ERR("Failed to append path to CoAP message"); goto out; } } out: return ret; } int coap_find_options(const struct coap_packet *cpkt, uint16_t code, struct coap_option *options, uint16_t veclen) { uint16_t opt_len; uint16_t offset; uint16_t delta; uint8_t num; int r; /* Check if there are options to parse */ if (cpkt->hdr_len == cpkt->max_len) { return 0; } offset = cpkt->hdr_len; opt_len = 0U; delta = 0U; num = 0U; while (delta <= code && num < veclen) { r = parse_option(cpkt->data, offset, &offset, cpkt->max_len, &delta, &opt_len, &options[num]); if (r < 0) { return -EINVAL; } if (code == options[num].delta) { num++; } if (r == 0) { break; } } return num; } uint8_t coap_header_get_version(const struct coap_packet *cpkt) { if (!cpkt || !cpkt->data) { return 0; } return (cpkt->data[0] & 0xC0) >> 6; } uint8_t coap_header_get_type(const struct coap_packet *cpkt) { if (!cpkt || !cpkt->data) { return 0; } return (cpkt->data[0] & 0x30) >> 4; } static uint8_t __coap_header_get_code(const struct coap_packet *cpkt) { if (!cpkt || !cpkt->data) { return 0; } return cpkt->data[1]; } int coap_header_set_code(const struct coap_packet *cpkt, uint8_t code) { if (!cpkt || !cpkt->data) { return -EINVAL; } cpkt->data[1] = code; return 0; } uint8_t coap_header_get_token(const struct coap_packet *cpkt, uint8_t *token) { uint8_t tkl; if (!cpkt || !cpkt->data) { return 0; } tkl = cpkt->data[0] & 0x0f; if (tkl > COAP_TOKEN_MAX_LEN) { return 0; } if (tkl) { memcpy(token, cpkt->data + BASIC_HEADER_SIZE, tkl); } return tkl; } uint8_t coap_header_get_code(const struct coap_packet *cpkt) { uint8_t code = __coap_header_get_code(cpkt); switch (code) { /* Methods are encoded in the code field too */ case COAP_METHOD_GET: case COAP_METHOD_POST: case COAP_METHOD_PUT: case COAP_METHOD_DELETE: case COAP_METHOD_FETCH: case COAP_METHOD_PATCH: case COAP_METHOD_IPATCH: /* All the defined response codes */ case COAP_RESPONSE_CODE_OK: case COAP_RESPONSE_CODE_CREATED: case COAP_RESPONSE_CODE_DELETED: case COAP_RESPONSE_CODE_VALID: case COAP_RESPONSE_CODE_CHANGED: case COAP_RESPONSE_CODE_CONTENT: case COAP_RESPONSE_CODE_CONTINUE: case COAP_RESPONSE_CODE_BAD_REQUEST: case COAP_RESPONSE_CODE_UNAUTHORIZED: case COAP_RESPONSE_CODE_BAD_OPTION: case COAP_RESPONSE_CODE_FORBIDDEN: case COAP_RESPONSE_CODE_NOT_FOUND: case COAP_RESPONSE_CODE_NOT_ALLOWED: case COAP_RESPONSE_CODE_NOT_ACCEPTABLE: case COAP_RESPONSE_CODE_INCOMPLETE: case COAP_RESPONSE_CODE_CONFLICT: case COAP_RESPONSE_CODE_PRECONDITION_FAILED: case COAP_RESPONSE_CODE_REQUEST_TOO_LARGE: case COAP_RESPONSE_CODE_UNSUPPORTED_CONTENT_FORMAT: case COAP_RESPONSE_CODE_UNPROCESSABLE_ENTITY: case COAP_RESPONSE_CODE_TOO_MANY_REQUESTS: case COAP_RESPONSE_CODE_INTERNAL_ERROR: case COAP_RESPONSE_CODE_NOT_IMPLEMENTED: case COAP_RESPONSE_CODE_BAD_GATEWAY: case COAP_RESPONSE_CODE_SERVICE_UNAVAILABLE: case COAP_RESPONSE_CODE_GATEWAY_TIMEOUT: case COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED: case COAP_CODE_EMPTY: return code; default: return COAP_CODE_EMPTY; } } uint16_t coap_header_get_id(const struct coap_packet *cpkt) { if (!cpkt || !cpkt->data) { return 0; } return (cpkt->data[2] << 8) | cpkt->data[3]; } const uint8_t *coap_packet_get_payload(const struct coap_packet *cpkt, uint16_t *len) { int payload_len; if (!cpkt || !len) { return NULL; } payload_len = cpkt->offset - cpkt->hdr_len - cpkt->opt_len; if (payload_len > 1) { *len = payload_len - 1; /* subtract payload marker length */ } else { *len = 0U; } return *len == 0 ? NULL : cpkt->data + cpkt->hdr_len + cpkt->opt_len + 1; } bool coap_uri_path_match(const char * const *path, struct coap_option *options, uint8_t opt_num) { uint8_t i; uint8_t j = 0U; for (i = 0U; i < opt_num && path[j]; i++) { if (options[i].delta != COAP_OPTION_URI_PATH) { continue; } if (IS_ENABLED(CONFIG_COAP_URI_WILDCARD) && strlen(path[j]) == 1) { if (*path[j] == '+') { /* Single-level wildcard */ j++; continue; } else if (*path[j] == '#') { /* Multi-level wildcard */ return true; } } if (options[i].len != strlen(path[j])) { return false; } if (memcmp(options[i].value, path[j], options[i].len)) { return false; } j++; } if (path[j]) { return false; } for (; i < opt_num; i++) { if (options[i].delta == COAP_OPTION_URI_PATH) { return false; } } return true; } static int method_from_code(const struct coap_resource *resource, uint8_t code, coap_method_t *method) { switch (code) { case COAP_METHOD_GET: *method = resource->get; return 0; case COAP_METHOD_POST: *method = resource->post; return 0; case COAP_METHOD_PUT: *method = resource->put; return 0; case COAP_METHOD_DELETE: *method = resource->del; return 0; case COAP_METHOD_FETCH: *method = resource->fetch; return 0; case COAP_METHOD_PATCH: *method = resource->patch; return 0; case COAP_METHOD_IPATCH: *method = resource->ipatch; return 0; default: return -EINVAL; } } static inline bool is_empty_message(const struct coap_packet *cpkt) { return __coap_header_get_code(cpkt) == COAP_CODE_EMPTY; } bool coap_packet_is_request(const struct coap_packet *cpkt) { uint8_t code = coap_header_get_code(cpkt); return !(code & ~COAP_REQUEST_MASK); } int coap_handle_request_len(struct coap_packet *cpkt, struct coap_resource *resources, size_t resources_len, struct coap_option *options, uint8_t opt_num, struct sockaddr *addr, socklen_t addr_len) { if (!coap_packet_is_request(cpkt)) { return 0; } /* FIXME: deal with hierarchical resources */ for (size_t i = 0; i < resources_len; i++) { coap_method_t method; uint8_t code; if (!coap_uri_path_match(resources[i].path, options, opt_num)) { continue; } code = coap_header_get_code(cpkt); if (method_from_code(&resources[i], code, &method) < 0) { return -ENOTSUP; } if (!method) { return -EPERM; } return method(&resources[i], cpkt, addr, addr_len); } return -ENOENT; } int coap_handle_request(struct coap_packet *cpkt, struct coap_resource *resources, struct coap_option *options, uint8_t opt_num, struct sockaddr *addr, socklen_t addr_len) { size_t resources_len = 0; struct coap_resource *resource; for (resource = resources; resource && resource->path; resource++) { resources_len++; } return coap_handle_request_len(cpkt, resources, resources_len, options, opt_num, addr, addr_len); } int coap_block_transfer_init(struct coap_block_context *ctx, enum coap_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)) int coap_append_descriptive_block_option(struct coap_packet *cpkt, struct coap_block_context *ctx) { if (coap_packet_is_request(cpkt)) { return coap_append_block1_option(cpkt, ctx); } else { return coap_append_block2_option(cpkt, ctx); } } bool coap_has_descriptive_block_option(struct coap_packet *cpkt) { if (coap_packet_is_request(cpkt)) { return coap_get_option_int(cpkt, COAP_OPTION_BLOCK1) >= 0; } else { return coap_get_option_int(cpkt, COAP_OPTION_BLOCK2) >= 0; } } int coap_remove_descriptive_block_option(struct coap_packet *cpkt) { if (coap_packet_is_request(cpkt)) { return coap_packet_remove_option(cpkt, COAP_OPTION_BLOCK1); } else { return coap_packet_remove_option(cpkt, COAP_OPTION_BLOCK2); } } int coap_append_block1_option(struct coap_packet *cpkt, struct coap_block_context *ctx) { uint16_t bytes = coap_block_size_to_bytes(ctx->block_size); unsigned int val = 0U; int r; if (coap_packet_is_request(cpkt)) { 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 = coap_append_option_int(cpkt, COAP_OPTION_BLOCK1, val); return r; } int coap_append_block2_option(struct coap_packet *cpkt, struct coap_block_context *ctx) { int r, val = 0; uint16_t bytes = coap_block_size_to_bytes(ctx->block_size); if (coap_packet_is_request(cpkt)) { 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 = coap_append_option_int(cpkt, COAP_OPTION_BLOCK2, val); return r; } int coap_append_size1_option(struct coap_packet *cpkt, struct coap_block_context *ctx) { return coap_append_option_int(cpkt, COAP_OPTION_SIZE1, ctx->total_size); } int coap_append_size2_option(struct coap_packet *cpkt, struct coap_block_context *ctx) { return coap_append_option_int(cpkt, COAP_OPTION_SIZE2, ctx->total_size); } int coap_get_option_int(const struct coap_packet *cpkt, uint16_t code) { struct coap_option option = {}; unsigned int val; int count = 1; count = coap_find_options(cpkt, code, &option, count); if (count <= 0) { return -ENOENT; } val = coap_option_value_to_int(&option); return val; } int coap_get_block1_option(const struct coap_packet *cpkt, bool *has_more, uint8_t *block_number) { int ret = coap_get_option_int(cpkt, COAP_OPTION_BLOCK1); if (ret < 0) { return ret; } *has_more = GET_MORE(ret); *block_number = GET_NUM(ret); ret = 1 << (GET_BLOCK_SIZE(ret) + 4); return ret; } int coap_get_block2_option(const struct coap_packet *cpkt, uint8_t *block_number) { int ret = coap_get_option_int(cpkt, COAP_OPTION_BLOCK2); if (ret < 0) { return ret; } *block_number = GET_NUM(ret); ret = 1 << (GET_BLOCK_SIZE(ret) + 4); return ret; } int insert_option(struct coap_packet *cpkt, uint16_t code, const uint8_t *value, uint16_t len) { uint16_t offset = cpkt->hdr_len; uint16_t opt_delta = 0; uint16_t opt_len = 0; uint16_t last_opt = 0; uint16_t last_offset = cpkt->hdr_len; struct coap_option option; int r; while (offset < cpkt->hdr_len + cpkt->opt_len) { r = parse_option(cpkt->data, offset, &offset, cpkt->hdr_len + cpkt->opt_len, &opt_delta, &opt_len, &option); if (r < 0) { return -EILSEQ; } if (opt_delta > code) { break; } last_opt = opt_delta; last_offset = offset; } const uint16_t option_size = offset - last_offset; /* clear option after new option (delta changed) */ memmove(cpkt->data + last_offset, cpkt->data + offset, cpkt->offset - offset); cpkt->opt_len -= option_size; cpkt->offset -= option_size; /* add the new option */ const uint16_t new_option_delta = code - last_opt; r = encode_option(cpkt, new_option_delta, value, len, last_offset); if (r < 0) { return -EINVAL; } cpkt->opt_len += r; /* reinsert option that comes after the new option (with adjusted delta) */ r = encode_option(cpkt, option.delta - code, option.value, option.len, last_offset + r); if (r < 0) { return -EINVAL; } cpkt->opt_len += r; return 0; } static int update_descriptive_block(struct coap_block_context *ctx, int block, int size) { size_t new_current = GET_NUM(block) << (GET_BLOCK_SIZE(block) + 4); if (block == -ENOENT) { return 0; } if (size && ctx->total_size && ctx->total_size != size) { return -EINVAL; } if (ctx->current > 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 = MIN(GET_BLOCK_SIZE(block), ctx->block_size); return 0; } static int update_control_block1(struct coap_block_context *ctx, int block, int size) { size_t new_current = GET_NUM(block) << (GET_BLOCK_SIZE(block) + 4); if (block == -ENOENT) { 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); if (size >= 0) { ctx->total_size = size; } return 0; } static int update_control_block2(struct coap_block_context *ctx, int block, int size) { size_t new_current = GET_NUM(block) << (GET_BLOCK_SIZE(block) + 4); if (block == -ENOENT) { 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 = MIN(GET_BLOCK_SIZE(block), ctx->block_size); return 0; } int coap_update_from_block(const struct coap_packet *cpkt, struct coap_block_context *ctx) { int r, block1, block2, size1, size2; block1 = coap_get_option_int(cpkt, COAP_OPTION_BLOCK1); block2 = coap_get_option_int(cpkt, COAP_OPTION_BLOCK2); size1 = coap_get_option_int(cpkt, COAP_OPTION_SIZE1); size2 = coap_get_option_int(cpkt, COAP_OPTION_SIZE2); if (coap_packet_is_request(cpkt)) { r = update_control_block2(ctx, block2, size2); if (r) { return r; } return update_descriptive_block(ctx, block1, size1 == -ENOENT ? 0 : size1); } r = update_control_block1(ctx, block1, size1); if (r) { return r; } return update_descriptive_block(ctx, block2, size2 == -ENOENT ? 0 : size2); } int coap_next_block_for_option(const struct coap_packet *cpkt, struct coap_block_context *ctx, enum coap_option_num option) { int block; uint16_t block_len; if (option != COAP_OPTION_BLOCK1 && option != COAP_OPTION_BLOCK2) { return -EINVAL; } block = coap_get_option_int(cpkt, option); if (block < 0) { return block; } coap_packet_get_payload(cpkt, &block_len); /* Check that the package does not exceed the expected size ONLY */ if ((ctx->total_size > 0) && (ctx->total_size < (ctx->current + block_len))) { return -EMSGSIZE; } ctx->current += block_len; if (!GET_MORE(block)) { return 0; } return (int)ctx->current; } size_t coap_next_block(const struct coap_packet *cpkt, struct coap_block_context *ctx) { enum coap_option_num option; int ret; option = coap_packet_is_request(cpkt) ? COAP_OPTION_BLOCK1 : COAP_OPTION_BLOCK2; ret = coap_next_block_for_option(cpkt, ctx, option); return MAX(ret, 0); } int coap_pending_init(struct coap_pending *pending, const struct coap_packet *request, const struct sockaddr *addr, const struct coap_transmission_parameters *params) { memset(pending, 0, sizeof(*pending)); pending->id = coap_header_get_id(request); memcpy(&pending->addr, addr, sizeof(*addr)); if (params) { pending->params = *params; } else { pending->params = coap_transmission_params; } pending->data = request->data; pending->len = request->offset; pending->t0 = k_uptime_get(); pending->retries = pending->params.max_retransmission; return 0; } struct coap_pending *coap_pending_next_unused( struct coap_pending *pendings, size_t len) { struct coap_pending *p; size_t i; for (i = 0, p = pendings; i < len; i++, p++) { if (p->data == 0) { return p; } } return NULL; } struct coap_reply *coap_reply_next_unused( struct coap_reply *replies, size_t len) { struct coap_reply *r; size_t i; for (i = 0, r = replies; i < len; i++, r++) { if (!r->reply) { return r; } } return NULL; } static inline bool is_addr_unspecified(const struct sockaddr *addr) { if (addr->sa_family == AF_UNSPEC) { return true; } if (addr->sa_family == AF_INET6) { return net_ipv6_is_addr_unspecified( &(net_sin6(addr)->sin6_addr)); } else if (addr->sa_family == AF_INET) { return net_sin(addr)->sin_addr.s4_addr32[0] == 0U; } return false; } struct coap_observer *coap_observer_next_unused( struct coap_observer *observers, size_t len) { struct coap_observer *o; size_t i; for (i = 0, o = observers; i < len; i++, o++) { if (is_addr_unspecified(&o->addr)) { return o; } } return NULL; } struct coap_pending *coap_pending_received( const struct coap_packet *response, struct coap_pending *pendings, size_t len) { struct coap_pending *p; uint16_t resp_id = coap_header_get_id(response); size_t i; for (i = 0, p = pendings; i < len; i++, p++) { if (!p->timeout) { continue; } if (resp_id != p->id) { continue; } return p; } return NULL; } struct coap_pending *coap_pending_next_to_expire( struct coap_pending *pendings, size_t len) { struct coap_pending *p, *found = NULL; size_t i; int64_t expiry, min_expiry = INT64_MAX; for (i = 0, p = pendings; i < len; i++, p++) { if (!p->timeout) { continue; } expiry = p->t0 + p->timeout; if (expiry < min_expiry) { min_expiry = expiry; found = p; } } return found; } static uint32_t init_ack_timeout(const struct coap_transmission_parameters *params) { #if defined(CONFIG_COAP_RANDOMIZE_ACK_TIMEOUT) const uint32_t max_ack = params->ack_timeout * CONFIG_COAP_ACK_RANDOM_PERCENT / 100; const uint32_t min_ack = params->ack_timeout; /* Randomly generated initial ACK timeout * ACK_TIMEOUT < INIT_ACK_TIMEOUT < ACK_TIMEOUT * ACK_RANDOM_FACTOR * Ref: https://tools.ietf.org/html/rfc7252#section-4.8 */ return min_ack + (sys_rand32_get() % (max_ack - min_ack)); #else return params->ack_timeout; #endif /* defined(CONFIG_COAP_RANDOMIZE_ACK_TIMEOUT) */ } bool coap_pending_cycle(struct coap_pending *pending) { if (pending->timeout == 0) { /* Initial transmission. */ pending->timeout = init_ack_timeout(&pending->params); return true; } if (pending->retries == 0) { return false; } pending->t0 += pending->timeout; pending->timeout = pending->timeout * pending->params.coap_backoff_percent / 100; pending->retries--; return true; } void coap_pending_clear(struct coap_pending *pending) { pending->timeout = 0; pending->data = NULL; } void coap_pendings_clear(struct coap_pending *pendings, size_t len) { struct coap_pending *p; size_t i; for (i = 0, p = pendings; i < len; i++, p++) { coap_pending_clear(p); } } size_t coap_pendings_count(struct coap_pending *pendings, size_t len) { struct coap_pending *p = pendings; size_t c = 0; for (size_t i = 0; i < len && p; i++, p++) { if (p->timeout) { c++; } } return c; } /* Reordering according to RFC7641 section 3.4 but without timestamp comparison */ static inline bool is_newer(int v1, int v2) { return (v1 < v2 && v2 - v1 < (1 << 23)) || (v1 > v2 && v1 - v2 > (1 << 23)); } struct coap_reply *coap_response_received( const struct coap_packet *response, const struct sockaddr *from, struct coap_reply *replies, size_t len) { struct coap_reply *r; uint8_t token[COAP_TOKEN_MAX_LEN]; uint16_t id; uint8_t tkl; size_t i; if (!is_empty_message(response) && coap_packet_is_request(response)) { /* Request can't be response */ return NULL; } id = coap_header_get_id(response); tkl = coap_header_get_token(response, token); for (i = 0, r = replies; i < len; i++, r++) { int age; if ((r->id == 0U) && (r->tkl == 0U)) { continue; } /* Piggybacked must match id when token is empty */ if ((r->id != id) && (tkl == 0U)) { continue; } if (tkl > 0 && memcmp(r->token, token, tkl)) { continue; } age = coap_get_option_int(response, COAP_OPTION_OBSERVE); /* handle observed requests only if received in order */ if (age == -ENOENT || is_newer(r->age, age)) { r->age = age; if (coap_header_get_code(response) != COAP_RESPONSE_CODE_CONTINUE) { r->reply(response, r, from); } } return r; } return NULL; } void coap_reply_init(struct coap_reply *reply, const struct coap_packet *request) { uint8_t token[COAP_TOKEN_MAX_LEN]; uint8_t tkl; reply->id = coap_header_get_id(request); tkl = coap_header_get_token(request, token); if (tkl > 0) { memcpy(reply->token, token, tkl); } reply->tkl = tkl; /* Any initial observe response should be accepted */ reply->age = -1; } void coap_reply_clear(struct coap_reply *reply) { (void)memset(reply, 0, sizeof(*reply)); } void coap_replies_clear(struct coap_reply *replies, size_t len) { struct coap_reply *r; size_t i; for (i = 0, r = replies; i < len; i++, r++) { coap_reply_clear(r); } } int coap_resource_notify(struct coap_resource *resource) { struct coap_observer *o; if (!resource->notify) { return -ENOENT; } resource->age++; SYS_SLIST_FOR_EACH_CONTAINER(&resource->observers, o, list) { resource->notify(resource, o); } return 0; } bool coap_request_is_observe(const struct coap_packet *request) { return coap_get_option_int(request, COAP_OPTION_OBSERVE) == 0; } void coap_observer_init(struct coap_observer *observer, const struct coap_packet *request, const struct sockaddr *addr) { observer->tkl = coap_header_get_token(request, observer->token); net_ipaddr_copy(&observer->addr, addr); } static inline void coap_observer_raise_event(struct coap_resource *resource, struct coap_observer *observer, uint32_t mgmt_event) { #ifdef CONFIG_NET_MGMT_EVENT_INFO const struct net_event_coap_observer net_event = { .resource = resource, .observer = observer, }; net_mgmt_event_notify_with_info(mgmt_event, NULL, (void *)&net_event, sizeof(net_event)); #else ARG_UNUSED(resource); ARG_UNUSED(observer); net_mgmt_event_notify(mgmt_event, NULL); #endif } bool coap_register_observer(struct coap_resource *resource, struct coap_observer *observer) { bool first; sys_slist_append(&resource->observers, &observer->list); first = resource->age == 0; if (first) { resource->age = 2; } coap_observer_raise_event(resource, observer, NET_EVENT_COAP_OBSERVER_ADDED); return first; } bool coap_remove_observer(struct coap_resource *resource, struct coap_observer *observer) { if (!sys_slist_find_and_remove(&resource->observers, &observer->list)) { return false; } coap_observer_raise_event(resource, observer, NET_EVENT_COAP_OBSERVER_REMOVED); return true; } static bool sockaddr_equal(const struct sockaddr *a, const struct sockaddr *b) { /* FIXME: Should we consider ipv6-mapped ipv4 addresses as equal to * ipv4 addresses? */ if (a->sa_family != b->sa_family) { return false; } if (a->sa_family == AF_INET) { const struct sockaddr_in *a4 = net_sin(a); const struct sockaddr_in *b4 = net_sin(b); if (a4->sin_port != b4->sin_port) { return false; } return net_ipv4_addr_cmp(&a4->sin_addr, &b4->sin_addr); } if (b->sa_family == AF_INET6) { const struct sockaddr_in6 *a6 = net_sin6(a); const struct sockaddr_in6 *b6 = net_sin6(b); if (a6->sin6_port != b6->sin6_port) { return false; } return net_ipv6_addr_cmp(&a6->sin6_addr, &b6->sin6_addr); } /* Invalid address family */ return false; } struct coap_observer *coap_find_observer( struct coap_observer *observers, size_t len, const struct sockaddr *addr, const uint8_t *token, uint8_t token_len) { if (token_len == 0U || token_len > COAP_TOKEN_MAX_LEN) { return NULL; } for (size_t i = 0; i < len; i++) { struct coap_observer *o = &observers[i]; if (o->tkl == token_len && memcmp(o->token, token, token_len) == 0 && sockaddr_equal(&o->addr, addr)) { return o; } } return NULL; } struct coap_observer *coap_find_observer_by_addr( struct coap_observer *observers, size_t len, const struct sockaddr *addr) { size_t i; for (i = 0; i < len; i++) { struct coap_observer *o = &observers[i]; if (sockaddr_equal(&o->addr, addr)) { return o; } } return NULL; } struct coap_observer *coap_find_observer_by_token( struct coap_observer *observers, size_t len, const uint8_t *token, uint8_t token_len) { if (token_len == 0U || token_len > COAP_TOKEN_MAX_LEN) { return NULL; } for (size_t i = 0; i < len; i++) { struct coap_observer *o = &observers[i]; if (o->tkl == token_len && memcmp(o->token, token, token_len) == 0) { return o; } } return NULL; } /** * @brief Internal initialization function for CoAP library. * * Called by the network layer init procedure. Seeds the CoAP @message_id with a * random number in accordance with recommendations in CoAP specification. * * @note This function is not exposed in a public header, as it's for internal * use and should therefore not be exposed to applications. */ void net_coap_init(void) { /* Initialize message_id to a random number */ message_id = (uint16_t)sys_rand32_get(); } uint16_t coap_next_id(void) { return message_id++; } struct coap_transmission_parameters coap_get_transmission_parameters(void) { return coap_transmission_params; } void coap_set_transmission_parameters(const struct coap_transmission_parameters *params) { coap_transmission_params = *params; }