zephyr/subsys/net/lib/zoap/zoap_link_format.c

727 lines
14 KiB
C

/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stddef.h>
#include <zephyr/types.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <misc/byteorder.h>
#include <net/buf.h>
#include <net/net_pkt.h>
#include <misc/printk.h>
#include <net/zoap.h>
#include <net/zoap_link_format.h>
#define PKT_WAIT_TIME K_SECONDS(1)
/* CoAP End Of Options Marker */
#define COAP_MARKER 0xFF
static bool match_path_uri(const char * const *path,
const char *uri, u16_t len)
{
const char * const *p = NULL;
int i, j, k, plen;
if (!path) {
return false;
}
if (len <= 1 || 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 zoap_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++) {
u16_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 zoap_resource *resource,
const struct zoap_option *query,
int num_queries)
{
struct zoap_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;
u16_t uri_len = query->len - (href_len + 1);
return match_path_uri(resource->path, uri, uri_len);
}
return match_attributes(attributes, query);
}
static int send_error_response(struct zoap_resource *resource,
struct zoap_packet *request,
const struct sockaddr *from)
{
struct net_context *context;
struct zoap_packet response;
struct net_pkt *pkt;
struct net_buf *frag;
u16_t id;
int r;
id = zoap_header_get_id(request);
context = net_pkt_context(request->pkt);
pkt = net_pkt_get_tx(context, PKT_WAIT_TIME);
if (!pkt) {
return -ENOMEM;
}
frag = net_pkt_get_data(context, PKT_WAIT_TIME);
if (!frag) {
net_pkt_unref(pkt);
return -ENOMEM;
}
net_pkt_frag_add(pkt, frag);
r = zoap_packet_init(&response, pkt);
if (r < 0) {
net_pkt_unref(pkt);
return r;
}
/* FIXME: Could be that zoap_packet_init() sets some defaults */
zoap_header_set_version(&response, 1);
zoap_header_set_type(&response, ZOAP_TYPE_ACK);
zoap_header_set_code(&response, ZOAP_RESPONSE_CODE_BAD_REQUEST);
zoap_header_set_id(&response, id);
r = net_context_sendto(pkt, from, sizeof(struct sockaddr_in6),
NULL, 0, NULL, NULL);
if (r < 0) {
net_pkt_unref(pkt);
}
return r;
}
#if defined(CONFIG_ZOAP_WELL_KNOWN_BLOCK_WISE)
#define MAX_BLOCK_WISE_TRANSFER_SIZE 2048
enum zoap_block_size default_block_size(void)
{
switch (CONFIG_ZOAP_WELL_KNOWN_BLOCK_WISE_SIZE) {
case 16:
return ZOAP_BLOCK_16;
case 32:
return ZOAP_BLOCK_32;
case 64:
return ZOAP_BLOCK_64;
case 128:
return ZOAP_BLOCK_128;
case 256:
return ZOAP_BLOCK_256;
case 512:
return ZOAP_BLOCK_512;
case 1024:
return ZOAP_BLOCK_1024;
}
return ZOAP_BLOCK_64;
}
static void add_to_net_buf(struct net_buf *buf, const char *str, u16_t len,
u16_t *remaining, size_t *offset, size_t current)
{
u16_t pos;
char *ptr;
if (!*remaining) {
return;
}
pos = 0;
if (*offset < current) {
pos = current - *offset;
if (len >= pos) {
len -= pos;
*offset += pos;
} else {
*offset += len;
return;
}
}
if (len > *remaining) {
len = *remaining;
}
ptr = net_buf_add(buf, len);
strncpy(ptr, str + pos, len);
*remaining -= len;
*offset += len;
}
static int format_uri(const char * const *path, struct net_buf *buf,
u16_t *remaining, size_t *offset,
size_t current, bool *more)
{
static const char prefix[] = "</";
const char * const *p;
if (!path) {
return -EINVAL;
}
add_to_net_buf(buf, &prefix[0], sizeof(prefix) - 1,
remaining, offset, current);
if (!*remaining) {
*more = true;
return 0;
}
for (p = path; *p; ) {
u16_t path_len = strlen(*p);
add_to_net_buf(buf, *p, path_len,
remaining, offset, current);
if (!*remaining) {
*more = true;
return 0;
}
p++;
if (!*p) {
continue;
}
add_to_net_buf(buf, "/", 1,
remaining, offset, current);
if (!*remaining) {
*more = true;
return 0;
}
}
add_to_net_buf(buf, ">", 1, remaining, offset, current);
*more = false;
return 0;
}
static int format_attributes(const char * const *attributes,
struct net_buf *buf,
u16_t *remaining, size_t *offset,
size_t current, bool *more)
{
const char * const *attr;
if (!attributes) {
goto terminator;
}
for (attr = attributes; *attr; ) {
int attr_len = strlen(*attr);
add_to_net_buf(buf, *attr, attr_len,
remaining, offset, current);
if (!*remaining) {
*more = true;
return 0;
}
attr++;
if (!*attr) {
continue;
}
add_to_net_buf(buf, ";", 1,
remaining, offset, current);
if (!*remaining) {
*more = true;
return 0;
}
}
terminator:
add_to_net_buf(buf, ";", 1, remaining, offset, current);
*more = false;
return 0;
}
static int format_resource(const struct zoap_resource *resource,
struct net_buf *buf,
u16_t *remaining, size_t *offset,
size_t current, bool *more)
{
struct zoap_core_metadata *meta = resource->user_data;
const char * const *attributes = NULL;
int r;
r = format_uri(resource->path, buf, 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, buf, remaining, offset, current,
more);
}
int _zoap_well_known_core_get(struct zoap_resource *resource,
struct zoap_packet *request,
const struct sockaddr *from)
{
/* FIXME: Add support for concurrent connections at same time,
* Maintain separate Context for each client (from addr) through slist
* and match it with 'from' address.
*/
static struct zoap_block_context ctx;
struct net_context *context;
struct zoap_packet response;
struct zoap_option query;
struct net_pkt *pkt;
struct net_buf *frag;
unsigned int num_queries;
size_t offset;
const u8_t *token;
bool more;
u16_t remaining;
u16_t id;
u8_t tkl;
u8_t format = 40; /* application/link-format */
u8_t *str;
int r;
if (ctx.total_size == 0) {
zoap_block_transfer_init(&ctx, default_block_size(),
MAX_BLOCK_WISE_TRANSFER_SIZE);
}
r = zoap_update_from_block(request, &ctx);
if (r < 0) {
return -EINVAL;
}
id = zoap_header_get_id(request);
token = zoap_header_get_token(request, &tkl);
/* Per RFC 6690, Section 4.1, only one (or none) query parameter may be
* provided, use the first if multiple.
*/
r = zoap_find_options(request, ZOAP_OPTION_URI_QUERY, &query, 1);
if (r < 0) {
return r;
}
num_queries = r;
context = net_pkt_context(request->pkt);
pkt = net_pkt_get_tx(context, PKT_WAIT_TIME);
if (!pkt) {
return -ENOMEM;
}
frag = net_pkt_get_data(context, PKT_WAIT_TIME);
if (!frag) {
net_pkt_unref(pkt);
return -ENOMEM;
}
net_pkt_frag_add(pkt, frag);
r = zoap_packet_init(&response, pkt);
if (r < 0) {
goto done;
}
/* FIXME: Could be that zoap_packet_init() sets some defaults */
zoap_header_set_version(&response, 1);
zoap_header_set_type(&response, ZOAP_TYPE_ACK);
zoap_header_set_code(&response, ZOAP_RESPONSE_CODE_CONTENT);
zoap_header_set_id(&response, id);
zoap_header_set_token(&response, token, tkl);
r = zoap_add_option(&response, ZOAP_OPTION_CONTENT_FORMAT,
&format, sizeof(format));
if (r < 0) {
net_pkt_unref(pkt);
return -EINVAL;
}
offset = 0;
more = false;
remaining = zoap_block_size_to_bytes(ctx.block_size);
while (resource++ && resource->path) {
struct net_buf *temp;
if (!match_queries_resource(resource, &query, num_queries)) {
continue;
}
if (!remaining) {
more = true;
break;
}
temp = net_pkt_get_data(context, PKT_WAIT_TIME);
if (!temp) {
net_pkt_unref(pkt);
return -ENOMEM;
}
net_pkt_frag_add(pkt, temp);
r = format_resource(resource, temp, &remaining, &offset,
ctx.current, &more);
if (r < 0) {
goto done;
}
}
if (!response.pkt->frags->frags) {
r = -ENOENT;
goto done;
}
if (!more) {
ctx.total_size = offset;
}
r = zoap_add_block2_option(&response, &ctx);
if (r < 0) {
net_pkt_unref(pkt);
return -EINVAL;
}
str = net_buf_add(response.pkt->frags, 1);
*str = COAP_MARKER;
response.start = str + 1;
net_pkt_compact(pkt);
done:
if (r < 0) {
net_pkt_unref(pkt);
return send_error_response(resource, request, from);
}
r = net_context_sendto(pkt, from, sizeof(struct sockaddr_in6),
NULL, 0, NULL, NULL);
if (r < 0) {
net_pkt_unref(pkt);
}
if (!more) {
/* So it's a last block, reset context */
memset(&ctx, 0, sizeof(ctx));
}
return r;
}
#else
static int format_uri(const char * const *path, struct net_buf *buf)
{
static const char prefix[] = "</";
const char * const *p;
char *str;
if (!path) {
return -EINVAL;
}
str = net_buf_add(buf, sizeof(prefix) - 1);
strncpy(str, prefix, sizeof(prefix) - 1);
for (p = path; *p; ) {
u16_t path_len = strlen(*p);
str = net_buf_add(buf, path_len);
strncpy(str, *p, path_len);
p++;
if (*p) {
str = net_buf_add(buf, 1);
*str = '/';
}
}
str = net_buf_add(buf, 1);
*str = '>';
return 0;
}
static int format_attributes(const char * const *attributes,
struct net_buf *buf)
{
const char * const *attr;
char *str;
if (!attributes) {
goto terminator;
}
for (attr = attributes; *attr; ) {
int attr_len = strlen(*attr);
str = net_buf_add(buf, attr_len);
strncpy(str, *attr, attr_len);
attr++;
if (*attr) {
str = net_buf_add(buf, 1);
*str = ';';
}
}
terminator:
str = net_buf_add(buf, 1);
*str = ';';
return 0;
}
static int format_resource(const struct zoap_resource *resource,
struct net_buf *buf)
{
struct zoap_core_metadata *meta = resource->user_data;
const char * const *attributes = NULL;
int r;
r = format_uri(resource->path, buf);
if (r < 0) {
return r;
}
if (meta && meta->attributes) {
attributes = meta->attributes;
}
return format_attributes(attributes, buf);
}
int _zoap_well_known_core_get(struct zoap_resource *resource,
struct zoap_packet *request,
const struct sockaddr *from)
{
struct net_context *context;
struct zoap_packet response;
struct zoap_option query;
struct net_pkt *pkt;
struct net_buf *frag;
const u8_t *token;
unsigned int num_queries;
u16_t id;
u8_t tkl;
u8_t format = 40; /* application/link-format */
u8_t *str;
int r;
id = zoap_header_get_id(request);
token = zoap_header_get_token(request, &tkl);
/* Per RFC 6690, Section 4.1, only one (or none) query parameter may be
* provided, use the first if multiple.
*/
r = zoap_find_options(request, ZOAP_OPTION_URI_QUERY, &query, 1);
if (r < 0) {
return r;
}
num_queries = r;
context = net_pkt_context(request->pkt);
pkt = net_pkt_get_tx(context, PKT_WAIT_TIME);
if (!pkt) {
return -ENOMEM;
}
frag = net_pkt_get_data(context, PKT_WAIT_TIME);
if (!frag) {
net_pkt_unref(pkt);
return -ENOMEM;
}
net_pkt_frag_add(pkt, frag);
r = zoap_packet_init(&response, pkt);
if (r < 0) {
goto done;
}
/* FIXME: Could be that zoap_packet_init() sets some defaults */
zoap_header_set_version(&response, 1);
zoap_header_set_type(&response, ZOAP_TYPE_ACK);
zoap_header_set_code(&response, ZOAP_RESPONSE_CODE_CONTENT);
zoap_header_set_id(&response, id);
zoap_header_set_token(&response, token, tkl);
r = zoap_add_option(&response, ZOAP_OPTION_CONTENT_FORMAT,
&format, sizeof(format));
if (r < 0) {
net_pkt_unref(pkt);
return -EINVAL;
}
r = -ENOENT;
str = net_buf_add(response.pkt->frags, 1);
*str = COAP_MARKER;
response.start = str + 1;
while (resource++ && resource->path) {
struct net_buf *temp;
if (!match_queries_resource(resource, &query, num_queries)) {
continue;
}
temp = net_pkt_get_data(context, PKT_WAIT_TIME);
if (!temp) {
net_pkt_unref(pkt);
return -ENOMEM;
}
net_pkt_frag_add(pkt, temp);
r = format_resource(resource, temp);
if (r < 0) {
goto done;
}
}
net_pkt_compact(pkt);
done:
if (r < 0) {
net_pkt_unref(pkt);
return send_error_response(resource, request, from);
}
r = net_context_sendto(pkt, from, sizeof(struct sockaddr_in6),
NULL, 0, NULL, NULL);
if (r < 0) {
net_pkt_unref(pkt);
}
return r;
}
#endif
/* Exposing some of the APIs to ZoAP unit tests in tests/net/lib/zoap */
#if defined(CONFIG_ZOAP_TEST_API_ENABLE)
bool _zoap_match_path_uri(const char * const *path,
const char *uri, u16_t len)
{
return match_path_uri(path, uri, len);
}
#endif