727 lines
14 KiB
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
|