mirror of
https://github.com/obgm/libcoap.git
synced 2026-11-12 11:40:39 +03:00
examples/coap-server.c: Only call coap_resource_notify() whenever a PUT is done to a created dynamic resource, instead of once a second. Issue found by Andy Ryan.
1029 lines
32 KiB
C
1029 lines
32 KiB
C
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* coap -- simple implementation of the Constrained Application Protocol (CoAP)
|
|
* as defined in RFC 7252
|
|
*
|
|
* Copyright (C) 2010--2019 Olaf Bergmann <bergmann@tzi.org> and others
|
|
*
|
|
* This file is part of the CoAP library libcoap. Please see README for terms
|
|
* of use.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#ifdef _WIN32
|
|
#define strcasecmp _stricmp
|
|
#include "getopt.c"
|
|
#if !defined(S_ISDIR)
|
|
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
|
#endif
|
|
#else
|
|
#include <unistd.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <dirent.h>
|
|
#endif
|
|
|
|
/* Need to refresh time once per sec */
|
|
#define COAP_RESOURCE_CHECK_TIME 1
|
|
|
|
#include <coap2/coap.h>
|
|
|
|
#ifndef min
|
|
#define min(a,b) ((a) < (b) ? (a) : (b))
|
|
#endif
|
|
|
|
/* temporary storage for dynamic resource representations */
|
|
static int quit = 0;
|
|
|
|
/* changeable clock base (see handle_put_time()) */
|
|
static time_t clock_offset;
|
|
static time_t my_clock_base = 0;
|
|
|
|
struct coap_resource_t *time_resource = NULL;
|
|
|
|
static int resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_CON;
|
|
|
|
static char *cert_file = NULL; /* Combined certificate and private key in PEM */
|
|
static char *ca_file = NULL; /* CA for cert_file - for cert checking in PEM */
|
|
static char *root_ca_file = NULL; /* List of trusted Root CAs in PEM */
|
|
static int require_peer_cert = 1; /* By default require peer cert */
|
|
#define MAX_KEY 64 /* Maximum length of a key (i.e., PSK) in bytes. */
|
|
static uint8_t key[MAX_KEY];
|
|
static ssize_t key_length = 0;
|
|
int key_defined = 0;
|
|
static const char *hint = "CoAP";
|
|
static int support_dynamic = 0;
|
|
|
|
#ifndef WITHOUT_ASYNC
|
|
/* This variable is used to mimic long-running tasks that require
|
|
* asynchronous responses. */
|
|
static coap_async_state_t *async = NULL;
|
|
#endif /* WITHOUT_ASYNC */
|
|
|
|
#ifdef __GNUC__
|
|
#define UNUSED_PARAM __attribute__ ((unused))
|
|
#else /* not a GCC */
|
|
#define UNUSED_PARAM
|
|
#endif /* GCC */
|
|
|
|
/* SIGINT handler: set quit to 1 for graceful termination */
|
|
static void
|
|
handle_sigint(int signum UNUSED_PARAM) {
|
|
quit = 1;
|
|
}
|
|
|
|
#define INDEX "This is a test server made with libcoap (see https://libcoap.net)\n" \
|
|
"Copyright (C) 2010--2019 Olaf Bergmann <bergmann@tzi.org> and others\n\n"
|
|
|
|
static void
|
|
hnd_get_index(coap_context_t *ctx UNUSED_PARAM,
|
|
struct coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
coap_pdu_t *request,
|
|
coap_binary_t *token,
|
|
coap_string_t *query UNUSED_PARAM,
|
|
coap_pdu_t *response) {
|
|
|
|
coap_add_data_blocked_response(resource, session, request, response, token,
|
|
COAP_MEDIATYPE_TEXT_PLAIN, 0x2ffff,
|
|
strlen(INDEX),
|
|
(const uint8_t *)INDEX);
|
|
}
|
|
|
|
static void
|
|
hnd_get_time(coap_context_t *ctx UNUSED_PARAM,
|
|
struct coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
coap_pdu_t *request,
|
|
coap_binary_t *token,
|
|
coap_string_t *query,
|
|
coap_pdu_t *response) {
|
|
unsigned char buf[40];
|
|
size_t len;
|
|
time_t now;
|
|
coap_tick_t t;
|
|
(void)request;
|
|
|
|
/* FIXME: return time, e.g. in human-readable by default and ticks
|
|
* when query ?ticks is given. */
|
|
|
|
if (my_clock_base) {
|
|
|
|
/* calculate current time */
|
|
coap_ticks(&t);
|
|
now = my_clock_base + (t / COAP_TICKS_PER_SECOND);
|
|
|
|
if (query != NULL
|
|
&& coap_string_equal(query, coap_make_str_const("ticks"))) {
|
|
/* output ticks */
|
|
len = snprintf((char *)buf, sizeof(buf), "%u", (unsigned int)now);
|
|
|
|
} else { /* output human-readable time */
|
|
struct tm *tmp;
|
|
tmp = gmtime(&now);
|
|
if (!tmp) {
|
|
/* If 'now' is not valid */
|
|
response->code = COAP_RESPONSE_CODE(404);
|
|
return;
|
|
}
|
|
else {
|
|
len = strftime((char *)buf, sizeof(buf), "%b %d %H:%M:%S", tmp);
|
|
}
|
|
}
|
|
coap_add_data_blocked_response(resource, session, request, response, token,
|
|
COAP_MEDIATYPE_TEXT_PLAIN, 1,
|
|
len,
|
|
buf);
|
|
}
|
|
else {
|
|
/* if my_clock_base was deleted, we pretend to have no such resource */
|
|
response->code = COAP_RESPONSE_CODE(404);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hnd_put_time(coap_context_t *ctx UNUSED_PARAM,
|
|
struct coap_resource_t *resource,
|
|
coap_session_t *session UNUSED_PARAM,
|
|
coap_pdu_t *request,
|
|
coap_binary_t *token UNUSED_PARAM,
|
|
coap_string_t *query UNUSED_PARAM,
|
|
coap_pdu_t *response) {
|
|
coap_tick_t t;
|
|
size_t size;
|
|
unsigned char *data;
|
|
|
|
/* FIXME: re-set my_clock_base to clock_offset if my_clock_base == 0
|
|
* and request is empty. When not empty, set to value in request payload
|
|
* (insist on query ?ticks). Return Created or Ok.
|
|
*/
|
|
|
|
/* if my_clock_base was deleted, we pretend to have no such resource */
|
|
response->code =
|
|
my_clock_base ? COAP_RESPONSE_CODE(204) : COAP_RESPONSE_CODE(201);
|
|
|
|
coap_resource_notify_observers(resource, NULL);
|
|
|
|
/* coap_get_data() sets size to 0 on error */
|
|
(void)coap_get_data(request, &size, &data);
|
|
|
|
if (size == 0) /* re-init */
|
|
my_clock_base = clock_offset;
|
|
else {
|
|
my_clock_base = 0;
|
|
coap_ticks(&t);
|
|
while(size--)
|
|
my_clock_base = my_clock_base * 10 + *data++;
|
|
my_clock_base -= t / COAP_TICKS_PER_SECOND;
|
|
|
|
/* Sanity check input value */
|
|
if (!gmtime(&my_clock_base)) {
|
|
unsigned char buf[3];
|
|
response->code = COAP_RESPONSE_CODE(400);
|
|
coap_add_option(response,
|
|
COAP_OPTION_CONTENT_FORMAT,
|
|
coap_encode_var_safe(buf, sizeof(buf),
|
|
COAP_MEDIATYPE_TEXT_PLAIN), buf);
|
|
coap_add_data(response, 22, (const uint8_t*)"Invalid set time value");
|
|
/* re-init as value is bad */
|
|
my_clock_base = clock_offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hnd_delete_time(coap_context_t *ctx UNUSED_PARAM,
|
|
struct coap_resource_t *resource UNUSED_PARAM,
|
|
coap_session_t *session UNUSED_PARAM,
|
|
coap_pdu_t *request UNUSED_PARAM,
|
|
coap_binary_t *token UNUSED_PARAM,
|
|
coap_string_t *query UNUSED_PARAM,
|
|
coap_pdu_t *response UNUSED_PARAM) {
|
|
my_clock_base = 0; /* mark clock as "deleted" */
|
|
|
|
/* type = request->hdr->type == COAP_MESSAGE_CON */
|
|
/* ? COAP_MESSAGE_ACK : COAP_MESSAGE_NON; */
|
|
}
|
|
|
|
#ifndef WITHOUT_ASYNC
|
|
static void
|
|
hnd_get_async(coap_context_t *ctx,
|
|
struct coap_resource_t *resource UNUSED_PARAM,
|
|
coap_session_t *session,
|
|
coap_pdu_t *request,
|
|
coap_binary_t *token UNUSED_PARAM,
|
|
coap_string_t *query UNUSED_PARAM,
|
|
coap_pdu_t *response) {
|
|
unsigned long delay = 5;
|
|
size_t size;
|
|
|
|
if (async) {
|
|
if (async->id != request->tid) {
|
|
coap_opt_filter_t f;
|
|
coap_option_filter_clear(f);
|
|
response->code = COAP_RESPONSE_CODE(503);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (query) {
|
|
const uint8_t *p = query->s;
|
|
|
|
delay = 0;
|
|
for (size = query->length; size; --size, ++p)
|
|
delay = delay * 10 + (*p - '0');
|
|
}
|
|
|
|
async = coap_register_async(ctx,
|
|
session,
|
|
request,
|
|
COAP_ASYNC_SEPARATE | COAP_ASYNC_CONFIRM,
|
|
(void *)(COAP_TICKS_PER_SECOND * delay));
|
|
}
|
|
|
|
static void
|
|
check_async(coap_context_t *ctx,
|
|
coap_tick_t now) {
|
|
coap_pdu_t *response;
|
|
coap_async_state_t *tmp;
|
|
|
|
size_t size = 13;
|
|
|
|
if (!async || now < async->created + (unsigned long)async->appdata)
|
|
return;
|
|
|
|
response = coap_pdu_init(async->flags & COAP_ASYNC_CONFIRM
|
|
? COAP_MESSAGE_CON
|
|
: COAP_MESSAGE_NON,
|
|
COAP_RESPONSE_CODE(205), 0, size);
|
|
if (!response) {
|
|
coap_log(LOG_DEBUG, "check_async: insufficient memory, we'll try later\n");
|
|
async->appdata =
|
|
(void *)((unsigned long)async->appdata + 15 * COAP_TICKS_PER_SECOND);
|
|
return;
|
|
}
|
|
|
|
response->tid = coap_new_message_id(async->session);
|
|
|
|
if (async->tokenlen)
|
|
coap_add_token(response, async->tokenlen, async->token);
|
|
|
|
coap_add_data(response, 4, (const uint8_t *)"done");
|
|
|
|
if (coap_send(async->session, response) == COAP_INVALID_TID) {
|
|
coap_log(LOG_DEBUG, "check_async: cannot send response for message\n");
|
|
}
|
|
coap_remove_async(ctx, async->session, async->id, &tmp);
|
|
coap_free_async(async);
|
|
async = NULL;
|
|
}
|
|
#endif /* WITHOUT_ASYNC */
|
|
|
|
typedef struct dynamic_resource_t {
|
|
coap_string_t *uri_path;
|
|
coap_string_t *value;
|
|
coap_resource_t *resource;
|
|
int created;
|
|
uint16_t media_type;
|
|
} dynamic_resource_t;
|
|
|
|
static int dynamic_count = 0;
|
|
static dynamic_resource_t *dynamic_entry = NULL;
|
|
|
|
/*
|
|
* Regular DELETE handler - used by resources created by the
|
|
* Unknown Resource PUT handler
|
|
*/
|
|
|
|
static void
|
|
hnd_delete(coap_context_t *ctx,
|
|
coap_resource_t *resource,
|
|
coap_session_t *session UNUSED_PARAM,
|
|
coap_pdu_t *request UNUSED_PARAM,
|
|
coap_binary_t *token UNUSED_PARAM,
|
|
coap_string_t *query UNUSED_PARAM,
|
|
coap_pdu_t *response UNUSED_PARAM
|
|
) {
|
|
int i;
|
|
coap_string_t *uri_path;
|
|
|
|
/* get the uri_path */
|
|
uri_path = coap_get_uri_path(request);
|
|
if (!uri_path) {
|
|
response->code = COAP_RESPONSE_CODE(404);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < dynamic_count; i++) {
|
|
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
|
|
/* Dynamic entry no longer required - delete it */
|
|
coap_delete_string(dynamic_entry[i].value);
|
|
if (dynamic_count-i > 1) {
|
|
memmove (&dynamic_entry[i],
|
|
&dynamic_entry[i+1],
|
|
(dynamic_count-i-1) * sizeof (dynamic_entry[0]));
|
|
}
|
|
dynamic_count--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Dynamic resource no longer required - delete it */
|
|
coap_delete_resource(ctx, resource);
|
|
response->code = COAP_RESPONSE_CODE(202);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Regular GET handler - used by resources created by the
|
|
* Unknown Resource PUT handler
|
|
*/
|
|
|
|
static void
|
|
hnd_get(coap_context_t *ctx UNUSED_PARAM,
|
|
coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
coap_pdu_t *request,
|
|
coap_binary_t *token,
|
|
coap_string_t *query UNUSED_PARAM,
|
|
coap_pdu_t *response
|
|
) {
|
|
coap_str_const_t *uri_path;
|
|
int i;
|
|
dynamic_resource_t *resource_entry = NULL;
|
|
coap_str_const_t value = { 0, NULL };
|
|
/*
|
|
* request will be NULL if an Observe triggered request, so the uri_path,
|
|
* if needed, must be abstracted from the resource.
|
|
* The uri_path string is a const pointer
|
|
*/
|
|
|
|
uri_path = coap_resource_get_uri_path(resource);
|
|
if (!uri_path) {
|
|
response->code = COAP_RESPONSE_CODE(404);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < dynamic_count; i++) {
|
|
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == dynamic_count) {
|
|
response->code = COAP_RESPONSE_CODE(404);
|
|
return;
|
|
}
|
|
|
|
resource_entry = &dynamic_entry[i];
|
|
|
|
if (resource_entry->value) {
|
|
value.length = resource_entry->value->length;
|
|
value.s = resource_entry->value->s;
|
|
}
|
|
coap_add_data_blocked_response(resource, session, request, response, token,
|
|
resource_entry->media_type, -1,
|
|
value.length,
|
|
value.s);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Regular PUT handler - used by resources created by the
|
|
* Unknown Resource PUT handler
|
|
*/
|
|
|
|
static void
|
|
hnd_put(coap_context_t *ctx UNUSED_PARAM,
|
|
coap_resource_t *resource,
|
|
coap_session_t *session UNUSED_PARAM,
|
|
coap_pdu_t *request,
|
|
coap_binary_t *token UNUSED_PARAM,
|
|
coap_string_t *query UNUSED_PARAM,
|
|
coap_pdu_t *response
|
|
) {
|
|
coap_string_t *uri_path;
|
|
int i;
|
|
size_t size;
|
|
uint8_t *data;
|
|
coap_block_t block1;
|
|
dynamic_resource_t *resource_entry = NULL;
|
|
unsigned char buf[6]; /* space to hold encoded/decoded uints */
|
|
coap_opt_iterator_t opt_iter;
|
|
coap_opt_t *option;
|
|
|
|
/* get the uri_path */
|
|
uri_path = coap_get_uri_path(request);
|
|
if (!uri_path) {
|
|
response->code = COAP_RESPONSE_CODE(404);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Locate the correct dynamic block for this request
|
|
*/
|
|
for (i = 0; i < dynamic_count; i++) {
|
|
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == dynamic_count) {
|
|
if (dynamic_count >= support_dynamic) {
|
|
/* Should have been caught in hnd_unknown_put() */
|
|
response->code = COAP_RESPONSE_CODE(406);
|
|
return;
|
|
}
|
|
dynamic_count++;
|
|
dynamic_entry = realloc (dynamic_entry, dynamic_count * sizeof(dynamic_entry[0]));
|
|
if (dynamic_entry) {
|
|
dynamic_entry[i].uri_path = uri_path;
|
|
dynamic_entry[i].value = NULL;
|
|
dynamic_entry[i].resource = resource;
|
|
dynamic_entry[i].created = 1;
|
|
response->code = COAP_RESPONSE_CODE(201);
|
|
if ((option = coap_check_option(request, COAP_OPTION_CONTENT_TYPE, &opt_iter)) != NULL) {
|
|
dynamic_entry[i].media_type =
|
|
coap_decode_var_bytes (coap_opt_value (option), coap_opt_length (option));
|
|
}
|
|
else {
|
|
dynamic_entry[i].media_type = COAP_MEDIATYPE_TEXT_PLAIN;
|
|
}
|
|
/* Store media type of new resource in ct. We can use buf here
|
|
* as coap_add_attr() will copy the passed string. */
|
|
memset(buf, 0, sizeof(buf));
|
|
snprintf((char *)buf, sizeof(buf), "%d", dynamic_entry[i].media_type);
|
|
/* ensure that buf is always zero-terminated */
|
|
assert(buf[sizeof(buf) - 1] == '\0');
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
coap_add_attr(resource,
|
|
coap_make_str_const("ct"),
|
|
coap_make_str_const(buf),
|
|
0);
|
|
} else {
|
|
dynamic_count--;
|
|
response->code = COAP_RESPONSE_CODE(500);
|
|
return;
|
|
}
|
|
} else {
|
|
/* Need to do this as coap_get_uri_path() created it */
|
|
coap_delete_string(uri_path);
|
|
response->code = COAP_RESPONSE_CODE(204);
|
|
dynamic_entry[i].created = 0;
|
|
coap_resource_notify_observers(dynamic_entry[i].resource, NULL);
|
|
}
|
|
|
|
resource_entry = &dynamic_entry[i];
|
|
|
|
if (coap_get_block(request, COAP_OPTION_BLOCK1, &block1)) {
|
|
/* handle BLOCK1 */
|
|
if (coap_get_data(request, &size, &data) && (size > 0)) {
|
|
size_t offset = block1.num << (block1.szx + 4);
|
|
coap_string_t *value = resource_entry->value;
|
|
if (offset == 0) {
|
|
if (value) {
|
|
coap_delete_string(value);
|
|
value = NULL;
|
|
}
|
|
}
|
|
else if (offset >
|
|
(resource_entry->value ? resource_entry->value->length : 0)) {
|
|
/* Upload is not sequential - block missing */
|
|
response->code = COAP_RESPONSE_CODE(408);
|
|
return;
|
|
}
|
|
else if (offset <
|
|
(resource_entry->value ? resource_entry->value->length : 0)) {
|
|
/* Upload is not sequential - block duplicated */
|
|
goto just_respond;
|
|
}
|
|
/* Add in new block to end of current data */
|
|
resource_entry->value = coap_new_string(offset + size);
|
|
memcpy (&resource_entry->value->s[offset], data, size);
|
|
resource_entry->value->length = offset + size;
|
|
if (value) {
|
|
memcpy (resource_entry->value->s, value->s, value->length);
|
|
coap_delete_string(value);
|
|
}
|
|
}
|
|
just_respond:
|
|
if (block1.m) {
|
|
response->code = COAP_RESPONSE_CODE(231);
|
|
}
|
|
else if (resource_entry->created) {
|
|
response->code = COAP_RESPONSE_CODE(201);
|
|
}
|
|
else {
|
|
response->code = COAP_RESPONSE_CODE(204);
|
|
}
|
|
coap_add_option(response,
|
|
COAP_OPTION_BLOCK1,
|
|
coap_encode_var_safe(buf, sizeof(buf),
|
|
((block1.num << 4) |
|
|
(block1.m << 3) |
|
|
block1.szx)),
|
|
buf);
|
|
}
|
|
else if (coap_get_data(request, &size, &data) && (size > 0)) {
|
|
/* Not a BLOCK1 with data */
|
|
if (resource_entry->value) {
|
|
coap_delete_string(resource_entry->value);
|
|
resource_entry->value = NULL;
|
|
}
|
|
resource_entry->value = coap_new_string(size);
|
|
memcpy (resource_entry->value->s, data, size);
|
|
resource_entry->value->length = size;
|
|
}
|
|
else {
|
|
/* Not a BLOCK1 and no data */
|
|
if (resource_entry->value) {
|
|
coap_delete_string(resource_entry->value);
|
|
resource_entry->value = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unknown Resource PUT handler
|
|
*/
|
|
|
|
static void
|
|
hnd_unknown_put(coap_context_t *ctx,
|
|
coap_resource_t *resource UNUSED_PARAM,
|
|
coap_session_t *session,
|
|
coap_pdu_t *request,
|
|
coap_binary_t *token,
|
|
coap_string_t *query,
|
|
coap_pdu_t *response
|
|
) {
|
|
coap_resource_t *r;
|
|
coap_string_t *uri_path;
|
|
|
|
/* get the uri_path - will will get used by coap_resource_init() */
|
|
uri_path = coap_get_uri_path(request);
|
|
if (!uri_path) {
|
|
response->code = COAP_RESPONSE_CODE(404);
|
|
return;
|
|
}
|
|
|
|
if (dynamic_count >= support_dynamic) {
|
|
response->code = COAP_RESPONSE_CODE(406);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Create a resource to handle the new URI
|
|
* uri_path will get deleted when the resource is removed
|
|
*/
|
|
r = coap_resource_init((coap_str_const_t*)uri_path,
|
|
COAP_RESOURCE_FLAGS_RELEASE_URI | resource_flags);
|
|
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"Dynamic\""), 0);
|
|
coap_register_handler(r, COAP_REQUEST_PUT, hnd_put);
|
|
coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete);
|
|
/* We possibly want to Observe the GETs */
|
|
coap_resource_set_get_observable(r, 1);
|
|
coap_register_handler(r, COAP_REQUEST_GET, hnd_get);
|
|
coap_add_resource(ctx, r);
|
|
|
|
/* Do the PUT for this first call */
|
|
hnd_put(ctx, r, session, request, token, query, response);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
init_resources(coap_context_t *ctx) {
|
|
coap_resource_t *r;
|
|
|
|
r = coap_resource_init(NULL, 0);
|
|
coap_register_handler(r, COAP_REQUEST_GET, hnd_get_index);
|
|
|
|
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
|
|
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"General Info\""), 0);
|
|
coap_add_resource(ctx, r);
|
|
|
|
/* store clock base to use in /time */
|
|
my_clock_base = clock_offset;
|
|
|
|
r = coap_resource_init(coap_make_str_const("time"), resource_flags);
|
|
coap_register_handler(r, COAP_REQUEST_GET, hnd_get_time);
|
|
coap_register_handler(r, COAP_REQUEST_PUT, hnd_put_time);
|
|
coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete_time);
|
|
coap_resource_set_get_observable(r, 1);
|
|
|
|
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
|
|
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"Internal Clock\""), 0);
|
|
coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"ticks\""), 0);
|
|
coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("\"clock\""), 0);
|
|
|
|
coap_add_resource(ctx, r);
|
|
time_resource = r;
|
|
|
|
if (support_dynamic > 0) {
|
|
/* Create a resource to handle PUTs to unknown URIs */
|
|
r = coap_resource_unknown_init(hnd_unknown_put);
|
|
coap_add_resource(ctx, r);
|
|
}
|
|
#ifndef WITHOUT_ASYNC
|
|
r = coap_resource_init(coap_make_str_const("async"), 0);
|
|
coap_register_handler(r, COAP_REQUEST_GET, hnd_get_async);
|
|
|
|
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
|
|
coap_add_resource(ctx, r);
|
|
#endif /* WITHOUT_ASYNC */
|
|
}
|
|
|
|
static int
|
|
verify_cn_callback(const char *cn,
|
|
const uint8_t *asn1_public_cert UNUSED_PARAM,
|
|
size_t asn1_length UNUSED_PARAM,
|
|
coap_session_t *session UNUSED_PARAM,
|
|
unsigned depth,
|
|
int validated UNUSED_PARAM,
|
|
void *arg UNUSED_PARAM
|
|
) {
|
|
coap_log(LOG_INFO, "CN '%s' presented by client (%s)\n",
|
|
cn, depth ? "CA" : "Certificate");
|
|
return 1;
|
|
}
|
|
|
|
static coap_dtls_key_t *
|
|
verify_sni_callback(const char *sni,
|
|
void *arg UNUSED_PARAM
|
|
) {
|
|
static coap_dtls_key_t dtls_key;
|
|
|
|
/* Just use the defined keys for now */
|
|
memset (&dtls_key, 0, sizeof(dtls_key));
|
|
dtls_key.key_type = COAP_PKI_KEY_PEM;
|
|
dtls_key.key.pem.public_cert = cert_file;
|
|
dtls_key.key.pem.private_key = cert_file;
|
|
dtls_key.key.pem.ca_file = ca_file;
|
|
if (sni[0]) {
|
|
coap_log(LOG_INFO, "SNI '%s' requested\n", sni);
|
|
}
|
|
else {
|
|
coap_log(LOG_DEBUG, "SNI not requested\n");
|
|
}
|
|
return &dtls_key;
|
|
}
|
|
|
|
static void
|
|
fill_keystore(coap_context_t *ctx) {
|
|
if (cert_file) {
|
|
coap_dtls_pki_t dtls_pki;
|
|
memset (&dtls_pki, 0, sizeof(dtls_pki));
|
|
dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
|
|
if (ca_file) {
|
|
/*
|
|
* Add in additional certificate checking.
|
|
* This list of enabled can be tuned for the specific
|
|
* requirements - see 'man coap_encryption'.
|
|
*/
|
|
dtls_pki.verify_peer_cert = 1;
|
|
dtls_pki.require_peer_cert = require_peer_cert;
|
|
dtls_pki.allow_self_signed = 1;
|
|
dtls_pki.allow_expired_certs = 1;
|
|
dtls_pki.cert_chain_validation = 1;
|
|
dtls_pki.cert_chain_verify_depth = 2;
|
|
dtls_pki.check_cert_revocation = 1;
|
|
dtls_pki.allow_no_crl = 1;
|
|
dtls_pki.allow_expired_crl = 1;
|
|
dtls_pki.validate_cn_call_back = verify_cn_callback;
|
|
dtls_pki.cn_call_back_arg = NULL;
|
|
dtls_pki.validate_sni_call_back = verify_sni_callback;
|
|
dtls_pki.sni_call_back_arg = NULL;
|
|
}
|
|
dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM;
|
|
dtls_pki.pki_key.key.pem.public_cert = cert_file;
|
|
dtls_pki.pki_key.key.pem.private_key = cert_file;
|
|
dtls_pki.pki_key.key.pem.ca_file = ca_file;
|
|
/* If general root CAs are defined */
|
|
if (root_ca_file) {
|
|
struct stat stbuf;
|
|
if ((stat(root_ca_file, &stbuf) == 0) && S_ISDIR(stbuf.st_mode)) {
|
|
coap_context_set_pki_root_cas(ctx, NULL, root_ca_file);
|
|
} else {
|
|
coap_context_set_pki_root_cas(ctx, root_ca_file, NULL);
|
|
}
|
|
}
|
|
if (key_defined)
|
|
coap_context_set_psk(ctx, hint, key, key_length);
|
|
coap_context_set_pki(ctx, &dtls_pki);
|
|
}
|
|
else if (key_defined) {
|
|
coap_context_set_psk(ctx, hint, key, key_length);
|
|
}
|
|
else if (coap_dtls_is_supported() || coap_tls_is_supported()) {
|
|
coap_log(LOG_DEBUG,
|
|
"(D)TLS not enabled as neither -k or -c options specified\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
usage( const char *program, const char *version) {
|
|
const char *p;
|
|
char buffer[64];
|
|
|
|
p = strrchr( program, '/' );
|
|
if ( p )
|
|
program = ++p;
|
|
|
|
fprintf( stderr, "%s v%s -- a small CoAP implementation\n"
|
|
"(c) 2010,2011,2015-2018 Olaf Bergmann <bergmann@tzi.org> and others\n\n"
|
|
"%s\n\n"
|
|
"Usage: %s [-d max] [-g group] [-l loss] [-p port] [-v num]\n"
|
|
"\t\t[-A address] [-N]\n"
|
|
"\t\t[[-k key] [-h hint]]\n"
|
|
"\t\t[[-c certfile][-C cafile] [-n] [-R root_cafile]]\n"
|
|
"General Options\n"
|
|
"\t-d max \t\tAllow dynamic creation of up to a total of max\n"
|
|
"\t \t\tresources. If max is reached, a 4.06 code is returned\n"
|
|
"\t \t\tuntil one of the dynamic resources has been deleted\n"
|
|
"\t-g group\tJoin the given multicast group\n"
|
|
"\t-l list\t\tFail to send some datagrams specified by a comma\n"
|
|
"\t \t\tseparated list of numbers or number ranges\n"
|
|
"\t \t\t(for debugging only)\n"
|
|
"\t-l loss%%\tRandomly fail to send datagrams with the specified\n"
|
|
"\t \t\tprobability - 100%% all datagrams, 0%% no datagrams\n"
|
|
"\t \t\t(for debugging only)\n"
|
|
"\t-p port\t\tListen on specified port\n"
|
|
"\t-v num \t\tVerbosity level (default 3, maximum is 9). Above 7,\n"
|
|
"\t \t\tthere is increased verbosity in GnuTLS logging\n"
|
|
"\t-A address\tInterface address to bind to\n"
|
|
"\t-N \t\tMake \"observe\" responses NON-confirmable. Even if set\n"
|
|
"\t \t\tevery fifth response will still be sent as a confirmable\n"
|
|
"\t \t\tresponse (RFC 7641 requirement)\n"
|
|
"PSK Options (if supported by underlying (D)TLS library)\n"
|
|
"\t-h hint\t\tPSK Hint. Default is CoAP\n"
|
|
"\t-k key \t\tPre-shared key. This argument requires (D)TLS with PSK\n"
|
|
"\t \t\tto be available. This cannot be empty if defined.\n"
|
|
"\t \t\tNote that both -c and -k need to be defined\n"
|
|
"\t \t\tfor both PSK and PKI to be concurrently supported\n"
|
|
"PKI Options (if supported by underlying (D)TLS library)\n"
|
|
"\t-c certfile\tPEM file containing both CERTIFICATE and PRIVATE KEY\n"
|
|
"\t \t\tThis argument requires (D)TLS with PKI to be available\n"
|
|
"\t-n \t\tDisable the requirement for clients to have defined\n"
|
|
"\t \t\tclient certificates\n"
|
|
"\t-C cafile\tPEM file containing the CA Certificate that was used to\n"
|
|
"\t \t\tsign the certfile. If defined, then the client will be\n"
|
|
"\t \t\tgiven this CA Certificate during the TLS set up.\n"
|
|
"\t \t\tFurthermore, this will trigger the validation of the\n"
|
|
"\t \t\tclient certificate. If certfile is self-signed (as\n"
|
|
"\t \t\tdefined by '-c certfile'), then you need to have on the\n"
|
|
"\t \t\tcommand line the same filename for both the certfile and\n"
|
|
"\t \t\tcafile (as in '-c certfile -C certfile') to trigger\n"
|
|
"\t \t\tvalidation\n"
|
|
"\t-R root_cafile\tPEM file containing the set of trusted root CAs that\n"
|
|
"\t \t\tare to be used to validate the client certificate.\n"
|
|
"\t \t\tThe '-C cafile' does not have to be in this list and is\n"
|
|
"\t \t\t'trusted' for the verification.\n"
|
|
"\t \t\tAlternatively, this can point to a directory containing\n"
|
|
"\t \t\ta set of CA PEM files\n"
|
|
, program, version, coap_string_tls_version(buffer, sizeof(buffer)),
|
|
program);
|
|
}
|
|
|
|
static coap_context_t *
|
|
get_context(const char *node, const char *port) {
|
|
coap_context_t *ctx = NULL;
|
|
int s;
|
|
struct addrinfo hints;
|
|
struct addrinfo *result, *rp;
|
|
|
|
ctx = coap_new_context(NULL);
|
|
if (!ctx) {
|
|
return NULL;
|
|
}
|
|
/* Need PSK set up before we set up (D)TLS endpoints */
|
|
fill_keystore(ctx);
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
|
|
hints.ai_socktype = SOCK_DGRAM; /* Coap uses UDP */
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
|
|
|
|
s = getaddrinfo(node, port, &hints, &result);
|
|
if ( s != 0 ) {
|
|
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
|
|
coap_free_context(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
/* iterate through results until success */
|
|
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
|
coap_address_t addr, addrs;
|
|
coap_endpoint_t *ep_udp = NULL, *ep_dtls = NULL, *ep_tcp = NULL, *ep_tls = NULL;
|
|
|
|
if (rp->ai_addrlen <= sizeof(addr.addr)) {
|
|
coap_address_init(&addr);
|
|
addr.size = rp->ai_addrlen;
|
|
memcpy(&addr.addr, rp->ai_addr, rp->ai_addrlen);
|
|
addrs = addr;
|
|
if (addr.addr.sa.sa_family == AF_INET) {
|
|
uint16_t temp = ntohs(addr.addr.sin.sin_port) + 1;
|
|
addrs.addr.sin.sin_port = htons(temp);
|
|
} else if (addr.addr.sa.sa_family == AF_INET6) {
|
|
uint16_t temp = ntohs(addr.addr.sin6.sin6_port) + 1;
|
|
addrs.addr.sin6.sin6_port = htons(temp);
|
|
} else {
|
|
goto finish;
|
|
}
|
|
|
|
ep_udp = coap_new_endpoint(ctx, &addr, COAP_PROTO_UDP);
|
|
if (ep_udp) {
|
|
if (coap_dtls_is_supported() && (key_defined || cert_file)) {
|
|
ep_dtls = coap_new_endpoint(ctx, &addrs, COAP_PROTO_DTLS);
|
|
if (!ep_dtls)
|
|
coap_log(LOG_CRIT, "cannot create DTLS endpoint\n");
|
|
}
|
|
} else {
|
|
coap_log(LOG_CRIT, "cannot create UDP endpoint\n");
|
|
continue;
|
|
}
|
|
ep_tcp = coap_new_endpoint(ctx, &addr, COAP_PROTO_TCP);
|
|
if (ep_tcp) {
|
|
if (coap_tls_is_supported() && (key_defined || cert_file)) {
|
|
ep_tls = coap_new_endpoint(ctx, &addrs, COAP_PROTO_TLS);
|
|
if (!ep_tls)
|
|
coap_log(LOG_CRIT, "cannot create TLS endpoint\n");
|
|
}
|
|
} else {
|
|
coap_log(LOG_CRIT, "cannot create TCP endpoint\n");
|
|
}
|
|
if (ep_udp)
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "no context available for interface '%s'\n", node);
|
|
|
|
finish:
|
|
freeaddrinfo(result);
|
|
return ctx;
|
|
}
|
|
|
|
static ssize_t
|
|
cmdline_read_key(char *arg, unsigned char *buf, size_t maxlen) {
|
|
size_t len = strnlen(arg, maxlen);
|
|
if (len) {
|
|
memcpy(buf, arg, len);
|
|
return len;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv) {
|
|
coap_context_t *ctx;
|
|
char *group = NULL;
|
|
coap_tick_t now;
|
|
char addr_str[NI_MAXHOST] = "::";
|
|
char port_str[NI_MAXSERV] = "5683";
|
|
int opt;
|
|
coap_log_t log_level = LOG_WARNING;
|
|
unsigned wait_ms;
|
|
time_t t_last = 0;
|
|
#ifndef _WIN32
|
|
struct sigaction sa;
|
|
#endif
|
|
|
|
clock_offset = time(NULL);
|
|
|
|
while ((opt = getopt(argc, argv, "A:d:c:C:g:h:k:l:nNp:R:v:")) != -1) {
|
|
switch (opt) {
|
|
case 'A' :
|
|
strncpy(addr_str, optarg, NI_MAXHOST-1);
|
|
addr_str[NI_MAXHOST - 1] = '\0';
|
|
break;
|
|
case 'c' :
|
|
cert_file = optarg;
|
|
break;
|
|
case 'C' :
|
|
ca_file = optarg;
|
|
break;
|
|
case 'd' :
|
|
support_dynamic = atoi(optarg);
|
|
break;
|
|
case 'g' :
|
|
group = optarg;
|
|
break;
|
|
case 'h' :
|
|
if (!optarg[0]) {
|
|
coap_log( LOG_CRIT, "Invalid PSK hint specified\n" );
|
|
break;
|
|
}
|
|
hint = optarg;
|
|
break;
|
|
case 'k' :
|
|
key_length = cmdline_read_key(optarg, key, MAX_KEY);
|
|
if (key_length < 0) {
|
|
coap_log( LOG_CRIT, "Invalid PSK key specified\n" );
|
|
break;
|
|
}
|
|
key_defined = 1;
|
|
break;
|
|
case 'l':
|
|
if (!coap_debug_set_packet_loss(optarg)) {
|
|
usage(argv[0], LIBCOAP_PACKAGE_VERSION);
|
|
exit(1);
|
|
}
|
|
break;
|
|
case 'n':
|
|
require_peer_cert = 0;
|
|
break;
|
|
case 'N':
|
|
resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_NON;
|
|
break;
|
|
case 'p' :
|
|
strncpy(port_str, optarg, NI_MAXSERV-1);
|
|
port_str[NI_MAXSERV - 1] = '\0';
|
|
break;
|
|
case 'R' :
|
|
root_ca_file = optarg;
|
|
break;
|
|
case 'v' :
|
|
log_level = strtol(optarg, NULL, 10);
|
|
break;
|
|
default:
|
|
usage( argv[0], LIBCOAP_PACKAGE_VERSION );
|
|
exit( 1 );
|
|
}
|
|
}
|
|
|
|
coap_startup();
|
|
coap_dtls_set_log_level(log_level);
|
|
coap_set_log_level(log_level);
|
|
|
|
ctx = get_context(addr_str, port_str);
|
|
if (!ctx)
|
|
return -1;
|
|
|
|
init_resources(ctx);
|
|
|
|
/* join multicast group if requested at command line */
|
|
if (group)
|
|
coap_join_mcast_group(ctx, group);
|
|
|
|
#ifdef _WIN32
|
|
signal(SIGINT, handle_sigint);
|
|
#else
|
|
memset (&sa, 0, sizeof(sa));
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_handler = handle_sigint;
|
|
sa.sa_flags = 0;
|
|
sigaction (SIGINT, &sa, NULL);
|
|
sigaction (SIGTERM, &sa, NULL);
|
|
#endif
|
|
|
|
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
|
|
|
|
while ( !quit ) {
|
|
int result = coap_run_once( ctx, wait_ms );
|
|
if ( result < 0 ) {
|
|
break;
|
|
} else if ( result && (unsigned)result < wait_ms ) {
|
|
/* decrement if there is a result wait time returned */
|
|
wait_ms -= result;
|
|
} else {
|
|
/*
|
|
* result == 0, or result >= wait_ms
|
|
* (wait_ms could have decremented to a small value, below
|
|
* the granularity of the timer in coap_run_once() and hence
|
|
* result == 0)
|
|
*/
|
|
time_t t_now = time(NULL);
|
|
if (t_last != t_now) {
|
|
/* Happens once per second */
|
|
t_last = t_now;
|
|
if (time_resource) {
|
|
coap_resource_notify_observers(time_resource, NULL);
|
|
}
|
|
}
|
|
if (result) {
|
|
/* result must have been >= wait_ms, so reset wait_ms */
|
|
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
|
|
}
|
|
}
|
|
|
|
#ifndef WITHOUT_ASYNC
|
|
/* check if we have to send asynchronous responses */
|
|
coap_ticks( &now );
|
|
check_async(ctx, now);
|
|
#endif /* WITHOUT_ASYNC */
|
|
}
|
|
|
|
coap_free_context(ctx);
|
|
coap_cleanup();
|
|
|
|
return 0;
|
|
}
|