/* * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_DECLARE(net_coap, CONFIG_COAP_LOG_LEVEL); #include #include #include #include #include #include #include #include #include #include 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[] = "", 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 = "'); 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