From 7da09f6a1e2319f23194f8dbdb5672b928cf702e Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 20 Feb 2025 19:23:05 +0000 Subject: [PATCH] Add COAP example Adds a COAP client and server example. Requires libcoap and tinydtls. --- README.md | 1 + pico_w/wifi/CMakeLists.txt | 1 + pico_w/wifi/coap/CMakeLists.txt | 87 ++++++ pico_w/wifi/coap/README.md | 64 +++++ pico_w/wifi/coap/certs/.gitignore | 1 + pico_w/wifi/coap/certs/makecerts.sh | 42 +++ pico_w/wifi/coap/coap_client.c | 373 ++++++++++++++++++++++++ pico_w/wifi/coap/coap_config.h | 76 +++++ pico_w/wifi/coap/coap_server.c | 392 ++++++++++++++++++++++++++ pico_w/wifi/coap/libcoap.cmake | 56 ++++ pico_w/wifi/coap/libtinydtls.cmake | 36 +++ pico_w/wifi/coap/lwipopts.h | 29 ++ pico_w/wifi/coap/lwippools.h | 174 ++++++++++++ pico_w/wifi/coap/mbedtls_alt_timing.c | 33 +++ pico_w/wifi/coap/mbedtls_config.h | 25 ++ pico_w/wifi/coap/timing_alt.h | 16 ++ 16 files changed, 1406 insertions(+) create mode 100644 pico_w/wifi/coap/CMakeLists.txt create mode 100644 pico_w/wifi/coap/README.md create mode 100644 pico_w/wifi/coap/certs/.gitignore create mode 100755 pico_w/wifi/coap/certs/makecerts.sh create mode 100644 pico_w/wifi/coap/coap_client.c create mode 100644 pico_w/wifi/coap/coap_config.h create mode 100644 pico_w/wifi/coap/coap_server.c create mode 100644 pico_w/wifi/coap/libcoap.cmake create mode 100644 pico_w/wifi/coap/libtinydtls.cmake create mode 100644 pico_w/wifi/coap/lwipopts.h create mode 100644 pico_w/wifi/coap/lwippools.h create mode 100644 pico_w/wifi/coap/mbedtls_alt_timing.c create mode 100644 pico_w/wifi/coap/mbedtls_config.h create mode 100644 pico_w/wifi/coap/timing_alt.h diff --git a/README.md b/README.md index 5a436a348..87de55305 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ App|Description [picow_http_client](pico_w/wifi/http_client) | Demonstrates how to make http and https requests [picow_http_client_verify](pico_w/wifi/http_client) | Demonstrates how to make a https request with server authentication [picow_mqtt_client](pico_w/wifi/mqtt) | Demonstrates how to implement a MQTT client application +[picow_coap](pico_w/wifi/mqtt) | Demonstrates how to implment a COAP client #### FreeRTOS examples diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index 31797084b..912a9a7e3 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -19,6 +19,7 @@ else() add_subdirectory_exclude_platforms(udp_beacon) add_subdirectory_exclude_platforms(http_client) add_subdirectory_exclude_platforms(mqtt) + add_subdirectory_exclude_platforms(coap) if (NOT PICO_MBEDTLS_PATH) message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined") diff --git a/pico_w/wifi/coap/CMakeLists.txt b/pico_w/wifi/coap/CMakeLists.txt new file mode 100644 index 000000000..f5b56d575 --- /dev/null +++ b/pico_w/wifi/coap/CMakeLists.txt @@ -0,0 +1,87 @@ +if (DEFINED ENV{COAP_SERVER} AND NOT COAP_SERVER) + set(COAP_SERVER $ENV{COAP_SERVER}) + message("Using COAP_SERVER from environment ('${COAP_SERVER}')") +endif() +if (NOT COAP_SERVER) + message("Skipping COAP example as COAP_SERVER is not defined") + return() +endif() +set(COAP_SERVER "${COAP_SERVER}" CACHE INTERNAL "COAP server for examples") +if (DEFINED ENV{LIB_COAP_DIR} AND NOT LIB_COAP_DIR) + set(LIB_COAP_DIR $ENV{LIB_COAP_DIR}) +endif() +if (NOT LIB_COAP_DIR) + message("Skipping COAP example as LIB_COAP_DIR is not defined") + return() +endif() +set(LIB_COAP_DIR "${LIB_COAP_DIR}" CACHE INTERNAL "LIB_COAP_DIR for examples") +if (DEFINED ENV{LIB_TINYDTLS_DIR} AND NOT LIB_TINYDTLS_DIR) + set(LIB_TINYDTLS_DIR $ENV{LIB_TINYDTLS_DIR}) +endif() +if (NOT LIB_TINYDTLS_DIR) + message("Skipping COAP example as LIB_TINYDTLS_DIR is not defined") + return() +endif() +set(LIB_TINYDTLS_DIR "${LIB_TINYDTLS_DIR}" CACHE INTERNAL "LIB_TINYDTLS_DIR for examples") +include(libcoap.cmake) +include(libtinydtls.cmake) + +set(TARGET_NAME coap_server) +add_executable(${TARGET_NAME} + coap_server.c + ) +target_include_directories(${TARGET_NAME} PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common headers + ) +target_link_libraries(${TARGET_NAME} PRIVATE + pico_cyw43_arch_lwip_threadsafe_background + pico_async_context_threadsafe_background + pico_lwip_nosys + pico_stdlib + pico_coap + hardware_adc + pico_tinydtls + ) +target_compile_definitions(${TARGET_NAME} PRIVATE + CYW43_HOST_NAME=\"pico_coap_example\" + ) +if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/certs/${COAP_SERVER}") + target_compile_definitions(${TARGET_NAME} PRIVATE + COAP_CERT_INC=\"certs/${COAP_SERVER}/coap_server.inc\" + ) +endif() +target_compile_definitions(${TARGET_NAME} PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + ) +pico_add_extra_outputs(${TARGET_NAME}) + +set(TARGET_NAME coap_client) +add_executable(${TARGET_NAME} + coap_client.c + ) +target_include_directories(${TARGET_NAME} PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts + ) +target_link_libraries(${TARGET_NAME} PRIVATE + pico_cyw43_arch_lwip_threadsafe_background + pico_lwip_nosys + pico_stdlib + pico_coap + pico_tinydtls + ) +target_compile_definitions(${TARGET_NAME} PRIVATE + COAP_SERVER=\"${COAP_SERVER}\" + ) +if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/certs/${COAP_SERVER}") + target_compile_definitions(${TARGET_NAME} PRIVATE + COAP_CERT_INC=\"certs/${COAP_SERVER}/coap_client.inc\" + ) +endif() +target_compile_definitions(${TARGET_NAME} PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + ) +pico_add_extra_outputs(${TARGET_NAME}) diff --git a/pico_w/wifi/coap/README.md b/pico_w/wifi/coap/README.md new file mode 100644 index 000000000..0bb902039 --- /dev/null +++ b/pico_w/wifi/coap/README.md @@ -0,0 +1,64 @@ +# Setup + +These examples require libcoap and tinydtls to work. Clone these repos somewhere,,, + +``` +git clone https://github.com/obgm/libcoap.git +git submodule update --init +``` + +You can define these paths in environment variables or pass the names to cmake + +``` +export LIB_COAP_DIR=/home/pi/libcoap +export LIB_TINYDTLS_DIR=/home/pi/tinydtls +``` + +You also need to define the coap server in another environment variable... + +``` +export COAP_SERVER=myhost +``` + +The examples use keys generated by a script in the certs folder... + +``` +cd certs +./makecerts.sh +``` + +The code should now build. +tinydtls and this example currently only supports raw public keys. + +``` +cd libcoap +mkdir build +cd build +cmake .. -DDTLS_BACKEND=tinydtls +make -j8 +``` + +# Coap Client + +To test the client you need a server to talk to. The libcoap library comes with the coap-server example that should work. +server.key is generated by the makecerts.sh script above. + +``` +coap-server -M server.key -v 7 +``` + +The coap client example requests "coap:///.well-known/core" and subscribes to "coap:///time" and coap:///subscribe?123/led + +# Coap Server + +The coap server example supports the following... + +* Find out what requests it supports: coap-client -M client.key -m get coaps://$COAP_SERVER/.well-known/core +* Get the current temperature: coap-client -M client.key -m get coaps://$COAP_SERVER/temp +* Get the current led state: coap-client -M client.key -m get coaps://$COAP_SERVER/led +* Turn the led on: coap-client -M client.key -m put coaps://$COAP_SERVER/led -e 1 +* Subscribe to temperature changes: coap-client -M client.key -m get coaps://$COAP_SERVER/temp -s 60 -w +* Get time: coap-client -M client.key -m get coaps://$COAP_SERVER/time + +$COAP_SERVER is the name of the coap server. +client.key is generated by the makecerts.sh script above. diff --git a/pico_w/wifi/coap/certs/.gitignore b/pico_w/wifi/coap/certs/.gitignore new file mode 100644 index 000000000..355164c12 --- /dev/null +++ b/pico_w/wifi/coap/certs/.gitignore @@ -0,0 +1 @@ +*/ diff --git a/pico_w/wifi/coap/certs/makecerts.sh b/pico_w/wifi/coap/certs/makecerts.sh new file mode 100755 index 000000000..88734ed76 --- /dev/null +++ b/pico_w/wifi/coap/certs/makecerts.sh @@ -0,0 +1,42 @@ +#!/usr/bin/bash + +if [ "${PWD##*/}" != "certs" ]; then + echo Run this in the certs folder + exit 1 +fi +if [ -z "$COAP_SERVER" ]; then + echo Define COAP_SERVER + exit 1 +fi +SERVER_NAME=$COAP_SERVER + +if [ -d "$SERVER_NAME" ]; then + echo Run \"rm -fr $SERVER_NAME\" to regenerate these keys + exit 1 +fi +mkdir $SERVER_NAME +echo Generating keys in $PWD/$SERVER_NAME + +openssl ecparam -name prime256v1 -genkey -noout -out $SERVER_NAME/client.key +openssl ec -in $SERVER_NAME/client.key -pubout -out $SERVER_NAME/client.pub + +echo -n \#define COAP_KEY \" > $SERVER_NAME/coap_client.inc +cat $SERVER_NAME/client.key | awk '{printf "%s\\n\\\n", $0}' >> $SERVER_NAME/coap_client.inc +echo "\"" >> $SERVER_NAME/coap_client.inc +echo >> $SERVER_NAME/coap_client.inc + +echo -n \#define COAP_PUB_KEY \" >> $SERVER_NAME/coap_client.inc +cat $SERVER_NAME/client.pub | awk '{printf "%s\\n\\\n", $0}' >> $SERVER_NAME/coap_client.inc +echo "\"" >> $SERVER_NAME/coap_client.inc + +openssl ecparam -name prime256v1 -genkey -noout -out $SERVER_NAME/server.key +openssl ec -in $SERVER_NAME/server.key -pubout -out $SERVER_NAME/server.pub + +echo -n \#define COAP_KEY \" > $SERVER_NAME/coap_server.inc +cat $SERVER_NAME/server.key | awk '{printf "%s\\n\\\n", $0}' >> $SERVER_NAME/coap_server.inc +echo "\"" >> $SERVER_NAME/coap_server.inc +echo >> $SERVER_NAME/coap_server.inc + +echo -n \#define COAP_PUB_KEY \" >> $SERVER_NAME/coap_server.inc +cat $SERVER_NAME/server.pub | awk '{printf "%s\\n\\\n", $0}' >> $SERVER_NAME/coap_server.inc +echo "\"" >> $SERVER_NAME/coap_server.inc diff --git a/pico_w/wifi/coap/coap_client.c b/pico_w/wifi/coap/coap_client.c new file mode 100644 index 000000000..715f5828f --- /dev/null +++ b/pico_w/wifi/coap/coap_client.c @@ -0,0 +1,373 @@ +/** + * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// A simple coap client that requests "coap:///.well-known/core" and "coap:///time" +// Run coap-server from the libcoap library, e.g. for udp // ./coap-server -v 7 +// coap with dtls currently only works with tinydtls and it only supports raw public keys +// build the server like this cmake .. -DDTLS_BACKEND=tinydtls +// then run the server like this: ./coap-server -M server.key -v 7 + +#include +#include + +#include "pico/stdlib.h" +#include "pico/cyw43_arch.h" +#include "pico/lwip_nosys.h" + +#include "lwip/netif.h" +#include "lwip/dns.h" + +#include "coap_config.h" +#include + +#ifdef COAP_CERT_INC +#include COAP_CERT_INC +#endif + +#ifndef COAP_SERVER +#error Need to define COAP_SERVER +#endif + +static coap_context_t *main_coap_context; + +// token for observing time requests +static uint8_t time_observe_token_data[COAP_TOKEN_DEFAULT_MAX]; +coap_binary_t time_observe_token = { 0, time_observe_token_data }; + +// token for observing led requests +static uint8_t led_observe_token_data[COAP_TOKEN_DEFAULT_MAX]; +coap_binary_t led_observe_token = { 0, led_observe_token_data }; + +// Give up after getting a few responses +static int time_counter = 10; +static bool test_complete; + +// The client sends a ping expecting a confirmation. It retries 4 times +// If the server is dead we'll see a COAP_NACK_TOO_MANY_RETRIES error +#define KEEP_ALIVE_TIME_S 30 + +#ifndef MAX_COAP_URI_STR +#define MAX_COAP_URI_STR 100 +#endif + +#define COAP_CN "pico" + +#ifdef COAP_CERT_INC +#define COAP_PORT COAPS_DEFAULT_PORT +#else +#define COAP_PORT COAP_DEFAULT_PORT +#endif + +void coap_io_process_worker_func(async_context_t *context, async_at_time_worker_t *worker) +{ + assert(worker->user_data); + coap_context_t *coap_context = (coap_context_t*)worker->user_data; + + coap_tick_t before; + unsigned int num_sockets; + unsigned int timeout; + + coap_ticks(&before); + timeout = coap_io_prepare_io(coap_context, NULL, 0, &num_sockets, before); + if (timeout < 1000) { + timeout = 1000; + } + async_context_add_at_time_worker_in_ms(context, worker, timeout); + coap_context->timer_configured = 1; +} +static async_at_time_worker_t coap_worker = { + .do_work = coap_io_process_worker_func +}; + +static coap_response_t message_handler(coap_session_t *session, const coap_pdu_t *sent, const coap_pdu_t *received, const coap_mid_t id) { + const uint8_t *data; + size_t len, offset, total; + if (coap_get_data_large(received, &len, &data, &offset, &total)) { + printf("%*.*s", (int)len, (int)len, (const char *)data); + if (len + offset == total) { + printf("\n"); + } + } + coap_bin_const_t token = coap_pdu_get_token(received); + if (coap_binary_equal(&token, &time_observe_token)) { + time_counter--; + if (time_counter <= 0 && time_observe_token.length > 0) { + coap_cancel_observe(session, &time_observe_token, coap_pdu_get_type(sent)); + time_observe_token.length = 0; + } + } else if (coap_binary_equal(&token, &led_observe_token)) { + if (len >= 1) { + cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN, data[0] != '0'); + } + } + return COAP_RESPONSE_OK; +} + +static void nack_handler(coap_session_t *session COAP_UNUSED, const coap_pdu_t *sent COAP_UNUSED, const coap_nack_reason_t reason, const coap_mid_t id COAP_UNUSED) { + coap_log_debug("nack_handler: %d\n", reason); + switch (reason) { + case COAP_NACK_TOO_MANY_RETRIES: + case COAP_NACK_NOT_DELIVERABLE: + case COAP_NACK_RST: + case COAP_NACK_TLS_FAILED: + panic("cannot send coap pdu"); + break; + case COAP_NACK_ICMP_ISSUE: + default: + break; + } + return; +} + +bool make_request(coap_session_t *session, const coap_address_t *server_addr_coap, const char *url, bool confirm, coap_binary_t *subscribe_token) { + + static char uri_str[MAX_COAP_URI_STR]; + snprintf(uri_str, sizeof(uri_str), "coap://%s/%s", ipaddr_ntoa(&server_addr_coap->addr), url); + + bool success = false; + coap_optlist_t *optlist = NULL; + do { + coap_uri_t uri_coap; + int len = coap_split_uri((const unsigned char *)uri_str, strlen(uri_str), &uri_coap); + assert(len == 0); + if (len != 0) { + coap_log_err("coap_split_uri failed\n"); + break; + } + coap_pdu_t *pdu = coap_pdu_init(confirm ? COAP_MESSAGE_CON : COAP_MESSAGE_NON, COAP_REQUEST_CODE_GET, coap_new_message_id(session), coap_session_max_pdu_size(session)); + if (!pdu) { + coap_log_err("coap_pdu_init failed\n"); + break; + } + + static uint8_t buf[MAX_COAP_URI_STR]; + len = coap_uri_into_options(&uri_coap, server_addr_coap, &optlist, 1, buf, sizeof(buf)); + assert(len == 0 && optlist); + if (len != 0 || !optlist) { + coap_log_err("coap_uri_into_options failed\n"); + break; + } + if (subscribe_token) { + // add observe option + coap_insert_optlist(&optlist, coap_new_optlist(COAP_OPTION_OBSERVE, coap_encode_var_safe(buf, sizeof(buf), COAP_OBSERVE_ESTABLISH), buf)); + // add token to request + coap_session_new_token(session, &subscribe_token->length, subscribe_token->s); + if (!coap_add_token(pdu, subscribe_token->length, subscribe_token->s)) { + coap_log_debug("cannot add token to request\n"); + } + } + + int res = coap_add_optlist_pdu(pdu, &optlist); + if (res != 1) { + coap_log_err("coap_add_optlist_pdu failed\n"); + break; + } + + printf("Sending %s\n", uri_str); + success = (coap_send(session, pdu) >= 0); + if (!success) { + coap_log_err("coap_send failed\n"); + break; + } + } while(false); + if (optlist) coap_delete_optlist(optlist); + return success; +} + +static int verify_cn_callback(const char *cn, + const uint8_t *asn1_public_cert, size_t asn1_length COAP_UNUSED, + coap_session_t *session COAP_UNUSED, unsigned depth, + int validated COAP_UNUSED, void *arg COAP_UNUSED) { + coap_log_info("CN '%s' presented by server (%s)\n", cn, depth ? "CA" : "Certificate"); + return 1; +} + +static coap_context_t *start_client(const ip_addr_t *server_addr) { + coap_log_info("server address %s\n", ipaddr_ntoa(server_addr)); + + // Should not call this twice + static bool done; + assert(!done); + +#ifndef NDEBUG + coap_set_log_level(COAP_LOG_DEBUG); +#else + coap_set_log_level(COAP_LOG_INFO); +#endif + + coap_context_t *ctx = coap_new_context(NULL); + assert(ctx); + if (!ctx) { + coap_log_err("failed to create coap context\n"); + return NULL; + } + + coap_context_set_keepalive(ctx, KEEP_ALIVE_TIME_S); + coap_context_set_block_mode(ctx, COAP_BLOCK_USE_LIBCOAP); + + coap_address_t server_addr_coap; + server_addr_coap.addr = *server_addr; + coap_address_set_port(&server_addr_coap, COAP_PORT); + + coap_session_t *session = NULL; +#ifdef COAP_CERT_INC + // Set up security stuff + static const uint8_t coap_key[] = COAP_KEY; + static const uint8_t coap_pub_key[] = COAP_PUB_KEY; + static coap_dtls_pki_t dtls_pki; + memset(&dtls_pki, 0, sizeof(dtls_pki)); + dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION; + dtls_pki.validate_cn_call_back = verify_cn_callback; + dtls_pki.client_sni = COAP_CN; + dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF; + dtls_pki.is_rpk_not_cert = 1; // tinydtls only supports raw public keys + dtls_pki.verify_peer_cert = 1; // not implemented? + dtls_pki.pki_key.key.pem_buf.public_cert = coap_pub_key; // rpk + dtls_pki.pki_key.key.pem_buf.public_cert_len = sizeof(coap_pub_key); + dtls_pki.pki_key.key.pem_buf.private_key = coap_key; + dtls_pki.pki_key.key.pem_buf.private_key_len = sizeof(coap_key); + session = coap_new_client_session_pki(ctx, NULL, &server_addr_coap, COAP_PROTO_DTLS, &dtls_pki); +#else + session = coap_new_client_session(ctx, NULL, &server_addr_coap, COAP_PROTO_UDP); +#endif + assert(session); + if (!session) { + coap_log_err("failed to create coap session\n"); + coap_free_context(ctx); + return NULL; + } + + coap_register_response_handler(ctx, message_handler); + coap_register_nack_handler(ctx, nack_handler); + + bool success = make_request(session, &server_addr_coap, ".well-known/core", true, NULL); + assert(success); + if (!success) { + coap_log_err("coap request failed\n"); + coap_free_context(ctx); + return NULL; + } + success = make_request(session, &server_addr_coap, "time", true, &time_observe_token); + assert(success); + if (!success) { + coap_log_err("coap time request failed\n"); + coap_free_context(ctx); + return NULL; + } + success = make_request(session, &server_addr_coap, "led", true, &led_observe_token); + assert(success); + if (!success) { + coap_log_err("coap led request failed\n"); + coap_free_context(ctx); + return NULL; + } + + done = true; + return ctx; +} + +// Call back with a DNS result +static void server_address_found(__unused const char *hostname, const ip_addr_t *addr, __unused void *arg) { + if (addr) { + main_coap_context = start_client(addr); + if (!main_coap_context) { + panic("Test failed"); + } + coap_worker.user_data = main_coap_context; + async_context_add_at_time_worker_in_ms(cyw43_arch_async_context(), &coap_worker, 1000); + } else { + panic("dns request failed"); // todo: retry? + } +} + +static bool coap_client_init(async_context_t *context) +{ + coap_startup(); + coap_set_log_level(COAP_LOG_WARN); + + async_context_acquire_lock_blocking(context); + ip_addr_t addr; + int ret = dns_gethostbyname(COAP_SERVER, &addr, server_address_found, main_coap_context); + async_context_release_lock(context); + if (ret == ERR_OK) { + async_context_acquire_lock_blocking(context); + server_address_found(COAP_SERVER, &addr, NULL); + coap_worker.user_data = main_coap_context; + async_context_add_at_time_worker_in_ms(context, &coap_worker, 1000); + async_context_release_lock(context); + } else if (ret != ERR_INPROGRESS) { + panic("dns request failed"); + } + + return true; +} + +static void coap_client_deinit(void) +{ + coap_free_context(main_coap_context); + main_coap_context = NULL; + coap_cleanup(); +} + +void key_pressed_worker_func(async_context_t *context, async_when_pending_worker_t *worker) { + int key = getchar_timeout_us(0); // get any pending key press but don't wait + if (test_complete) { + return; + } + if (key == 'q' || key == 'Q') { + test_complete = true; + } +} + +static async_when_pending_worker_t key_pressed_worker = { + .do_work = key_pressed_worker_func +}; + +void key_pressed_func(void *param) { + key_pressed_worker.user_data = param; + async_context_set_work_pending(cyw43_arch_async_context(), &key_pressed_worker); +} + + +int main() { + stdio_init_all(); + + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return 1; + } + cyw43_arch_enable_sta_mode(); + printf("Connecting to Wi-Fi...\n"); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { + printf("failed to connect.\n"); + return 1; + } else { + printf("Connected.\n"); + } + printf("Ready, using server at %s\n", COAP_SERVER); + printf("Press 'q' to quit\n"); + cyw43_wifi_pm(&cyw43_state, CYW43_NONE_PM); + + coap_client_init(cyw43_arch_async_context()); + async_context_add_when_pending_worker(cyw43_arch_async_context(), &key_pressed_worker); + stdio_set_chars_available_callback(key_pressed_func, NULL); + + while (!test_complete) { + cyw43_arch_poll(); + cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000)); + } + + coap_client_deinit(); + lwip_nosys_deinit(cyw43_arch_async_context()); + cyw43_arch_deinit(); + panic("Test passed"); + return 0; +} + +void coap_address_init(coap_address_t *addr) { + assert(addr); + memset(addr, 0, sizeof(coap_address_t)); +} diff --git a/pico_w/wifi/coap/coap_config.h b/pico_w/wifi/coap/coap_config.h new file mode 100644 index 000000000..e63b27f27 --- /dev/null +++ b/pico_w/wifi/coap/coap_config.h @@ -0,0 +1,76 @@ +/* + * coap_config.h.lwip -- LwIP configuration for libcoap + * + * Copyright (C) 2021-2023 Olaf Bergmann and others + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#ifndef COAP_CONFIG_H_ +#define COAP_CONFIG_H_ + +#include +#include +#include /* provide ntohs, htons */ + +#define WITH_LWIP 1 + +#if LWIP_IPV4 +#define COAP_IPV4_SUPPORT 1 +#endif /* LWIP_IPV4 */ + +#if LWIP_IPV6 +#define COAP_IPV6_SUPPORT 1 +#endif /* LWIP_IPV6 */ + +#ifndef COAP_CONSTRAINED_STACK +/* Define to 1 to minimize stack usage. */ +#define COAP_CONSTRAINED_STACK 1 +#endif + +#ifndef COAP_DISABLE_TCP +/* Define to 1 to build without TCP support. */ +#define COAP_DISABLE_TCP 1 +#endif + +#ifndef COAP_ASYNC_SUPPORT +/* Define to 1 to build with support for async separate responses. */ +#define COAP_ASYNC_SUPPORT 1 +#endif + +#ifndef COAP_WITH_OBSERVE_PERSIST +/* Define to 1 to build support for persisting observes. */ +#define COAP_WITH_OBSERVE_PERSIST 0 +#endif + +#ifndef COAP_WS_SUPPORT +/* Define to 1 to build with WebSockets support. */ +#define COAP_WS_SUPPORT 0 +#endif + +#ifndef COAP_Q_BLOCK_SUPPORT +/* Define to 1 to build with Q-Block (RFC9177) support. */ +#define COAP_Q_BLOCK_SUPPORT 0 +#endif + +#define PACKAGE_NAME "libcoap" +#define PACKAGE_VERSION "4.3.4" +#define PACKAGE_STRING "libcoap 4.3.4" + +/* it's just provided by libc. i hope we don't get too many of those, as + * actually we'd need autotools again to find out what environment we're + * building in */ +#define HAVE_STRNLEN 1 + +#define HAVE_LIMITS_H + +//#define HAVE_NETDB_H + +#define HAVE_SNPRINTF + +#define HAVE_ERRNO_H + +#endif /* COAP_CONFIG_H_ */ diff --git a/pico_w/wifi/coap/coap_server.c b/pico_w/wifi/coap/coap_server.c new file mode 100644 index 000000000..3740f2e5e --- /dev/null +++ b/pico_w/wifi/coap/coap_server.c @@ -0,0 +1,392 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// A simple coap server +// Find out what requests it supports: coap-client -M $CLIENT_KEY -m get coaps://$COAP_SERVER/.well-known/core +// Get the current temperature: coap-client -M $CLIENT_KEY -m get coaps://$COAP_SERVER/temp +// Get the current led state: coap-client -M $CLIENT_KEY -m get coaps://$COAP_SERVER/led +// Turn the led on: coap-client -M $CLIENT_KEY -m put coaps://$COAP_SERVER/led -e 1 +// Subscribe to temperature changes: coap-client -M $CLIENT_KEY -m get coaps://$COAP_SERVER/temp -s 60 -w +// Get time: coap-client -M $CLIENT_KEY -m get coaps://$COAP_SERVER/time +// where $COAP_SERVER is the host name of the coap server +// and $CLIENT_KEY is the file path to the server.key (private raw public key when built with tinydtls) + +#include +#include + +#include "pico/stdlib.h" +#include "pico/cyw43_arch.h" +#include "pico/lwip_nosys.h" +#include "pico/util/datetime.h" +#include "hardware/adc.h" + +#include "lwip/netif.h" + +#include "coap_config.h" +#include + +#ifdef COAP_CERT_INC +#include COAP_CERT_INC +#endif + +#define ADC_CHANNEL_TEMPSENSOR 4 +#define TEMP_WORKER_TIME_S 30 +#define SESSION_TIMEOUT 30 + +#ifdef COAP_CERT_INC +#define COAP_PORT COAPS_DEFAULT_PORT +#define COAP_PROTO COAP_PROTO_DTLS +#else +#define COAP_PORT COAP_DEFAULT_PORT +#define COAP_PROTO COAP_PROTO_UDP +#endif + +static coap_context_t *main_coap_context; +static coap_resource_t *temp_resource = NULL; +static coap_resource_t *led_resource = NULL; +static coap_resource_t *time_resource = NULL; +static float temp_deg_c; + +static void coap_io_process_worker_func(async_context_t *context, async_at_time_worker_t *worker) +{ + assert(worker->user_data); + coap_context_t *coap_context = (coap_context_t*)worker->user_data; + + coap_tick_t before; + unsigned int num_sockets; + unsigned int timeout; + + coap_ticks(&before); + timeout = coap_io_prepare_io(coap_context, NULL, 0, &num_sockets, before); + if (timeout < 1000) { + timeout = 1000; + } + async_context_add_at_time_worker_in_ms(context, worker, timeout); + coap_context->timer_configured = 1; +} +static async_at_time_worker_t coap_worker = { + .do_work = coap_io_process_worker_func +}; + +void check_notify_worker_func(async_context_t *async_context, async_when_pending_worker_t *worker) +{ + assert(worker->user_data); + coap_context_t *coap_context = (coap_context_t*)worker->user_data; + coap_check_notify(coap_context); +} + +static async_when_pending_worker_t check_notify_worker = { + .do_work = check_notify_worker_func +}; + +static void notify_observers(coap_resource_t *resource) +{ + coap_resource_notify_observers(resource, NULL); + async_context_set_work_pending(cyw43_arch_async_context(), &check_notify_worker); +} + +bool sample_temp(void) { + adc_select_input(ADC_CHANNEL_TEMPSENSOR); + uint32_t raw32 = adc_read(); + const uint32_t bits = 12; + + // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) + uint16_t raw16 = raw32 << (16 - bits) | raw32 >> (2 * bits - 16); + + // ref https://github.com/raspberrypi/pico-micropython-examples/blob/master/adc/temperature.py + const float conversion_factor = 3.3 / (65535); + float reading = raw16 * conversion_factor; + + // The temperature sensor measures the Vbe voltage of a biased bipolar diode, connected to the fifth ADC channel + // Typically, Vbe = 0.706V at 27 degrees C, with a slope of -1.721mV (0.001721) per degree. + float deg_c = 27 - (reading - 0.706) / 0.001721; + if (deg_c != temp_deg_c) { + temp_deg_c = deg_c; + coap_log_info("Temp %.2f\n", temp_deg_c); + return true; + } + return false; +} + +// Periodically publish temperature +static void temp_worker_func(async_context_t *context, async_at_time_worker_t *worker) { + assert(worker->user_data); + if (sample_temp()) { + notify_observers(temp_resource); + } + if (temp_resource->subscribers) { + async_context_add_at_time_worker_in_ms(context, worker, TEMP_WORKER_TIME_S * 1000); + } else { + worker->user_data = NULL; + } +} + +static async_at_time_worker_t temp_worker = { + .do_work = temp_worker_func +}; + +static void resource_get_temp(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { + unsigned char buf[40]; + size_t len; + assert(temp_resource == resource); + response->code = COAP_RESPONSE_CODE(404); + coap_add_option(response, COAP_OPTION_CONTENT_FORMAT, coap_encode_var_safe(buf, sizeof(buf), COAP_MEDIATYPE_TEXT_PLAIN), buf); + coap_add_option(response, COAP_OPTION_MAXAGE, coap_encode_var_safe(buf, sizeof(buf), 10), buf); + if (query == NULL || coap_string_equal(query, coap_make_str_const("chip"))) { + sample_temp(); + len = snprintf((char *)buf, sizeof(buf), "%.2f", temp_deg_c); + coap_add_data(response, len, buf); + response->code = COAP_RESPONSE_CODE(205); // or COAP_RESPONSE_CODE(404) if not available + } + if (temp_resource->subscribers && !temp_worker.user_data) { + temp_worker.user_data = temp_resource; + async_context_add_at_time_worker_in_ms(cyw43_arch_async_context(), &temp_worker, TEMP_WORKER_TIME_S * 1000); + } +} + +static void resource_get_led(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { + unsigned char buf[40]; + size_t len; + + response->code = COAP_RESPONSE_CODE(404); + if (query == NULL || coap_string_equal(query, coap_make_str_const("onboard"))) { + coap_add_option(response, COAP_OPTION_CONTENT_FORMAT, coap_encode_var_safe(buf, sizeof(buf), COAP_MEDIATYPE_TEXT_PLAIN), buf); + coap_add_option(response, COAP_OPTION_MAXAGE, coap_encode_var_safe(buf, sizeof(buf), 10), buf); + response->code = COAP_RESPONSE_CODE(205); + bool value; + cyw43_gpio_get(&cyw43_state, CYW43_WL_GPIO_LED_PIN, &value); + len = snprintf((char *)buf, sizeof(buf), "%s", value ? "1" : "0"); + coap_add_data(response, len, buf); + } +} + +static void resource_put_led(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { + size_t size; + const uint8_t *data; + bool handled = false; + bool changed = false; + unsigned char buf[40]; + coap_get_data(request, &size, &data); + if (size > 0 && (query == NULL || coap_string_equal(query, coap_make_str_const("onboard")))) { + coap_str_const_t *value = coap_new_str_const(data, size); + if (coap_string_equal(value, coap_make_str_const("1")) || coap_string_equal(value, coap_make_str_const("on"))) { + bool led_gpio; + cyw43_gpio_get(&cyw43_state, CYW43_WL_GPIO_LED_PIN, &led_gpio); + if (!led_gpio) { + cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN, true); + changed = true; + } + handled = true; + } else if (coap_string_equal(value, coap_make_str_const("0")) || coap_string_equal(value, coap_make_str_const("off"))) { + bool led_gpio; + cyw43_gpio_get(&cyw43_state, CYW43_WL_GPIO_LED_PIN, &led_gpio); + if (led_gpio) { + cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN, false); + changed = true; + } + handled = true; + } + coap_delete_str_const(value); + } + if (handled) { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED); + if (changed) { + notify_observers(resource); + } + } else { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST); + coap_add_option(response, COAP_OPTION_CONTENT_FORMAT, coap_encode_var_safe(buf, sizeof(buf), COAP_MEDIATYPE_TEXT_PLAIN), buf); + coap_add_data(response, 13, (const uint8_t *)"Invalid value"); + } +} + +static void time_worker_func(async_context_t *context, async_at_time_worker_t *worker) { + assert(worker->user_data); + notify_observers(time_resource); + if (time_resource->subscribers) { + async_context_add_at_time_worker_in_ms(context, worker, 1000); + } else { + worker->user_data = NULL; + } +} + +static async_at_time_worker_t time_worker = { + .do_work = time_worker_func +}; + +static void resource_get_time(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { + assert(time_resource == resource); + unsigned char buf[11]; + response->code = COAP_RESPONSE_CODE(404); + if ((query == NULL || coap_string_equal(query, coap_make_str_const("time")))) { + coap_add_option(response, COAP_OPTION_CONTENT_FORMAT, coap_encode_var_safe(buf, sizeof(buf), COAP_MEDIATYPE_TEXT_PLAIN), buf); + coap_add_option(response, COAP_OPTION_MAXAGE, coap_encode_var_safe(buf, sizeof(buf), 1), buf); + response->code = COAP_RESPONSE_CODE(205); + snprintf((char*)buf, sizeof(buf), "%u", to_ms_since_boot(get_absolute_time()) / 1000); + coap_add_data(response, strlen((char*)buf), buf); + } + if (time_resource->subscribers && !time_worker.user_data) { + time_worker.user_data = time_resource; + async_context_add_at_time_worker_in_ms(cyw43_arch_async_context(), &time_worker, 1000); + } +} + + +static bool init_coap_resources(coap_context_t *ctx) { + // chip temp + temp_resource = coap_resource_init(coap_make_str_const("temp"), 0); + assert(temp_resource); + if (!temp_resource) { + return false; + } + coap_resource_set_get_observable(temp_resource, true); + coap_register_handler(temp_resource, COAP_REQUEST_GET, resource_get_temp); + coap_add_attr(temp_resource, coap_make_str_const("ct"), coap_make_str_const("0"), 0); + coap_add_attr(temp_resource, coap_make_str_const("rt"), coap_make_str_const("\"chip\""), 0); + coap_add_attr(temp_resource, coap_make_str_const("if"), coap_make_str_const("\"temp\""), 0); + coap_add_resource(ctx, temp_resource); + // onboard led + led_resource = coap_resource_init(coap_make_str_const("led"), 0); + assert(led_resource); + if (!led_resource) { + return false; + } + coap_resource_set_get_observable(led_resource, true); + coap_register_handler(led_resource, COAP_REQUEST_GET, resource_get_led); + coap_register_handler(led_resource, COAP_REQUEST_PUT, resource_put_led); + coap_add_attr(led_resource, coap_make_str_const("ct"), coap_make_str_const("0"), 0); + coap_add_attr(led_resource, coap_make_str_const("rt"), coap_make_str_const("\"onboard\""), 0); + coap_add_attr(led_resource, coap_make_str_const("if"), coap_make_str_const("\"led\""), 0); + coap_add_resource(ctx, led_resource); + // time + time_resource = coap_resource_init(coap_make_str_const("time"), 0); + assert(time_resource); + if (!time_resource) { + return false; + } + coap_resource_set_get_observable(time_resource, true); + coap_register_handler(time_resource, COAP_REQUEST_GET, resource_get_time); + coap_add_attr(time_resource, coap_make_str_const("ct"), coap_make_str_const("0"), 0); + coap_add_attr(time_resource, coap_make_str_const("rt"), coap_make_str_const("\"time\""), 0); + coap_add_attr(time_resource, coap_make_str_const("if"), coap_make_str_const("\"seconds\""), 0); + coap_add_resource(ctx, time_resource); + return true; +} + +static coap_context_t* start_server(void) +{ + coap_startup(); + +#if 1 + coap_set_log_level(COAP_LOG_DEBUG); +#else + coap_set_log_level(COAP_LOG_INFO); +#endif + + coap_context_t *ctx = coap_new_context(NULL); + assert(ctx); + if (!ctx) { + coap_log_warn("failed to create main context\n"); + return NULL; + } + + coap_address_t addr; + addr.addr = netif_list->ip_addr; + coap_address_set_port(&addr, COAP_PORT); + +#ifdef COAP_CERT_INC + // Set up security stuff + static const uint8_t coap_key[] = COAP_KEY; + static const uint8_t coap_pub_key[] = COAP_PUB_KEY; + static coap_dtls_pki_t dtls_pki; + memset(&dtls_pki, 0, sizeof(dtls_pki)); + dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION; + dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF; + dtls_pki.is_rpk_not_cert = 1; // tinydtls only supports raw public keys + dtls_pki.verify_peer_cert = 1; // not implemented? + dtls_pki.pki_key.key.pem_buf.public_cert = coap_pub_key; // rpk + dtls_pki.pki_key.key.pem_buf.public_cert_len = sizeof(coap_pub_key); + dtls_pki.pki_key.key.pem_buf.private_key = coap_key; + dtls_pki.pki_key.key.pem_buf.private_key_len = sizeof(coap_key); + if (!coap_context_set_pki(ctx, &dtls_pki)) { + coap_log_err("Unable to set up keys\n"); + coap_free_context(ctx); + return NULL; + } +#endif + coap_endpoint_t *ep = coap_new_endpoint(ctx, &addr, COAP_PROTO); + assert(ep); + if (!ep) { + coap_log_warn("cannot create endpoint\n"); + coap_free_context(ctx); + return NULL; + } + assert(MEMP_NUM_COAPSESSION > 1); + coap_context_set_max_idle_sessions(ctx, MEMP_NUM_COAPSESSION - 1); + coap_context_set_session_timeout(ctx, SESSION_TIMEOUT); + init_coap_resources(ctx); + return ctx; +} + +static void coap_server_deinit(void) +{ + coap_free_context(main_coap_context); + main_coap_context = NULL; + coap_cleanup(); +} + +int main() { + stdio_init_all(); + + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return 1; + } + cyw43_arch_enable_sta_mode(); + printf("Connecting to Wi-Fi... (press 'd' to disconnect)\n"); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { + printf("failed to connect.\n"); + return 1; + } else { + printf("Connected.\n"); + } + cyw43_wifi_pm(&cyw43_state, CYW43_NONE_PM); + + main_coap_context = start_server(); + if (!main_coap_context) { + panic("Test failed"); + } +#if LWIP_IPV4 + coap_log_info("Ready, running coap server at %s \n", ipaddr_ntoa(&(netif_list->ip_addr))); +#else + coap_log_info("Ready, running coap server at %s\n", ipaddr_ntoa(&(netif_list->ip6_addr[1]))); +#endif + + // Initialise adc for the temp sensor + adc_init(); + adc_select_input(ADC_CHANNEL_TEMPSENSOR); + adc_set_temp_sensor_enabled(true); + + check_notify_worker.user_data = main_coap_context; + async_context_add_when_pending_worker(cyw43_arch_async_context(), &check_notify_worker); + coap_worker.user_data = main_coap_context; + async_context_add_at_time_worker_in_ms(cyw43_arch_async_context(), &coap_worker, 1000); + while (true) { + cyw43_arch_poll(); + cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000)); + } + + coap_server_deinit(); + lwip_nosys_deinit(cyw43_arch_async_context()); + cyw43_arch_deinit(); + panic("Test passed"); + return 0; +} + +void coap_address_init(coap_address_t *addr) { + assert(addr); + memset(addr, 0, sizeof(coap_address_t)); +} diff --git a/pico_w/wifi/coap/libcoap.cmake b/pico_w/wifi/coap/libcoap.cmake new file mode 100644 index 000000000..3d02b9bfb --- /dev/null +++ b/pico_w/wifi/coap/libcoap.cmake @@ -0,0 +1,56 @@ +if (NOT LIB_COAP_DIR) + message(FATAL_ERROR "Need to define LIB_COAP_DIR") +else() + set(LIB_COAP_DIR "${LIB_COAP_DIR}" CACHE INTERNAL "libcoap directory location") + + set(PACKAGE_URL "https://libcoap.net/") + set(PACKAGE_NAME "libcoap") + set(PACKAGE_STRING "libcoap 4.3.4") + set(PACKAGE_VERSION "4.3.4") + set(PACKAGE_BUGREPORT "libcoap-developers@lists.sourceforge.net") + set(LIBCOAP_API_VERSION 3) + set(LIBCOAP_VERSION 4003004) + + set(COAP_SRC + coap_asn1.c + coap_async.c + coap_block.c + coap_cache.c + coap_debug.c + coap_dtls.c + coap_encode.c + coap_hashkey.c + coap_io.c + coap_io_lwip.c + coap_layers.c + coap_net.c + coap_netif.c + coap_notls.c + coap_option.c + coap_oscore.c + coap_pdu.c + coap_resource.c + coap_session.c + coap_subscribe.c + coap_str.c + coap_tcp.c + coap_uri.c + coap_ws.c + coap_tinydtls.c + coap_prng.c + ) + list(TRANSFORM COAP_SRC PREPEND ${LIB_COAP_DIR}/src/) + + pico_add_library(pico_coap) + target_include_directories(pico_coap_headers INTERFACE + ${LIB_COAP_DIR}/include + ${CMAKE_CURRENT_BINARY_DIR}/include + ) + target_sources(pico_coap INTERFACE + ${COAP_SRC} + ) + target_compile_definitions(pico_coap INTERFACE + COAP_SERVER_SUPPORT + COAP_CLIENT_SUPPORT + ) +endif() diff --git a/pico_w/wifi/coap/libtinydtls.cmake b/pico_w/wifi/coap/libtinydtls.cmake new file mode 100644 index 000000000..8d1c3c344 --- /dev/null +++ b/pico_w/wifi/coap/libtinydtls.cmake @@ -0,0 +1,36 @@ +if (NOT LIB_TINYDTLS_DIR) + message(FATAL_ERROR "Need to define LIB_TINYDTLS_DIR") +else() + set(TINYDTLS_SRC + dtls.c + netq.c + peer.c + session.c + crypto.c + ccm.c + hmac.c + dtls_time.c + dtls_debug.c + dtls_prng.c + aes/rijndael.c + aes/rijndael_wrap.c + sha2/sha2.c + ecc/ecc.c + ) + list(TRANSFORM TINYDTLS_SRC PREPEND ${LIB_TINYDTLS_DIR}/) + + pico_add_library(pico_tinydtls) + target_include_directories(pico_tinydtls_headers INTERFACE + ${LIB_TINYDTLS_DIR}/ + ${LIB_TINYDTLS_DIR}/.. + ) + target_sources(pico_tinydtls INTERFACE + ${TINYDTLS_SRC} + ) + target_compile_definitions(pico_tinydtls INTERFACE + COAP_WITH_LIBTINYDTLS # This is a coap thing really? + WITH_LWIP + WITH_SHA256 + _DTLS_MUTEX_H_ # :( + ) +endif() diff --git a/pico_w/wifi/coap/lwipopts.h b/pico_w/wifi/coap/lwipopts.h new file mode 100644 index 000000000..7178c3dc8 --- /dev/null +++ b/pico_w/wifi/coap/lwipopts.h @@ -0,0 +1,29 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// needed for libcoap +#define MEMP_USE_CUSTOM_POOLS 1 + +#define MEM_SIZE 8000 + +// Generally you would define your own explicit list of lwIP options +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" + +#if NO_SYS +#define LOCK_TCPIP_CORE() +#define UNLOCK_TCPIP_CORE() + +// This is for tinydtls +typedef int dtls_mutex_t; +#define DTLS_MUTEX_INITIALIZER 0 +#define dtls_mutex_lock(a) *(a) = 1 +#define dtls_mutex_trylock(a) *(a) = 1 +#define dtls_mutex_unlock(a) *(a) = 0 +#else +#error todo: define dtls_mutex_t +#endif + +#endif diff --git a/pico_w/wifi/coap/lwippools.h b/pico_w/wifi/coap/lwippools.h new file mode 100644 index 000000000..d95a81efd --- /dev/null +++ b/pico_w/wifi/coap/lwippools.h @@ -0,0 +1,174 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +/* Memory pool definitions for the libcoap when used with lwIP (which has its + * own mechanism for quickly allocating chunks of data with known sizes). Has + * to be findable by lwIP (ie. an #include must either directly + * include this or include something more generic which includes this), and + * MEMP_USE_CUSTOM_POOLS has to be set in lwipopts.h. */ + +#include "coap3/coap_internal.h" +#include "coap3/coap_net.h" +#include "coap3/coap_resource.h" +#include "coap3/coap_subscribe.h" +#ifdef COAP_WITH_LIBTINYDTLS +#ifndef LWIP_TINYDTLS_LOCAL_FIX +#define LWIP_TINYDTLS_LOCAL_FIX +#include +/* Local copies of struct to simplify #include nightmare */ +typedef struct { + unsigned char size; /**< size of session_t::addr */ + unsigned short port; /**< transport layer port */ + ip_addr_t addr; /**< session IP address */ + int ifindex; /**< network interface index */ +} l_session_t; + +typedef struct l_coap_tiny_context_t { + struct dtls_context_t *dtls_context; + coap_context_t *coap_context; + coap_dtls_pki_t setup_data; + coap_binary_t *priv_key; + coap_binary_t *pub_key; +} l_coap_tiny_context_t; + +#endif /* LWIP_TINYDTLS_LOCAL_FIX */ +#endif /* COAP_WITH_LIBTINYDTLS */ + +#ifndef MEMP_NUM_COAPCONTEXT +#define MEMP_NUM_COAPCONTEXT 1 +#endif + +#ifndef MEMP_NUM_COAPENDPOINT +#ifdef COAP_WITH_LIBTINYDTLS +#define MEMP_NUM_COAPENDPOINT 2 +#else /* ! COAP_WITH_LIBTINYDTLS */ +#define MEMP_NUM_COAPENDPOINT 1 +#endif /* ! COAP_WITH_LIBTINYDTLS */ +#endif + +#ifndef MEMP_NUM_COAPPACKET +#define MEMP_NUM_COAPPACKET 2 +#endif + +#ifndef MEMP_NUM_COAPNODE +#define MEMP_NUM_COAPNODE 5 +#endif + +#ifndef MEMP_NUM_COAPPDU +#define MEMP_NUM_COAPPDU MEMP_NUM_COAPNODE +#endif + +#ifndef MEMP_NUM_COAPSESSION +#define MEMP_NUM_COAPSESSION 4 +#endif + +#ifndef MEMP_NUM_COAP_SUBSCRIPTION +#define MEMP_NUM_COAP_SUBSCRIPTION 4 +#endif + +#ifndef MEMP_NUM_COAPRESOURCE +#define MEMP_NUM_COAPRESOURCE 10 +#endif + +#ifndef MEMP_NUM_COAPRESOURCEATTR +#define MEMP_NUM_COAPRESOURCEATTR 20 +#endif + +#ifndef MEMP_NUM_COAPOPTLIST +#define MEMP_NUM_COAPOPTLIST 5 +#endif + +#ifndef MEMP_LEN_COAPOPTLIST +#define MEMP_LEN_COAPOPTLIST 12 +#endif + +#ifndef MEMP_NUM_COAPSTRING +#define MEMP_NUM_COAPSTRING 25 +#endif + +#ifndef MEMP_LEN_COAPSTRING +#define MEMP_LEN_COAPSTRING 192 +#endif + +#ifndef MEMP_NUM_COAPCACHE_KEYS +#define MEMP_NUM_COAPCACHE_KEYS (2U) +#endif /* MEMP_NUM_COAPCACHE_KEYS */ + +#ifndef MEMP_NUM_COAPCACHE_ENTRIES +#define MEMP_NUM_COAPCACHE_ENTRIES (2U) +#endif /* MEMP_NUM_COAPCACHE_ENTRIES */ + +#ifndef MEMP_NUM_COAPPDUBUF +#define MEMP_NUM_COAPPDUBUF 2 +#endif + +#ifndef MEMP_LEN_COAPPDUBUF +#define MEMP_LEN_COAPPDUBUF 400 +#endif + +#ifndef MEMP_NUM_COAPLGXMIT +#define MEMP_NUM_COAPLGXMIT 2 +#endif + +#ifndef MEMP_NUM_COAPLGCRCV +#define MEMP_NUM_COAPLGCRCV 2 +#endif + +#ifndef MEMP_NUM_COAPLGSRCV +#define MEMP_NUM_COAPLGSRCV 2 +#endif + +#ifndef MEMP_NUM_COAPDIGESTCTX +#define MEMP_NUM_COAPDIGESTCTX 4 +#endif + +#ifndef MEMP_NUM_COAPDTLS_SESSION +#define MEMP_NUM_COAPDTLS_SESSION 1 +#endif + +#ifndef MEMP_NUM_COAPDTLS_CONTEXT +#define MEMP_NUM_COAPDTLS_CONTEXT 1 +#endif + +LWIP_MEMPOOL(COAP_CONTEXT, MEMP_NUM_COAPCONTEXT, sizeof(coap_context_t), "COAP_CONTEXT") +#ifdef COAP_SERVER_SUPPORT +LWIP_MEMPOOL(COAP_ENDPOINT, MEMP_NUM_COAPENDPOINT, sizeof(coap_endpoint_t), "COAP_ENDPOINT") +#endif /* COAP_SERVER_SUPPORT */ +LWIP_MEMPOOL(COAP_PACKET, MEMP_NUM_COAPPACKET, sizeof(coap_packet_t), "COAP_PACKET") +LWIP_MEMPOOL(COAP_NODE, MEMP_NUM_COAPNODE, sizeof(coap_queue_t), "COAP_NODE") +LWIP_MEMPOOL(COAP_PDU, MEMP_NUM_COAPPDU, sizeof(coap_pdu_t), "COAP_PDU") +LWIP_MEMPOOL(COAP_SESSION, MEMP_NUM_COAPSESSION, sizeof(coap_session_t), "COAP_SESSION") +#ifdef COAP_SERVER_SUPPORT +LWIP_MEMPOOL(COAP_SUBSCRIPTION, MEMP_NUM_COAP_SUBSCRIPTION, sizeof(coap_subscription_t), + "COAP_SUBSCRIPTION") +LWIP_MEMPOOL(COAP_RESOURCE, MEMP_NUM_COAPRESOURCE, sizeof(coap_resource_t), "COAP_RESOURCE") +LWIP_MEMPOOL(COAP_RESOURCEATTR, MEMP_NUM_COAPRESOURCEATTR, sizeof(coap_attr_t), "COAP_RESOURCEATTR") +#endif /* COAP_SERVER_SUPPORT */ +LWIP_MEMPOOL(COAP_OPTLIST, MEMP_NUM_COAPOPTLIST, sizeof(coap_optlist_t)+MEMP_LEN_COAPOPTLIST, + "COAP_OPTLIST") +LWIP_MEMPOOL(COAP_STRING, MEMP_NUM_COAPSTRING, sizeof(coap_string_t)+MEMP_LEN_COAPSTRING, + "COAP_STRING") +#ifdef COAP_SERVER_SUPPORT +LWIP_MEMPOOL(COAP_CACHE_KEY, MEMP_NUM_COAPCACHE_KEYS, sizeof(coap_cache_key_t), "COAP_CACHE_KEY") +LWIP_MEMPOOL(COAP_CACHE_ENTRY, MEMP_NUM_COAPCACHE_ENTRIES, sizeof(coap_cache_entry_t), + "COAP_CACHE_ENTRY") +#endif /* COAP_SERVER_SUPPORT */ +LWIP_MEMPOOL(COAP_PDU_BUF, MEMP_NUM_COAPPDUBUF, MEMP_LEN_COAPPDUBUF, "COAP_PDU_BUF") +LWIP_MEMPOOL(COAP_LG_XMIT, MEMP_NUM_COAPLGXMIT, sizeof(coap_lg_xmit_t), "COAP_LG_XMIT") +#ifdef COAP_CLIENT_SUPPORT +LWIP_MEMPOOL(COAP_LG_CRCV, MEMP_NUM_COAPLGCRCV, sizeof(coap_lg_crcv_t), "COAP_LG_CRCV") +#endif /* COAP_CLIENT_SUPPORT */ +#ifdef COAP_SERVER_SUPPORT +LWIP_MEMPOOL(COAP_LG_SRCV, MEMP_NUM_COAPLGSRCV, sizeof(coap_lg_srcv_t), "COAP_LG_SRCV") +LWIP_MEMPOOL(COAP_DIGEST_CTX, MEMP_NUM_COAPDIGESTCTX, sizeof(coap_digest_t) + sizeof(size_t), + "COAP_DIGEST_CTX") +#endif /* COAP_SERVER_SUPPORT */ +#ifdef COAP_WITH_LIBTINYDTLS +LWIP_MEMPOOL(COAP_DTLS_SESSION, MEMP_NUM_COAPDTLS_SESSION, sizeof(l_session_t), "COAP_DTLS_SESSION") +LWIP_MEMPOOL(COAP_DTLS_CONTEXT, MEMP_NUM_COAPDTLS_CONTEXT, sizeof(l_coap_tiny_context_t), + "COAP_DTLS_CONTEXT") +#endif /* COAP_WITH_LIBTINYDTLS */ diff --git a/pico_w/wifi/coap/mbedtls_alt_timing.c b/pico_w/wifi/coap/mbedtls_alt_timing.c new file mode 100644 index 000000000..282a51f39 --- /dev/null +++ b/pico_w/wifi/coap/mbedtls_alt_timing.c @@ -0,0 +1,33 @@ +#include "mbedtls/timing.h" + +async_context_t *async_context; +async_at_time_worker_t worker; + +void mbedtls_timing_set_delay(void *data, uint32_t int_ms, uint32_t fin_ms) +{ + mbedtls_timing_delay_context *ctx = (mbedtls_timing_delay_context*)data; + if (fin_ms == 0) { + //async_context_remove_at_time_worker(ctx->async_context, &ctx->worker); + ctx->fin_time = nil_time; + return; + } + // todo: do we need this? + //async_context_add_at_time_worker_in_ms(ctx->async_context, &ctx->worker, fin_ms); + ctx->fin_time = make_timeout_time_ms(fin_ms); + ctx->int_time = make_timeout_time_ms(int_ms); +} + +int mbedtls_timing_get_delay(void *data) +{ + mbedtls_timing_delay_context *ctx = (mbedtls_timing_delay_context*)data; + if (is_nil_time(ctx->fin_time)) { + return -1; + } + if (time_reached(ctx->fin_time)) { + return 2; + } + if (time_reached(ctx->int_time)) { + return 1; + } + return 0; +} diff --git a/pico_w/wifi/coap/mbedtls_config.h b/pico_w/wifi/coap/mbedtls_config.h new file mode 100644 index 000000000..e1555c171 --- /dev/null +++ b/pico_w/wifi/coap/mbedtls_config.h @@ -0,0 +1,25 @@ +#ifndef MBEDTLS_CONFIG_TLS_CLIENT_H +#define MBEDTLS_CONFIG_TLS_CLIENT_H + +#include "mbedtls_config_examples_common.h" + +// Needed for dtls +#define MBEDTLS_SSL_PROTO_DTLS +#define MBEDTLS_SSL_COOKIE_C +#define MBEDTLS_SSL_DTLS_HELLO_VERIFY +#define MBEDTLS_DEBUG_C +#define MBEDTLS_TIMING_ALT +#define MBEDTLS_VERSION_C +#define MBEDTLS_SSL_COOKIE_C + +struct mbedtls_x509_crt; +static inline int mbedtls_x509_crt_parse_file(struct mbedtls_x509_crt *chain, const char *path) { + return -4; +} + +struct mbedtls_pk_context; +static inline int mbedtls_pk_parse_keyfile(struct mbedtls_pk_context *ctx, const char *path, const char *password) { + return -4; +} + +#endif \ No newline at end of file diff --git a/pico_w/wifi/coap/timing_alt.h b/pico_w/wifi/coap/timing_alt.h new file mode 100644 index 000000000..11fa4a152 --- /dev/null +++ b/pico_w/wifi/coap/timing_alt.h @@ -0,0 +1,16 @@ +#ifndef _TIMING_ALT_ +#define _TIMING_ALT_ + +#include "pico/async_context.h" +#include "pico/time.h" + +struct mbedtls_timing_hr_time { + absolute_time_t t; +}; + +typedef struct mbedtls_timing_delay_context { + absolute_time_t fin_time; + absolute_time_t int_time; +} mbedtls_timing_delay_context; + +#endif