| /* |
| * Copyright (c) 2018 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <logging/log.h> |
| LOG_MODULE_DECLARE(net_coap, CONFIG_COAP_LOG_LEVEL); |
| |
| #include <stdlib.h> |
| #include <stddef.h> |
| #include <zephyr/types.h> |
| #include <string.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| |
| #include <sys/byteorder.h> |
| |
| #include <sys/printk.h> |
| |
| #include <net/coap.h> |
| #include <net/coap_link_format.h> |
| |
| static inline bool append_u8(struct coap_packet *cpkt, uint8_t data) |
| { |
| if (!cpkt) { |
| return false; |
| } |
| |
| if (cpkt->max_len - cpkt->offset < 1) { |
| return false; |
| } |
| |
| cpkt->data[cpkt->offset++] = data; |
| |
| return true; |
| } |
| |
| static inline bool append_be16(struct coap_packet *cpkt, uint16_t data) |
| { |
| if (!cpkt) { |
| return false; |
| } |
| |
| if (cpkt->max_len - cpkt->offset < 2) { |
| return false; |
| } |
| |
| cpkt->data[cpkt->offset++] = data >> 8; |
| cpkt->data[cpkt->offset++] = (uint8_t) data; |
| |
| return true; |
| } |
| |
| static inline bool append(struct coap_packet *cpkt, const uint8_t *data, uint16_t len) |
| { |
| if (!cpkt || !data) { |
| return false; |
| } |
| |
| if (cpkt->max_len - cpkt->offset < len) { |
| return false; |
| } |
| |
| memcpy(cpkt->data + cpkt->offset, data, len); |
| cpkt->offset += len; |
| |
| return true; |
| } |
| |
| static bool match_path_uri(const char * const *path, |
| const char *uri, uint16_t len) |
| { |
| const char * const *p = NULL; |
| int i, j, k, plen; |
| |
| if (!path) { |
| return false; |
| } |
| |
| if (len <= 1U || uri[0] != '/') { |
| return false; |
| } |
| |
| p = path; |
| plen = *p ? strlen(*p) : 0; |
| j = 0; |
| |
| if (plen == 0) { |
| return false; |
| } |
| |
| /* Go through uri and try to find a matching path */ |
| for (i = 1; i < len; i++) { |
| while (*p) { |
| plen = strlen(*p); |
| |
| k = i; |
| |
| for (j = 0; j < plen; j++) { |
| if (uri[k] == '*') { |
| if ((k + 1) == len) { |
| return true; |
| } |
| } |
| |
| if (uri[k] != (*p)[j]) { |
| goto next; |
| } |
| |
| k++; |
| } |
| |
| if (i == (k - 1) && j == plen) { |
| return true; |
| } |
| |
| if (k == len && j == plen) { |
| return true; |
| } |
| |
| next: |
| p++; |
| } |
| } |
| |
| /* Did we find the resource or not */ |
| if (i == len && !*p) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool match_attributes(const char * const *attributes, |
| const struct coap_option *query) |
| { |
| const char * const *attr; |
| |
| /* FIXME: deal with the case when there are multiple options in a |
| * query, for example: 'rt=lux temperature', if I want to list |
| * resources with resource type lux or temperature. |
| */ |
| for (attr = attributes; attr && *attr; attr++) { |
| uint16_t attr_len = strlen(*attr); |
| |
| if (query->len != attr_len) { |
| continue; |
| } |
| |
| if (!strncmp((char *) query->value, *attr, attr_len)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool match_queries_resource(const struct coap_resource *resource, |
| const struct coap_option *query, |
| int num_queries) |
| { |
| struct coap_core_metadata *meta = resource->user_data; |
| const char * const *attributes = NULL; |
| const int href_len = strlen("href"); |
| |
| if (num_queries == 0) { |
| return true; |
| } |
| |
| if (meta && meta->attributes) { |
| attributes = meta->attributes; |
| } |
| |
| if (!attributes) { |
| return false; |
| } |
| |
| if (query->len > href_len + 1 && |
| !strncmp((char *) query->value, "href", href_len)) { |
| /* The stuff after 'href=' */ |
| const char *uri = (char *) query->value + href_len + 1; |
| uint16_t uri_len = query->len - (href_len + 1); |
| |
| return match_path_uri(resource->path, uri, uri_len); |
| } |
| |
| return match_attributes(attributes, query); |
| } |
| |
| #if defined(CONFIG_COAP_WELL_KNOWN_BLOCK_WISE) |
| |
| #define MAX_BLOCK_WISE_TRANSFER_SIZE 2048 |
| |
| enum coap_block_size default_block_size(void) |
| { |
| switch (CONFIG_COAP_WELL_KNOWN_BLOCK_WISE_SIZE) { |
| case 16: |
| return COAP_BLOCK_16; |
| case 32: |
| return COAP_BLOCK_32; |
| case 64: |
| return COAP_BLOCK_64; |
| case 128: |
| return COAP_BLOCK_128; |
| case 256: |
| return COAP_BLOCK_256; |
| case 512: |
| return COAP_BLOCK_512; |
| case 1024: |
| return COAP_BLOCK_1024; |
| } |
| |
| return COAP_BLOCK_64; |
| } |
| |
| static bool append_to_coap_pkt(struct coap_packet *response, |
| const char *str, uint16_t len, |
| uint16_t *remaining, size_t *offset, |
| size_t current) |
| { |
| uint16_t pos = 0U; |
| bool res; |
| |
| if (!*remaining) { |
| return true; |
| } |
| |
| if (*offset < current) { |
| pos = current - *offset; |
| |
| if (len >= pos) { |
| len -= pos; |
| *offset += pos; |
| } else { |
| *offset += len; |
| return true; |
| } |
| } |
| |
| if (len > *remaining) { |
| len = *remaining; |
| } |
| |
| res = append(response, str + pos, len); |
| |
| *remaining -= len; |
| *offset += len; |
| |
| return res; |
| } |
| |
| static int format_uri(const char * const *path, |
| struct coap_packet *response, |
| uint16_t *remaining, size_t *offset, |
| size_t current, bool *more) |
| { |
| static const char prefix[] = "</"; |
| const char * const *p; |
| bool res; |
| |
| if (!path) { |
| return -EINVAL; |
| } |
| |
| res = append_to_coap_pkt(response, &prefix[0], sizeof(prefix) - 1, |
| remaining, offset, current); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| if (!*remaining) { |
| *more = true; |
| return 0; |
| } |
| |
| for (p = path; *p; ) { |
| uint16_t path_len = strlen(*p); |
| |
| res = append_to_coap_pkt(response, *p, path_len, remaining, |
| offset, current); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| if (!*remaining) { |
| *more = true; |
| return 0; |
| } |
| |
| p++; |
| if (!*p) { |
| continue; |
| } |
| |
| res = append_to_coap_pkt(response, "/", 1, remaining, offset, |
| current); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| if (!*remaining) { |
| *more = true; |
| return 0; |
| } |
| |
| } |
| |
| res = append_to_coap_pkt(response, ">", 1, remaining, offset, current); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| if (!*remaining) { |
| *more = true; |
| return 0; |
| } |
| |
| *more = false; |
| |
| return 0; |
| } |
| |
| static int format_attributes(const char * const *attributes, |
| struct coap_packet *response, |
| uint16_t *remaining, size_t *offset, |
| size_t current, bool *more) |
| { |
| const char * const *attr; |
| bool res; |
| |
| if (!attributes) { |
| *more = false; |
| return 0; |
| } |
| |
| for (attr = attributes; *attr; attr++) { |
| int attr_len; |
| |
| res = append_to_coap_pkt(response, ";", 1, |
| remaining, offset, current); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| if (!*remaining) { |
| *more = true; |
| return 0; |
| } |
| |
| attr_len = strlen(*attr); |
| |
| res = append_to_coap_pkt(response, *attr, attr_len, |
| remaining, offset, current); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| if (*(attr + 1) && !*remaining) { |
| *more = true; |
| return 0; |
| } |
| } |
| |
| *more = false; |
| return 0; |
| } |
| |
| static int format_resource(const struct coap_resource *resource, |
| struct coap_packet *response, |
| uint16_t *remaining, size_t *offset, |
| size_t current, bool *more) |
| { |
| struct coap_core_metadata *meta = resource->user_data; |
| const char * const *attributes = NULL; |
| int r; |
| |
| r = format_uri(resource->path, response, remaining, |
| offset, current, more); |
| if (r < 0) { |
| return r; |
| } |
| |
| if (!*remaining) { |
| *more = true; |
| return 0; |
| } |
| |
| if (meta && meta->attributes) { |
| attributes = meta->attributes; |
| } |
| |
| return format_attributes(attributes, response, remaining, offset, |
| current, more); |
| } |
| |
| /* coap_well_known_core_get() added Option (delta and len) with |
| * out any extended options so this function will not consider Extended |
| * options at the moment. |
| */ |
| int clear_more_flag(struct coap_packet *cpkt) |
| { |
| uint16_t offset; |
| uint8_t opt; |
| uint8_t delta; |
| uint8_t len; |
| |
| offset = cpkt->hdr_len; |
| delta = 0U; |
| |
| while (1) { |
| opt = cpkt->data[offset++]; |
| |
| delta += ((opt & 0xF0) >> 4); |
| len = (opt & 0xF); |
| |
| if (delta == COAP_OPTION_BLOCK2) { |
| break; |
| } |
| |
| offset += len; |
| } |
| |
| /* As per RFC 7959 Sec 2.2 : NUM filed can be on 0-3 bytes. |
| * Skip NUM field to update M bit. |
| */ |
| if (len > 1) { |
| offset = offset + len - 1; |
| } |
| |
| cpkt->data[offset] = cpkt->data[offset] & 0xF7; |
| |
| return 0; |
| } |
| |
| int coap_well_known_core_get(struct coap_resource *resource, |
| struct coap_packet *request, |
| struct coap_packet *response, |
| uint8_t *data, uint16_t len) |
| { |
| static struct coap_block_context ctx; |
| struct coap_option query; |
| unsigned int num_queries; |
| size_t offset; |
| uint8_t token[COAP_TOKEN_MAX_LEN]; |
| uint16_t remaining; |
| uint16_t id; |
| uint8_t tkl; |
| int r; |
| bool more = false; |
| |
| if (!resource || !request || !response || !data || !len) { |
| return -EINVAL; |
| } |
| |
| if (ctx.total_size == 0) { |
| /* We have to iterate through resources and it's attributes, |
| * total size is unknown, so initialize it to |
| * MAX_BLOCK_WISE_TRANSFER_SIZE and update it according to |
| * offset. |
| */ |
| coap_block_transfer_init(&ctx, default_block_size(), |
| MAX_BLOCK_WISE_TRANSFER_SIZE); |
| } |
| |
| r = coap_update_from_block(request, &ctx); |
| if (r < 0) { |
| goto end; |
| } |
| |
| id = coap_header_get_id(request); |
| tkl = coap_header_get_token(request, token); |
| |
| /* Per RFC 6690, Section 4.1, only one (or none) query parameter may be |
| * provided, use the first if multiple. |
| */ |
| r = coap_find_options(request, COAP_OPTION_URI_QUERY, &query, 1); |
| if (r < 0) { |
| goto end; |
| } |
| |
| num_queries = r; |
| |
| r = coap_packet_init(response, data, len, COAP_VERSION_1, COAP_TYPE_ACK, |
| tkl, token, COAP_RESPONSE_CODE_CONTENT, id); |
| if (r < 0) { |
| goto end; |
| } |
| |
| r = coap_append_option_int(response, COAP_OPTION_CONTENT_FORMAT, |
| COAP_CONTENT_FORMAT_APP_LINK_FORMAT); |
| if (r < 0) { |
| goto end; |
| } |
| |
| r = coap_append_block2_option(response, &ctx); |
| if (r < 0) { |
| goto end; |
| } |
| |
| r = coap_packet_append_payload_marker(response); |
| if (r < 0) { |
| goto end; |
| } |
| |
| offset = 0; |
| remaining = coap_block_size_to_bytes(ctx.block_size); |
| |
| while (resource++ && resource->path) { |
| if (!remaining) { |
| more = true; |
| break; |
| } |
| |
| if (!match_queries_resource(resource, &query, num_queries)) { |
| continue; |
| } |
| |
| r = format_resource(resource, response, &remaining, &offset, |
| ctx.current, &more); |
| if (r < 0) { |
| goto end; |
| } |
| |
| if ((resource + 1) && (resource + 1)->path) { |
| r = append_to_coap_pkt(response, ",", 1, &remaining, |
| &offset, ctx.current); |
| if (!r) { |
| goto end; |
| } |
| } |
| } |
| |
| /* Offset is the total size now, but block2 option is already |
| * appended. So update only 'more' flag. |
| */ |
| if (!more) { |
| ctx.total_size = offset; |
| r = clear_more_flag(response); |
| } |
| |
| end: |
| /* So it's a last block, reset context */ |
| if (!more) { |
| (void)memset(&ctx, 0, sizeof(ctx)); |
| } |
| |
| return r; |
| } |
| |
| #else |
| |
| static int format_uri(const char * const *path, struct coap_packet *response) |
| { |
| const char * const *p; |
| char *prefix = "</"; |
| bool res; |
| |
| if (!path) { |
| return -EINVAL; |
| } |
| |
| res = append(response, (uint8_t *) prefix, strlen(prefix)); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| for (p = path; *p; ) { |
| res = append(response, (uint8_t *) *p, strlen(*p)); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| p++; |
| if (*p) { |
| res = append_u8(response, (uint8_t) '/'); |
| if (!res) { |
| return -ENOMEM; |
| } |
| } |
| } |
| |
| res = append_u8(response, (uint8_t) '>'); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int format_attributes(const char * const *attributes, |
| struct coap_packet *response) |
| { |
| const char * const *attr; |
| bool res; |
| |
| if (!attributes) { |
| return 0; |
| } |
| |
| for (attr = attributes; *attr; attr++) { |
| res = append_u8(response, (uint8_t) ';'); |
| if (!res) { |
| return -ENOMEM; |
| } |
| |
| res = append(response, (uint8_t *) *attr, strlen(*attr)); |
| if (!res) { |
| return -ENOMEM; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int format_resource(const struct coap_resource *resource, |
| struct coap_packet *response) |
| { |
| struct coap_core_metadata *meta = resource->user_data; |
| const char * const *attributes = NULL; |
| int r; |
| |
| r = format_uri(resource->path, response); |
| if (r < 0) { |
| return r; |
| } |
| |
| if (meta && meta->attributes) { |
| attributes = meta->attributes; |
| } |
| |
| return format_attributes(attributes, response); |
| } |
| |
| int coap_well_known_core_get(struct coap_resource *resource, |
| struct coap_packet *request, |
| struct coap_packet *response, |
| uint8_t *data, uint16_t len) |
| { |
| struct coap_option query; |
| uint8_t token[COAP_TOKEN_MAX_LEN]; |
| uint16_t id; |
| uint8_t tkl; |
| uint8_t num_queries; |
| int r; |
| |
| if (!resource || !request || !response || !data || !len) { |
| return -EINVAL; |
| } |
| |
| id = coap_header_get_id(request); |
| tkl = coap_header_get_token(request, token); |
| |
| /* Per RFC 6690, Section 4.1, only one (or none) query parameter may be |
| * provided, use the first if multiple. |
| */ |
| r = coap_find_options(request, COAP_OPTION_URI_QUERY, &query, 1); |
| if (r < 0) { |
| return r; |
| } |
| |
| num_queries = r; |
| |
| r = coap_packet_init(response, data, len, COAP_VERSION_1, COAP_TYPE_ACK, |
| tkl, token, COAP_RESPONSE_CODE_CONTENT, id); |
| if (r < 0) { |
| return r; |
| } |
| |
| r = coap_append_option_int(response, COAP_OPTION_CONTENT_FORMAT, |
| COAP_CONTENT_FORMAT_APP_LINK_FORMAT); |
| if (r < 0) { |
| return -EINVAL; |
| } |
| |
| r = coap_packet_append_payload_marker(response); |
| if (r < 0) { |
| return -EINVAL; |
| } |
| |
| while (resource++ && resource->path) { |
| if (!match_queries_resource(resource, &query, num_queries)) { |
| continue; |
| } |
| |
| r = format_resource(resource, response); |
| if (r < 0) { |
| return r; |
| } |
| |
| if ((resource + 1) && (resource + 1)->path) { |
| r = append_u8(response, (uint8_t) ','); |
| if (!r) { |
| return -ENOMEM; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| /* Exposing some of the APIs to CoAP unit tests in tests/net/lib/coap */ |
| #if defined(CONFIG_COAP_TEST_API_ENABLE) |
| bool _coap_match_path_uri(const char * const *path, |
| const char *uri, uint16_t len) |
| { |
| return match_path_uri(path, uri, len); |
| } |
| #endif |