/* * Copyright (c) 2015-2017 Ken Bannister. All rights reserved. * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup examples * @{ * * @file * @brief gcoap CLI support * * @author Ken Bannister * @author Hauke Petersen * * @} */ #include #include #include #include #include #include "event/periodic_callback.h" #include "event/thread.h" #include "fmt.h" #include "net/gcoap.h" #include "net/utils.h" #include "od.h" #include "periph/rtc.h" #include "time_units.h" #include "gcoap_example.h" #define ENABLE_DEBUG 0 #include "debug.h" #if IS_USED(MODULE_GCOAP_DTLS) #include "net/credman.h" #include "net/dsm.h" #include "tinydtls_keys.h" /* Example credential tag for credman. Tag together with the credential type needs to be unique. */ #define GCOAP_DTLS_CREDENTIAL_TAG 10 static const uint8_t psk_id_0[] = PSK_DEFAULT_IDENTITY; static const uint8_t psk_key_0[] = PSK_DEFAULT_KEY; static const credman_credential_t credential = { .type = CREDMAN_TYPE_PSK, .tag = GCOAP_DTLS_CREDENTIAL_TAG, .params = { .psk = { .key = { .s = psk_key_0, .len = sizeof(psk_key_0) - 1, }, .id = { .s = psk_id_0, .len = sizeof(psk_id_0) - 1, }, } }, }; #endif static ssize_t _encode_link(const coap_resource_t *resource, char *buf, size_t maxlen, coap_link_encoder_ctx_t *context); static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx); static ssize_t _riot_board_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx); #if IS_USED(MODULE_PERIPH_RTC) static ssize_t _rtc_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx); #endif /* CoAP resources. Must be sorted by path (ASCII order). */ static const coap_resource_t _resources[] = { { "/cli/stats", COAP_GET | COAP_PUT, _stats_handler, NULL }, { "/riot/board", COAP_GET, _riot_board_handler, NULL }, #if IS_USED(MODULE_PERIPH_RTC) { "/rtc", COAP_GET, _rtc_handler, NULL }, #endif }; static const char *_link_params[] = { ";ct=0;rt=\"count\";obs", NULL }; static gcoap_listener_t _listener = { &_resources[0], ARRAY_SIZE(_resources), GCOAP_SOCKET_TYPE_UNDEF, _encode_link, NULL, NULL }; /* Adds link format params to resource list */ static ssize_t _encode_link(const coap_resource_t *resource, char *buf, size_t maxlen, coap_link_encoder_ctx_t *context) { ssize_t res = gcoap_encode_link(resource, buf, maxlen, context); if (res > 0) { if (_link_params[context->link_pos] && (strlen(_link_params[context->link_pos]) < (maxlen - res))) { if (buf) { memcpy(buf+res, _link_params[context->link_pos], strlen(_link_params[context->link_pos])); } return res + strlen(_link_params[context->link_pos]); } } return res; } #if IS_USED(MODULE_PERIPH_RTC) static void _rtc_notify_observers(void *arg) { (void)arg; struct tm tm_now; if (rtc_get_time(&tm_now)) { DEBUG_PUTS("gcoap_server: RTC error"); return; } size_t len; char str_time[20] = ""; uint8_t buf[sizeof(coap_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1 + sizeof(str_time)]; coap_pkt_t pdu; const coap_resource_t *rtc_resource = NULL; const gcoap_listener_t *listener = NULL; while ((rtc_resource = gcoap_get_resource_by_path_iterator(&listener, rtc_resource, "/rtc"))) { if (!strcmp(rtc_resource->path, "/rtc")) { break; /* exact match */ } } if (rtc_resource) { switch (gcoap_obs_init(&pdu, buf, sizeof(buf), rtc_resource)) { case GCOAP_OBS_INIT_OK: len = coap_opt_finish(&pdu, COAP_OPT_FINISH_PAYLOAD); memcpy(pdu.payload, str_time, strftime(str_time, sizeof(str_time), "%Y-%m-%d %H:%M:%S", &tm_now)); pdu.payload_len = strlen(str_time); len += pdu.payload_len; if (!gcoap_obs_send(buf, len, rtc_resource)) { DEBUG_PUTS("gcoap_server: cannot send /rtc notification"); } break; case GCOAP_OBS_INIT_UNUSED: DEBUG_PUTS("gcoap_server: no observer for /rtc"); break; case GCOAP_OBS_INIT_ERR: DEBUG_PUTS("gcoap_server: error initializing /rtc notification"); break; } } } static ssize_t _rtc_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx) { (void)ctx; struct tm tm_now; rtc_get_time(&tm_now); gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD); char str_time[20] = ""; memcpy(pdu->payload, str_time, strftime(str_time, sizeof(str_time), "%Y-%m-%d %H:%M:%S", &tm_now)); pdu->payload_len = strlen(str_time); resp_len += pdu->payload_len; return resp_len; } #endif /* * Server callback for /cli/stats. Accepts either a GET or a PUT. * * GET: Returns the count of packets sent by the CLI. * PUT: Updates the count of packets. Rejects an obviously bad request, but * allows any two byte value for example purposes. Semantically, the only * valid action is to set the value to 0. */ static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx) { (void)ctx; /* read coap method type in packet */ unsigned method_flag = coap_method2flag(coap_get_code_detail(pdu)); switch (method_flag) { case COAP_GET: gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); coap_opt_add_format(pdu, COAP_FORMAT_TEXT); size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD); /* write the response buffer with the request count value */ resp_len += fmt_u16_dec((char *)pdu->payload, req_count); return resp_len; case COAP_PUT: /* convert the payload to an integer and update the internal value */ if (pdu->payload_len <= 5) { char payload[6] = { 0 }; memcpy(payload, (char *)pdu->payload, pdu->payload_len); req_count = (uint16_t)strtoul(payload, NULL, 10); return gcoap_response(pdu, buf, len, COAP_CODE_CHANGED); } else { return gcoap_response(pdu, buf, len, COAP_CODE_BAD_REQUEST); } } return 0; } static ssize_t _riot_board_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx) { (void)ctx; gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); coap_opt_add_format(pdu, COAP_FORMAT_TEXT); size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD); /* write the RIOT board name in the response buffer */ if (pdu->payload_len >= strlen(RIOT_BOARD)) { memcpy(pdu->payload, RIOT_BOARD, strlen(RIOT_BOARD)); return resp_len + strlen(RIOT_BOARD); } else { puts("gcoap_cli: msg buffer too small"); return gcoap_response(pdu, buf, len, COAP_CODE_INTERNAL_SERVER_ERROR); } } void notify_observers(void) { size_t len; uint8_t buf[CONFIG_GCOAP_PDU_BUF_SIZE]; coap_pkt_t pdu; /* send Observe notification for /cli/stats */ switch (gcoap_obs_init(&pdu, &buf[0], CONFIG_GCOAP_PDU_BUF_SIZE, &_resources[0])) { case GCOAP_OBS_INIT_OK: DEBUG("gcoap_cli: creating /cli/stats notification\n"); coap_opt_add_format(&pdu, COAP_FORMAT_TEXT); len = coap_opt_finish(&pdu, COAP_OPT_FINISH_PAYLOAD); len += fmt_u16_dec((char *)pdu.payload, req_count); gcoap_obs_send(&buf[0], len, &_resources[0]); break; case GCOAP_OBS_INIT_UNUSED: DEBUG("gcoap_cli: no observer for /cli/stats\n"); break; case GCOAP_OBS_INIT_ERR: DEBUG("gcoap_cli: error initializing /cli/stats notification\n"); break; } } void server_init(void) { #if IS_USED(MODULE_GCOAP_DTLS) int res = credman_add(&credential); if (res < 0 && res != CREDMAN_EXIST) { /* ignore duplicate credentials */ printf("gcoap: cannot add credential to system: %d\n", res); return; } sock_dtls_t *gcoap_sock_dtls = gcoap_get_sock_dtls(); res = sock_dtls_add_credential(gcoap_sock_dtls, GCOAP_DTLS_CREDENTIAL_TAG); if (res < 0) { printf("gcoap: cannot add credential to DTLS sock: %d\n", res); } #endif gcoap_register_listener(&_listener); #if IS_USED(MODULE_PERIPH_RTC) static event_periodic_callback_t _ev_pcb_rtc; event_periodic_callback_init(&_ev_pcb_rtc, ZTIMER_MSEC, EVENT_PRIO_MEDIUM, _rtc_notify_observers, NULL); event_periodic_callback_start(&_ev_pcb_rtc, 10 * MS_PER_SEC); #endif }