From 308564645ea4876e43126afe8e835051fbbe7654 Mon Sep 17 00:00:00 2001 From: Harshit Malpani Date: Fri, 9 Jun 2023 17:05:39 +0530 Subject: [PATCH] Add CoAP examples from ESP-IDF --- .github/workflows/build_and_run_examples.yml | 28 +- coap/examples/coap_client/CMakeLists.txt | 10 + coap/examples/coap_client/README.md | 111 ++++ coap/examples/coap_client/main/CMakeLists.txt | 4 + .../coap_client/main/Kconfig.projbuild | 33 ++ .../coap_client/main/certs/coap_ca.pem | 27 + .../coap_client/main/certs/coap_client.crt | 13 + .../coap_client/main/certs/coap_client.key | 4 + .../main/coap_client_example_main.c | 509 ++++++++++++++++++ .../coap_client/main/idf_component.yml | 6 + .../coap_client/pytest_coap_client_example.py | 21 + coap/examples/coap_client/sdkconfig.ci | 8 + coap/examples/coap_client/sdkconfig.defaults | 5 + .../coap_client/sdkconfig.defaults.esp32h2 | 2 + coap/examples/coap_server/CMakeLists.txt | 10 + coap/examples/coap_server/README.md | 96 ++++ coap/examples/coap_server/main/CMakeLists.txt | 3 + .../coap_server/main/Kconfig.projbuild | 67 +++ .../coap_server/main/certs/coap_ca.pem | 27 + .../coap_server/main/certs/coap_server.crt | 13 + .../coap_server/main/certs/coap_server.key | 4 + .../main/coap_server_example_main.c | 371 +++++++++++++ .../coap_server/main/idf_component.yml | 6 + coap/examples/coap_server/sdkconfig.defaults | 5 + .../coap_server/sdkconfig.defaults.esp32h2 | 2 + pytest.ini | 1 + 26 files changed, 1384 insertions(+), 2 deletions(-) create mode 100644 coap/examples/coap_client/CMakeLists.txt create mode 100644 coap/examples/coap_client/README.md create mode 100644 coap/examples/coap_client/main/CMakeLists.txt create mode 100644 coap/examples/coap_client/main/Kconfig.projbuild create mode 100644 coap/examples/coap_client/main/certs/coap_ca.pem create mode 100644 coap/examples/coap_client/main/certs/coap_client.crt create mode 100644 coap/examples/coap_client/main/certs/coap_client.key create mode 100644 coap/examples/coap_client/main/coap_client_example_main.c create mode 100644 coap/examples/coap_client/main/idf_component.yml create mode 100644 coap/examples/coap_client/pytest_coap_client_example.py create mode 100644 coap/examples/coap_client/sdkconfig.ci create mode 100644 coap/examples/coap_client/sdkconfig.defaults create mode 100644 coap/examples/coap_client/sdkconfig.defaults.esp32h2 create mode 100644 coap/examples/coap_server/CMakeLists.txt create mode 100644 coap/examples/coap_server/README.md create mode 100644 coap/examples/coap_server/main/CMakeLists.txt create mode 100644 coap/examples/coap_server/main/Kconfig.projbuild create mode 100644 coap/examples/coap_server/main/certs/coap_ca.pem create mode 100644 coap/examples/coap_server/main/certs/coap_server.crt create mode 100644 coap/examples/coap_server/main/certs/coap_server.key create mode 100644 coap/examples/coap_server/main/coap_server_example_main.c create mode 100644 coap/examples/coap_server/main/idf_component.yml create mode 100644 coap/examples/coap_server/sdkconfig.defaults create mode 100644 coap/examples/coap_server/sdkconfig.defaults.esp32h2 diff --git a/.github/workflows/build_and_run_examples.yml b/.github/workflows/build_and_run_examples.yml index 530623930f..2223a7f18f 100644 --- a/.github/workflows/build_and_run_examples.yml +++ b/.github/workflows/build_and_run_examples.yml @@ -28,7 +28,7 @@ jobs: export PEDANTIC_FLAGS="-DIDF_CI_BUILD -Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function" export EXTRA_CFLAGS="${PEDANTIC_FLAGS} -Wstrict-prototypes" export EXTRA_CXXFLAGS="${PEDANTIC_FLAGS}" - idf-build-apps find + idf-build-apps find --config sdkconfig.ci idf-build-apps build --parallel-index ${{ matrix.parallel_index }} - uses: actions/upload-artifact@v3 with: @@ -62,4 +62,28 @@ jobs: PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/" run: pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf - name: Run examples - run: pytest --target=${{ matrix.idf_target }} --build-dir=build_${{ matrix.idf_target }} --ignore=usb --ignore=test_app + run: pytest --target=${{ matrix.idf_target }} -m generic --build-dir=build_${{ matrix.idf_target }} --ignore=usb --ignore=test_app + + run-ethernet-based-examples: + name: Run CoAP examples on target + needs: build + strategy: + fail-fast: false + matrix: + idf_ver: ["release-v4.4", "release-v5.0", "release-v5.1", "latest"] + idf_target: ["esp32"] + runs-on: [self-hosted, ESP32-ETHERNET-KIT] + container: + image: python:3.7-buster + options: --privileged # Privileged mode has access to serial ports + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: example_binaries_${{ matrix.idf_ver }} + - name: Install Python packages + env: + PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/" + run: pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf + - name: Run examples + run: pytest --target=${{ matrix.idf_target }} -m ethernet --build-dir=build_${{ matrix.idf_target }} --ignore=usb --ignore=test_app diff --git a/coap/examples/coap_client/CMakeLists.txt b/coap/examples/coap_client/CMakeLists.txt new file mode 100644 index 0000000000..0d90604b74 --- /dev/null +++ b/coap/examples/coap_client/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(coap_client) diff --git a/coap/examples/coap_client/README.md b/coap/examples/coap_client/README.md new file mode 100644 index 0000000000..b1cf78bcea --- /dev/null +++ b/coap/examples/coap_client/README.md @@ -0,0 +1,111 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | + + +# CoAP client example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) +This CoAP client example is very simplified adaptation of one of the +[libcoap](https://github.com/obgm/libcoap) examples. + +CoAP client example will connect your ESP32 device to a CoAP server, send off a GET request and +fetch the response data from CoAP server. The client can be extended to PUT / POST / DELETE requests, +as well as supporting the Observer extensions [RFC7641](https://tools.ietf.org/html/rfc7641). + +If the URI is prefixed with coaps:// instead of coap://, then the CoAP client will attempt to use +the DTLS protocol using the defined Pre-Shared Keys(PSK) or Public Key Infrastructure (PKI) which the +CoAP server needs to know about. + +If the URI is prefixed with coap+tcp://, then the CoAP will try to use TCP for the communication. + +The Constrained Application Protocol (CoAP) is a specialized web transfer protocol for use with +constrained nodes and constrained networks in the Internet of Things. +The protocol is designed for machine-to-machine (M2M) applications such as smart energy and +building automation. + +Please refer to [RFC7252](https://www.rfc-editor.org/rfc/pdfrfc/rfc7252.txt.pdf) for more details. + +## How to use example + +### Configure the project + +``` +idf.py menuconfig +``` + +Example Connection Configuration ---> + * Set WiFi SSID + * Set WiFi Password +Component config ---> + CoAP Configuration ---> + * Set encryption method definition, PSK (default) or PKI + * Enable CoAP debugging if required + * Disable CoAP using TCP if this is not required (TCP needed for TLS) + * Disable CoAP server functionality to reduce code size +Example CoAP Client Configuration ---> + * Set CoAP Target Uri + * If PSK, Set CoAP Preshared Key to use in connection to the server + * If PSK, Set CoAP PSK Client identity (username) + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py build +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +Prerequisite: we startup a CoAP server on coap server example, +or use the default of coap://californium.eclipseprojects.io. + +and you could receive data from CoAP server if succeed, +such as the following log: + +``` +... +I (332) wifi: mode : sta (30:ae:a4:04:1b:7c) +I (1672) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1 +I (1672) wifi: state: init -> auth (b0) +I (1682) wifi: state: auth -> assoc (0) +I (1692) wifi: state: assoc -> run (10) +I (1692) wifi: connected with huawei_cw, channel 11 +I (1692) wifi: pm start, type: 1 + +I (2582) event: sta ip: 192.168.3.89, mask: 255.255.255.0, gw: 192.168.3.1 +I (2582) CoAP_client: Connected to AP +I (2582) CoAP_client: DNS lookup succeeded. IP=35.185.40.182 +Received: +**************************************************************** +CoAP RFC 7252 Cf 3.0.0-SNAPSHOT +**************************************************************** +This server is using the Eclipse Californium (Cf) CoAP framework +published under EPL+EDL: http://www.eclipse.org/californium/ + +(c) 2014-2020 Institute for Pervasive Computing, ETH Zurich and others +**************************************************************** +... +``` + +## libcoap Documentation +This can be found at [libcoap Documentation](https://libcoap.net/documentation.html). +The current API is 4.3.0. + +## libcoap Specific Issues +These can be raised at [libcoap Issues](https://github.com/obgm/libcoap/issues). + +## Troubleshooting +* Please make sure Target Url includes valid `host`, optional `port`, +optional `path`, and begins with `coap://`, `coaps://`, `coap+tcp://` or `coaps+tcp://` +(not all hosts support TCP/TLS including coap+tcp://californium.eclipseprojects.io). + +* CoAP logging can be enabled by running 'idf.py menuconfig -> Component config -> CoAP Configuration -> Enable CoAP debugging' +and setting appropriate log level. If Mbed TLS logging is required, this needs to be configured separately under mbedTLS +Component Configuration and the CoAP logging level set to mbedTLS. + +* CoAP library does not support IPv6 only configuration, so it is necessary to enable `LWIP_IPv4` diff --git a/coap/examples/coap_client/main/CMakeLists.txt b/coap/examples/coap_client/main/CMakeLists.txt new file mode 100644 index 0000000000..d64daaee2a --- /dev/null +++ b/coap/examples/coap_client/main/CMakeLists.txt @@ -0,0 +1,4 @@ +# Embed CA, certificate & key directly into binary +idf_component_register(SRCS "coap_client_example_main.c" + INCLUDE_DIRS "." + EMBED_TXTFILES certs/coap_ca.pem certs/coap_client.crt certs/coap_client.key) diff --git a/coap/examples/coap_client/main/Kconfig.projbuild b/coap/examples/coap_client/main/Kconfig.projbuild new file mode 100644 index 0000000000..add9065972 --- /dev/null +++ b/coap/examples/coap_client/main/Kconfig.projbuild @@ -0,0 +1,33 @@ +menu "Example CoAP Client Configuration" + + # Hidden option that selects IPv4 + config EXAMPLE_COAP_NEEDS_IPV4 + bool + default true + select LWIP_IPV4 + + config EXAMPLE_TARGET_DOMAIN_URI + string "Target Uri" + default "coaps://californium.eclipseprojects.io" + help + Target uri for the example to use. Use coaps:// prefix for encrypted traffic + using Pre-Shared Key (PSK) or Public Key Infrastructure (PKI). + + config EXAMPLE_COAP_PSK_KEY + string "Preshared Key (PSK) to used in the connection to the CoAP server" + depends on COAP_MBEDTLS_PSK + default "sesame" + help + The Preshared Key to use to encrypt the communicatons. The same key must be + used at both ends of the CoAP connection, and the CoaP client must request + an URI prefixed with coaps:// instead of coap:// for DTLS to be used. + + config EXAMPLE_COAP_PSK_IDENTITY + string "PSK Client identity (username)" + depends on COAP_MBEDTLS_PSK + default "password" + help + The identity (or username) to use to identify to the CoAP server which + PSK key to use. + +endmenu diff --git a/coap/examples/coap_client/main/certs/coap_ca.pem b/coap/examples/coap_client/main/certs/coap_ca.pem new file mode 100644 index 0000000000..07900ec471 --- /dev/null +++ b/coap/examples/coap_client/main/certs/coap_ca.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIICDzCCAbSgAwIBAgIIAbOUoVFDz/QwDAYIKoZIzj0EAwIFADBcMRAwDgYDVQQD +EwdjZi1yb290MRQwEgYDVQQLEwtDYWxpZm9ybml1bTEUMBIGA1UEChMLRWNsaXBz +ZSBJb1QxDzANBgNVBAcTBk90dGF3YTELMAkGA1UEBhMCQ0EwHhcNMjAxMTExMTAz +MDMzWhcNMjExMTExMTAzMDMzWjBaMQ4wDAYDVQQDEwVjZi1jYTEUMBIGA1UECxML +Q2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2UgSW9UMQ8wDQYDVQQHEwZPdHRh +d2ExCzAJBgNVBAYTAkNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7/3EXOZn +GZXNEIj7LuQAMZ8lfRYSCnpME1TBjKjZPtVeztLtGWgkkLvIX11pAJcBh51cpi7Z +fQtGpVE9CLOh6aNgMF4wHQYDVR0OBBYEFEvf57UcJhYYkx14twkeitd691fVMAsG +A1UdDwQEAwIBBjAPBgNVHRMECDAGAQH/AgEBMB8GA1UdIwQYMBaAFAsi3KbVERiK +JzFCfC/GVrYksGzEMAwGCCqGSM49BAMCBQADRwAwRAIgc5nVF/5Pip0XB17IZXqi +V84FXanWdn9Z0SiPdpOgvZMCIH13vL9tkCCjPN3tg3TYRY/bzyGohFGBcTrrEtUr +rVIm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB4DCCAYWgAwIBAgIIQR8ro8AQ02AwDAYIKoZIzj0EAwIFADBcMRAwDgYDVQQD +EwdjZi1yb290MRQwEgYDVQQLEwtDYWxpZm9ybml1bTEUMBIGA1UEChMLRWNsaXBz +ZSBJb1QxDzANBgNVBAcTBk90dGF3YTELMAkGA1UEBhMCQ0EwHhcNMjAxMTExMTAz +MDMyWhcNMjExMTExMTAzMDMyWjBcMRAwDgYDVQQDEwdjZi1yb290MRQwEgYDVQQL +EwtDYWxpZm9ybml1bTEUMBIGA1UEChMLRWNsaXBzZSBJb1QxDzANBgNVBAcTBk90 +dGF3YTELMAkGA1UEBhMCQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATZ1BRM +T1//Fzh9sneRZNwS4kgCxN1PvgwT271qCpYqyxnjLEa38AP1IAanhpiD/OkVc0Zd +7NgDPCw7n94EULMyoy8wLTAdBgNVHQ4EFgQUCyLcptURGIonMUJ8L8ZWtiSwbMQw +DAYDVR0TBAUwAwEB/zAMBggqhkjOPQQDAgUAA0cAMEQCIAdLEgcUWdpAl9jwdJiz +/cHW7/CBIWEvqiQfzE+XLyLOAiAvuxSdOtSDjh2aC5qEjUCH8CSKCxWB74j23tmp +aqPH4A== +-----END CERTIFICATE----- diff --git a/coap/examples/coap_client/main/certs/coap_client.crt b/coap/examples/coap_client/main/certs/coap_client.crt new file mode 100644 index 0000000000..e774231cb3 --- /dev/null +++ b/coap/examples/coap_client/main/certs/coap_client.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICAzCCAaagAwIBAgIJAJnE6sMNQNAoMAwGCCqGSM49BAMCBQAwWjEOMAwGA1UE +AxMFY2YtY2ExFDASBgNVBAsTC0NhbGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNl +IElvVDEPMA0GA1UEBxMGT3R0YXdhMQswCQYDVQQGEwJDQTAeFw0yMDExMTExMDMw +NDVaFw0yMTExMTExMDMwNDVaMF4xEjAQBgNVBAMTCWNmLWNsaWVudDEUMBIGA1UE +CxMLQ2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2UgSW9UMQ8wDQYDVQQHEwZP +dHRhd2ExCzAJBgNVBAYTAkNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFGno +107kFgJ4AvABQviE9hTJlEeB4wfS3L58Q5J8srWxSunEgniIbfr0p8Shw+C1XAcz +FxJrn8SjFCVqOKjrrqNPME0wHQYDVR0OBBYEFIwAAdmpYSm184Jx1ycc3BQGybhN +MAsGA1UdDwQEAwIHgDAfBgNVHSMEGDAWgBRL3+e1HCYWGJMdeLcJHorXevdX1TAM +BggqhkjOPQQDAgUAA0kAMEYCIQC+w/hm8TfTCqUV6midHAvmNxJN7MfMcpAiyi4e +6NBrPAIhAPeMUrnOlykTMcpYMRZs4YnyM6ihgU/F8UjknhDpkywm +-----END CERTIFICATE----- diff --git a/coap/examples/coap_client/main/certs/coap_client.key b/coap/examples/coap_client/main/certs/coap_client.key new file mode 100644 index 0000000000..dca2e92167 --- /dev/null +++ b/coap/examples/coap_client/main/certs/coap_client.key @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCBgNuyqKuRW0RxU1DVs +aEpBPtVRVLRZYq6hZRzvZ6igBw== +-----END PRIVATE KEY----- diff --git a/coap/examples/coap_client/main/coap_client_example_main.c b/coap/examples/coap_client/main/coap_client_example_main.c new file mode 100644 index 0000000000..40fd4baf5d --- /dev/null +++ b/coap/examples/coap_client/main/coap_client_example_main.c @@ -0,0 +1,509 @@ +/* CoAP client Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* + * WARNING + * libcoap is not multi-thread safe, so only this thread must make any coap_*() + * calls. Any external (to this thread) data transmitted in/out via libcoap + * therefore has to be passed in/out by xQueue*() via this thread. + */ + +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event.h" + +#include "nvs_flash.h" + +#include "protocol_examples_common.h" + +#include "coap3/coap.h" + + +#ifndef CONFIG_COAP_CLIENT_SUPPORT +#error COAP_CLIENT_SUPPORT needs to be enabled +#endif /* COAP_CLIENT_SUPPORT */ + +#define COAP_DEFAULT_TIME_SEC 60 + +/* The examples use simple Pre-Shared-Key configuration that you can set via + 'idf.py menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_COAP_PSK_KEY "some-agreed-preshared-key" + + Note: PSK will only be used if the URI is prefixed with coaps:// + instead of coap:// and the PSK must be one that the server supports + (potentially associated with the IDENTITY) +*/ +#define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY +#define EXAMPLE_COAP_PSK_IDENTITY CONFIG_EXAMPLE_COAP_PSK_IDENTITY + +/* The examples use uri Logging Level that + you can set via 'idf.py menuconfig'. + + If you'd rather not, just change the below entry to a value + that is between 0 and 7 with + the config you want - ie #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL 7 +*/ +#define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL + +/* The examples use uri "coap://californium.eclipseprojects.io" that + you can set via the project configuration (idf.py menuconfig) + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define COAP_DEFAULT_DEMO_URI "coaps://californium.eclipseprojects.io" +*/ +#define COAP_DEFAULT_DEMO_URI CONFIG_EXAMPLE_TARGET_DOMAIN_URI + +const static char *TAG = "CoAP_client"; + +static int resp_wait = 1; +static coap_optlist_t *optlist = NULL; +static int wait_ms; + +#ifdef CONFIG_COAP_MBEDTLS_PKI +/* CA cert, taken from coap_ca.pem + Client cert, taken from coap_client.crt + Client key, taken from coap_client.key + + The PEM, CRT and KEY file are examples taken from + https://github.com/eclipse/californium/tree/master/demo-certs/src/main/resources + as the Certificate test (by default) is against the californium server. + + To embed it in the app binary, the PEM, CRT and KEY file is named + in the component.mk COMPONENT_EMBED_TXTFILES variable. + */ +extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start"); +extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end"); +extern uint8_t client_crt_start[] asm("_binary_coap_client_crt_start"); +extern uint8_t client_crt_end[] asm("_binary_coap_client_crt_end"); +extern uint8_t client_key_start[] asm("_binary_coap_client_key_start"); +extern uint8_t client_key_end[] asm("_binary_coap_client_key_end"); +#endif /* CONFIG_COAP_MBEDTLS_PKI */ + +static coap_response_t +message_handler(coap_session_t *session, + const coap_pdu_t *sent, + const coap_pdu_t *received, + const coap_mid_t mid) +{ + const unsigned char *data = NULL; + size_t data_len; + size_t offset; + size_t total; + coap_pdu_code_t rcvd_code = coap_pdu_get_code(received); + + if (COAP_RESPONSE_CLASS(rcvd_code) == 2) { + if (coap_get_data_large(received, &data_len, &data, &offset, &total)) { + if (data_len != total) { + printf("Unexpected partial data received offset %u, length %u\n", offset, data_len); + } + printf("Received:\n%.*s\n", (int)data_len, data); + resp_wait = 0; + } + return COAP_RESPONSE_OK; + } + printf("%d.%02d", (rcvd_code >> 5), rcvd_code & 0x1F); + if (coap_get_data_large(received, &data_len, &data, &offset, &total)) { + printf(": "); + while (data_len--) { + printf("%c", isprint(*data) ? *data : '.'); + data++; + } + } + printf("\n"); + resp_wait = 0; + return COAP_RESPONSE_OK; +} + +#ifdef CONFIG_COAP_MBEDTLS_PKI + +static int +verify_cn_callback(const char *cn, + const uint8_t *asn1_public_cert, + size_t asn1_length, + coap_session_t *session, + unsigned depth, + int validated, + void *arg + ) +{ + coap_log(LOG_INFO, "CN '%s' presented by server (%s)\n", + cn, depth ? "CA" : "Certificate"); + return 1; +} +#endif /* CONFIG_COAP_MBEDTLS_PKI */ + +static void +coap_log_handler (coap_log_t level, const char *message) +{ + uint32_t esp_level = ESP_LOG_INFO; + char *cp = strchr(message, '\n'); + + if (cp) { + ESP_LOG_LEVEL(esp_level, TAG, "%.*s", (int)(cp - message), message); + } else { + ESP_LOG_LEVEL(esp_level, TAG, "%s", message); + } +} + +static coap_address_t * +coap_get_address(coap_uri_t *uri) +{ + static coap_address_t dst_addr; + char *phostname = NULL; + struct addrinfo hints; + struct addrinfo *addrres; + int error; + char tmpbuf[INET6_ADDRSTRLEN]; + + phostname = (char *)calloc(1, uri->host.length + 1); + if (phostname == NULL) { + ESP_LOGE(TAG, "calloc failed"); + return NULL; + } + memcpy(phostname, uri->host.s, uri->host.length); + + memset ((char *)&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; + hints.ai_family = AF_UNSPEC; + + error = getaddrinfo(phostname, NULL, &hints, &addrres); + if (error != 0) { + ESP_LOGE(TAG, "DNS lookup failed for destination address %s. error: %d", phostname, error); + free(phostname); + return NULL; + } + if (addrres == NULL) { + ESP_LOGE(TAG, "DNS lookup %s did not return any addresses", phostname); + free(phostname); + return NULL; + } + free(phostname); + coap_address_init(&dst_addr); + switch (addrres->ai_family) { + case AF_INET: + memcpy(&dst_addr.addr.sin, addrres->ai_addr, sizeof(dst_addr.addr.sin)); + dst_addr.addr.sin.sin_port = htons(uri->port); + inet_ntop(AF_INET, &dst_addr.addr.sin.sin_addr, tmpbuf, sizeof(tmpbuf)); + ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf); + break; + case AF_INET6: + memcpy(&dst_addr.addr.sin6, addrres->ai_addr, sizeof(dst_addr.addr.sin6)); + dst_addr.addr.sin6.sin6_port = htons(uri->port); + inet_ntop(AF_INET6, &dst_addr.addr.sin6.sin6_addr, tmpbuf, sizeof(tmpbuf)); + ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf); + break; + default: + ESP_LOGE(TAG, "DNS lookup response failed"); + return NULL; + } + freeaddrinfo(addrres); + + return &dst_addr; +} + +static int +coap_build_optlist(coap_uri_t *uri) +{ +#define BUFSIZE 40 + unsigned char _buf[BUFSIZE]; + unsigned char *buf; + size_t buflen; + int res; + + optlist = NULL; + + if (uri->scheme == COAP_URI_SCHEME_COAPS && !coap_dtls_is_supported()) { + ESP_LOGE(TAG, "MbedTLS DTLS Client Mode not configured"); + return 0; + } + if (uri->scheme == COAP_URI_SCHEME_COAPS_TCP && !coap_tls_is_supported()) { + ESP_LOGE(TAG, "MbedTLS TLS Client Mode not configured"); + return 0; + } + if (uri->scheme == COAP_URI_SCHEME_COAP_TCP && !coap_tcp_is_supported()) { + ESP_LOGE(TAG, "TCP Client Mode not configured"); + return 0; + } + + if (uri->path.length) { + buflen = BUFSIZE; + buf = _buf; + res = coap_split_path(uri->path.s, uri->path.length, buf, &buflen); + + while (res--) { + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_URI_PATH, + coap_opt_length(buf), + coap_opt_value(buf))); + + buf += coap_opt_size(buf); + } + } + + if (uri->query.length) { + buflen = BUFSIZE; + buf = _buf; + res = coap_split_query(uri->query.s, uri->query.length, buf, &buflen); + + while (res--) { + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_URI_QUERY, + coap_opt_length(buf), + coap_opt_value(buf))); + + buf += coap_opt_size(buf); + } + } + return 1; +} +#ifdef CONFIG_COAP_MBEDTLS_PSK +static coap_session_t * +coap_start_psk_session(coap_context_t *ctx, coap_address_t *dst_addr, coap_uri_t *uri) +{ + static coap_dtls_cpsk_t dtls_psk; + static char client_sni[256]; + + memset(client_sni, 0, sizeof(client_sni)); + memset (&dtls_psk, 0, sizeof(dtls_psk)); + dtls_psk.version = COAP_DTLS_CPSK_SETUP_VERSION; + dtls_psk.validate_ih_call_back = NULL; + dtls_psk.ih_call_back_arg = NULL; + if (uri->host.length) { + memcpy(client_sni, uri->host.s, MIN(uri->host.length, sizeof(client_sni) - 1)); + } else { + memcpy(client_sni, "localhost", 9); + } + dtls_psk.client_sni = client_sni; + dtls_psk.psk_info.identity.s = (const uint8_t *)EXAMPLE_COAP_PSK_IDENTITY; + dtls_psk.psk_info.identity.length = sizeof(EXAMPLE_COAP_PSK_IDENTITY) - 1; + dtls_psk.psk_info.key.s = (const uint8_t *)EXAMPLE_COAP_PSK_KEY; + dtls_psk.psk_info.key.length = sizeof(EXAMPLE_COAP_PSK_KEY) - 1; + return coap_new_client_session_psk2(ctx, NULL, dst_addr, + uri->scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS, + &dtls_psk); +} +#endif /* CONFIG_COAP_MBEDTLS_PSK */ + +#ifdef CONFIG_COAP_MBEDTLS_PKI +static coap_session_t * +coap_start_pki_session(coap_context_t *ctx, coap_address_t *dst_addr, coap_uri_t *uri) +{ + unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start; + unsigned int client_crt_bytes = client_crt_end - client_crt_start; + unsigned int client_key_bytes = client_key_end - client_key_start; + static coap_dtls_pki_t dtls_pki; + static char client_sni[256]; + + memset (&dtls_pki, 0, sizeof(dtls_pki)); + dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION; + if (ca_pem_bytes) { + /* + * Add in additional certificate checking. + * This list of enabled can be tuned for the specific + * requirements - see 'man coap_encryption'. + * + * Note: A list of root cas file can be setup separately using + * coap_context_set_pki_root_cas(), but the below is used to + * define what checking actually takes place. + */ + dtls_pki.verify_peer_cert = 1; + dtls_pki.check_common_ca = 1; + 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.allow_bad_md_hash = 1; + dtls_pki.allow_short_rsa_length = 1; + dtls_pki.validate_cn_call_back = verify_cn_callback; + dtls_pki.cn_call_back_arg = NULL; + dtls_pki.validate_sni_call_back = NULL; + dtls_pki.sni_call_back_arg = NULL; + memset(client_sni, 0, sizeof(client_sni)); + if (uri->host.length) { + memcpy(client_sni, uri->host.s, MIN(uri->host.length, sizeof(client_sni))); + } else { + memcpy(client_sni, "localhost", 9); + } + dtls_pki.client_sni = client_sni; + } + dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF; + dtls_pki.pki_key.key.pem_buf.public_cert = client_crt_start; + dtls_pki.pki_key.key.pem_buf.public_cert_len = client_crt_bytes; + dtls_pki.pki_key.key.pem_buf.private_key = client_key_start; + dtls_pki.pki_key.key.pem_buf.private_key_len = client_key_bytes; + dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start; + dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes; + + return coap_new_client_session_pki(ctx, NULL, dst_addr, + uri->scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS, + &dtls_pki); +} +#endif /* CONFIG_COAP_MBEDTLS_PKI */ + +static void coap_example_client(void *p) +{ + coap_address_t *dst_addr; + static coap_uri_t uri; + const char *server_uri = COAP_DEFAULT_DEMO_URI; + coap_context_t *ctx = NULL; + coap_session_t *session = NULL; + coap_pdu_t *request = NULL; + unsigned char token[8]; + size_t tokenlength; + + /* Set up the CoAP logging */ + coap_set_log_handler(coap_log_handler); + coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL); + + /* Set up the CoAP context */ + ctx = coap_new_context(NULL); + if (!ctx) { + ESP_LOGE(TAG, "coap_new_context() failed"); + goto clean_up; + } + coap_context_set_block_mode(ctx, + COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY); + + coap_register_response_handler(ctx, message_handler); + + if (coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri) == -1) { + ESP_LOGE(TAG, "CoAP server uri error"); + goto clean_up; + } + if (!coap_build_optlist(&uri)) { + goto clean_up; + } + + dst_addr = coap_get_address(&uri); + if (!dst_addr) { + goto clean_up; + } + + /* + * Note that if the URI starts with just coap:// (not coaps://) the + * session will still be plain text. + */ + if (uri.scheme == COAP_URI_SCHEME_COAPS || uri.scheme == COAP_URI_SCHEME_COAPS_TCP) { +#ifndef CONFIG_MBEDTLS_TLS_CLIENT + ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured"); + goto clean_up; +#endif /* CONFIG_MBEDTLS_TLS_CLIENT */ + +#ifdef CONFIG_COAP_MBEDTLS_PSK + session = coap_start_psk_session(ctx, dst_addr, &uri); +#endif /* CONFIG_COAP_MBEDTLS_PSK */ + +#ifdef CONFIG_COAP_MBEDTLS_PKI + session = coap_start_pki_session(ctx, dst_addr, &uri); +#endif /* CONFIG_COAP_MBEDTLS_PKI */ + } else { + session = coap_new_client_session(ctx, NULL, dst_addr, + uri.scheme == COAP_URI_SCHEME_COAP_TCP ? COAP_PROTO_TCP : + COAP_PROTO_UDP); + } + if (!session) { + ESP_LOGE(TAG, "coap_new_client_session() failed"); + goto clean_up; + } + + while (1) { + request = coap_new_pdu(coap_is_mcast(dst_addr) ? COAP_MESSAGE_NON : COAP_MESSAGE_CON, + COAP_REQUEST_CODE_GET, session); + if (!request) { + ESP_LOGE(TAG, "coap_new_pdu() failed"); + goto clean_up; + } + /* Add in an unique token */ + coap_session_new_token(session, &tokenlength, token); + coap_add_token(request, tokenlength, token); + + /* + * To make this a POST, you will need to do the following + * Change COAP_REQUEST_CODE_GET to COAP_REQUEST_CODE_POST for coap_new_pdu() + * Add in here a Content-Type Option based on the format of the POST text. E.G. for JSON + * u_char buf[4]; + * coap_insert_optlist(&optlist, + * coap_new_optlist(COAP_OPTION_CONTENT_FORMAT, + * coap_encode_var_safe (buf, sizeof (buf), + * COAP_MEDIATYPE_APPLICATION_JSON), + * buf)); + * Add in here the POST data of length length. E.G. + * coap_add_data_large_request(session, request length, data, NULL, NULL); + */ + + coap_add_optlist_pdu(request, &optlist); + + resp_wait = 1; + coap_send(session, request); + + wait_ms = COAP_DEFAULT_TIME_SEC * 1000; + + while (resp_wait) { + int result = coap_io_process(ctx, wait_ms > 1000 ? 1000 : wait_ms); + if (result >= 0) { + if (result >= wait_ms) { + ESP_LOGE(TAG, "No response from server"); + break; + } else { + wait_ms -= result; + } + } + } + for (int countdown = 10; countdown >= 0; countdown--) { + ESP_LOGI(TAG, "%d... ", countdown); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + ESP_LOGI(TAG, "Starting again!"); + } + +clean_up: + if (optlist) { + coap_delete_optlist(optlist); + optlist = NULL; + } + if (session) { + coap_session_release(session); + } + if (ctx) { + coap_free_context(ctx); + } + coap_cleanup(); + + ESP_LOGI(TAG, "Finished"); + vTaskDelete(NULL); +} + +void app_main(void) +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + xTaskCreate(coap_example_client, "coap", 8 * 1024, NULL, 5, NULL); +} diff --git a/coap/examples/coap_client/main/idf_component.yml b/coap/examples/coap_client/main/idf_component.yml new file mode 100644 index 0000000000..352e7134f0 --- /dev/null +++ b/coap/examples/coap_client/main/idf_component.yml @@ -0,0 +1,6 @@ +version: "1.0.0" +description: CoAP Client Example +dependencies: + espressif/coap: + version: "^4.3.0" + override_path: '../../../' diff --git a/coap/examples/coap_client/pytest_coap_client_example.py b/coap/examples/coap_client/pytest_coap_client_example.py new file mode 100644 index 0000000000..6e70feb74c --- /dev/null +++ b/coap/examples/coap_client/pytest_coap_client_example.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pexpect +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.ethernet +def test_coap_example(dut: Dut) -> None: + dut.expect('Loaded app from partition at offset', timeout=30) + try: + dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30) + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + + dut.expect('DNS lookup succeeded', timeout=30) + dut.expect('Received', timeout=30) + dut.expect(r'This server is using the Eclipse Californium \(Cf\) CoAP framework', timeout=30) + dut.expect(r'published under EPL\+EDL: http://www\.eclipse\.org/californium/', timeout=30) + dut.expect('Starting again!', timeout=30) diff --git a/coap/examples/coap_client/sdkconfig.ci b/coap/examples/coap_client/sdkconfig.ci new file mode 100644 index 0000000000..f958eb22e9 --- /dev/null +++ b/coap/examples/coap_client/sdkconfig.ci @@ -0,0 +1,8 @@ +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 diff --git a/coap/examples/coap_client/sdkconfig.defaults b/coap/examples/coap_client/sdkconfig.defaults new file mode 100644 index 0000000000..2cec21be75 --- /dev/null +++ b/coap/examples/coap_client/sdkconfig.defaults @@ -0,0 +1,5 @@ +CONFIG_MBEDTLS_SSL_PROTO_DTLS=y +CONFIG_MBEDTLS_PSK_MODES=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y +CONFIG_LWIP_NETBUF_RECVINFO=y +CONFIG_COAP_CLIENT_SUPPORT=y diff --git a/coap/examples/coap_client/sdkconfig.defaults.esp32h2 b/coap/examples/coap_client/sdkconfig.defaults.esp32h2 new file mode 100644 index 0000000000..ed944f6335 --- /dev/null +++ b/coap/examples/coap_client/sdkconfig.defaults.esp32h2 @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_CONNECT_ETHERNET=y \ No newline at end of file diff --git a/coap/examples/coap_server/CMakeLists.txt b/coap/examples/coap_server/CMakeLists.txt new file mode 100644 index 0000000000..32186b0906 --- /dev/null +++ b/coap/examples/coap_server/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(coap_server) diff --git a/coap/examples/coap_server/README.md b/coap/examples/coap_server/README.md new file mode 100644 index 0000000000..2f45f200d7 --- /dev/null +++ b/coap/examples/coap_server/README.md @@ -0,0 +1,96 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | + + +# CoAP server example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) +This CoAP server example is very simplified adaptation of one of the +[libcoap](https://github.com/obgm/libcoap) examples. + +CoAP server example will startup a daemon task, receive requests / data from CoAP client and transmit +data to CoAP client. + +If the incoming request requests the use of DTLS (connecting to port 5684), then the CoAP server will +try to establish a DTLS session using the previously defined Pre-Shared Key (PSK) - which +must be the same as the one that the CoAP client is using, or Public Key Infrastructure (PKI) where +the PKI information must match as requested. + +The Constrained Application Protocol (CoAP) is a specialized web transfer protocol for use with +constrained nodes and constrained networks in the Internet of Things. +The protocol is designed for machine-to-machine (M2M) applications such as smart energy and +building automation. + +Please refer to [RFC7252](https://www.rfc-editor.org/rfc/pdfrfc/rfc7252.txt.pdf) for more details. + +## How to use example + +### Configure the project + +``` +idf.py menuconfig +``` + +Example Connection Configuration ---> + * Set WiFi SSID + * Set WiFi Password +Component config ---> + CoAP Configuration ---> + * Set encryption method definition, PSK (default) or PKI + * Enable CoAP debugging if required + * Disable CoAP using TCP if this is not required (TCP needed for TLS) + * Disable CoAP client functionality to reduce code size unless this server is a proxy +Example CoAP Server Configuration ---> + * If PSK, Set CoAP Preshared Key to use for connections to the server + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py build +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +current CoAP server would startup a daemon task, +and the log is such as the following: + +``` +... +I (332) wifi: mode : sta (30:ae:a4:04:1b:7c) +I (1672) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1 +I (1672) wifi: state: init -> auth (b0) +I (1682) wifi: state: auth -> assoc (0) +I (1692) wifi: state: assoc -> run (10) +I (1692) wifi: connected with huawei_cw, channel 11 +I (1692) wifi: pm start, type: 1 + +I (2622) event: sta ip: 192.168.3.84, mask: 255.255.255.0, gw: 192.168.3.1 +I (2622) CoAP_server: Connected to AP +... +``` + +If a CoAP client queries the `/Espressif` resource, CoAP server will return `"Hello World!"` +until a CoAP client does a PUT with different data. + +## libcoap Documentation +This can be found at [libcoap Documentation](https://libcoap.net/documentation.html). +The current API is 4.3.0. + +## libcoap Specific Issues +These can be raised at [libcoap Issues](https://github.com/obgm/libcoap/issues). + +## Troubleshooting +* Please make sure CoAP client fetchs or puts data under path: `/Espressif` or +fetches `/.well-known/core` + +* CoAP logging can be enabled by running 'idf.py menuconfig -> Component config -> CoAP Configuration -> Enable CoAP debugging' +and setting appropriate log level. If Mbed TLS logging is required, this needs to be configured separately under mbedTLS +Component Configuration and the CoAP logging level set to mbedTLS. + +* CoAP library does not support IPv6 only configuration, so it is necessary to enable `LWIP_IPv4` diff --git a/coap/examples/coap_server/main/CMakeLists.txt b/coap/examples/coap_server/main/CMakeLists.txt new file mode 100644 index 0000000000..a014088ece --- /dev/null +++ b/coap/examples/coap_server/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "coap_server_example_main.c" + INCLUDE_DIRS "." + EMBED_TXTFILES certs/coap_ca.pem certs/coap_server.crt certs/coap_server.key) diff --git a/coap/examples/coap_server/main/Kconfig.projbuild b/coap/examples/coap_server/main/Kconfig.projbuild new file mode 100644 index 0000000000..7821b779ce --- /dev/null +++ b/coap/examples/coap_server/main/Kconfig.projbuild @@ -0,0 +1,67 @@ +menu "Example CoAP Server Configuration" + + # Hidden option that selects IPv4 + config EXAMPLE_COAP_NEEDS_IPV4 + bool + default true + select LWIP_IPV4 + + config EXAMPLE_COAP_PSK_KEY + string "Preshared Key (PSK) to used in the connection from the CoAP client" + depends on COAP_MBEDTLS_PSK + default "secret-key" + help + The Preshared Key to use to encrypt the communicatons. The same key must be + used at both ends of the CoAP connection, and the CoaP client must request + an URI prefixed with coaps:// instead of coap:// for DTLS to be used. + + choice EXAMPLE_COAP_MCAST_IP_MODE + prompt "Receive Multicast IP type" + help + Example can receive multicast IPV4, IPV6, both or none. + + config EXAMPLE_COAP_MCAST_NONE + bool "None" + + config EXAMPLE_COAP_MCAST_IPV4_V6 + bool "IPV4 & IPV6" + select EXAMPLE_COAP_MCAST_IPV4 + select EXAMPLE_COAP_MCAST_IPV6 + + config EXAMPLE_COAP_MCAST_IPV4_ONLY + bool "IPV4" + select EXAMPLE_COAP_MCAST_IPV4 + + config EXAMPLE_COAP_MCAST_IPV6_ONLY + bool "IPV6" + select EXAMPLE_COAP_MCAST_IPV6 + + endchoice + + config EXAMPLE_COAP_MCAST_IPV4 + bool + config EXAMPLE_COAP_MCAST_IPV6 + bool + select EXAMPLE_CONNECT_IPV6 if IDF_TARGET_ESP32 + + config EXAMPLE_COAP_MULTICAST_IPV4_ADDR + string "CoAP Multicast IPV4 Address (receive)" + default "224.0.1.187" + depends on EXAMPLE_COAP_MCAST_IPV4 + help + IPV4 multicast address to receive multicast traffic on. + + The default CoAP IPV4 address is 224.0.1.187. + + config EXAMPLE_COAP_MULTICAST_IPV6_ADDR + string "CoAP Multicast IPV6 Address (receive)" + default "FF02::FD" + depends on EXAMPLE_COAP_MCAST_IPV6 + help + IPV6 multicast address to receive multicast traffic on. + + The default CoAP FF02::FD address is a link-local multicast address. + Consult IPV6 specifications or documentation for information about + meaning of different IPV6 multicast ranges. + +endmenu diff --git a/coap/examples/coap_server/main/certs/coap_ca.pem b/coap/examples/coap_server/main/certs/coap_ca.pem new file mode 100644 index 0000000000..07900ec471 --- /dev/null +++ b/coap/examples/coap_server/main/certs/coap_ca.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIICDzCCAbSgAwIBAgIIAbOUoVFDz/QwDAYIKoZIzj0EAwIFADBcMRAwDgYDVQQD +EwdjZi1yb290MRQwEgYDVQQLEwtDYWxpZm9ybml1bTEUMBIGA1UEChMLRWNsaXBz +ZSBJb1QxDzANBgNVBAcTBk90dGF3YTELMAkGA1UEBhMCQ0EwHhcNMjAxMTExMTAz +MDMzWhcNMjExMTExMTAzMDMzWjBaMQ4wDAYDVQQDEwVjZi1jYTEUMBIGA1UECxML +Q2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2UgSW9UMQ8wDQYDVQQHEwZPdHRh +d2ExCzAJBgNVBAYTAkNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7/3EXOZn +GZXNEIj7LuQAMZ8lfRYSCnpME1TBjKjZPtVeztLtGWgkkLvIX11pAJcBh51cpi7Z +fQtGpVE9CLOh6aNgMF4wHQYDVR0OBBYEFEvf57UcJhYYkx14twkeitd691fVMAsG +A1UdDwQEAwIBBjAPBgNVHRMECDAGAQH/AgEBMB8GA1UdIwQYMBaAFAsi3KbVERiK +JzFCfC/GVrYksGzEMAwGCCqGSM49BAMCBQADRwAwRAIgc5nVF/5Pip0XB17IZXqi +V84FXanWdn9Z0SiPdpOgvZMCIH13vL9tkCCjPN3tg3TYRY/bzyGohFGBcTrrEtUr +rVIm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB4DCCAYWgAwIBAgIIQR8ro8AQ02AwDAYIKoZIzj0EAwIFADBcMRAwDgYDVQQD +EwdjZi1yb290MRQwEgYDVQQLEwtDYWxpZm9ybml1bTEUMBIGA1UEChMLRWNsaXBz +ZSBJb1QxDzANBgNVBAcTBk90dGF3YTELMAkGA1UEBhMCQ0EwHhcNMjAxMTExMTAz +MDMyWhcNMjExMTExMTAzMDMyWjBcMRAwDgYDVQQDEwdjZi1yb290MRQwEgYDVQQL +EwtDYWxpZm9ybml1bTEUMBIGA1UEChMLRWNsaXBzZSBJb1QxDzANBgNVBAcTBk90 +dGF3YTELMAkGA1UEBhMCQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATZ1BRM +T1//Fzh9sneRZNwS4kgCxN1PvgwT271qCpYqyxnjLEa38AP1IAanhpiD/OkVc0Zd +7NgDPCw7n94EULMyoy8wLTAdBgNVHQ4EFgQUCyLcptURGIonMUJ8L8ZWtiSwbMQw +DAYDVR0TBAUwAwEB/zAMBggqhkjOPQQDAgUAA0cAMEQCIAdLEgcUWdpAl9jwdJiz +/cHW7/CBIWEvqiQfzE+XLyLOAiAvuxSdOtSDjh2aC5qEjUCH8CSKCxWB74j23tmp +aqPH4A== +-----END CERTIFICATE----- diff --git a/coap/examples/coap_server/main/certs/coap_server.crt b/coap/examples/coap_server/main/certs/coap_server.crt new file mode 100644 index 0000000000..6522f4e4cd --- /dev/null +++ b/coap/examples/coap_server/main/certs/coap_server.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICAzCCAaagAwIBAgIJANqCHDjOKHh+MAwGCCqGSM49BAMCBQAwWjEOMAwGA1UE +AxMFY2YtY2ExFDASBgNVBAsTC0NhbGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNl +IElvVDEPMA0GA1UEBxMGT3R0YXdhMQswCQYDVQQGEwJDQTAeFw0yMDExMTExMDMw +MzRaFw0yMTExMTExMDMwMzRaMF4xEjAQBgNVBAMTCWNmLXNlcnZlcjEUMBIGA1UE +CxMLQ2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2UgSW9UMQ8wDQYDVQQHEwZP +dHRhd2ExCzAJBgNVBAYTAkNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+obM +gHmMB7zS4KArciXPD7CrvgEYqlnAf7NOTdb54RbTr4qEpPL+OJ6Pg8VhrF4hGEne +T6Aa4qqpmTkxmfT0vqNPME0wHQYDVR0OBBYEFE4XpfFad+F3+RcwI+s1cmJbTZWG +MAsGA1UdDwQEAwIHgDAfBgNVHSMEGDAWgBRL3+e1HCYWGJMdeLcJHorXevdX1TAM +BggqhkjOPQQDAgUAA0kAMEYCIQCEo+O5zqYKdwi/ElB4wfNVIf76P1OhIXAT5CHc +3ebBPQIhAN6UhCgQ0av6kf7INCazV3KmN7HmPXARaY4YKWsRwsg+ +-----END CERTIFICATE----- diff --git a/coap/examples/coap_server/main/certs/coap_server.key b/coap/examples/coap_server/main/certs/coap_server.key new file mode 100644 index 0000000000..362851524f --- /dev/null +++ b/coap/examples/coap_server/main/certs/coap_server.key @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCLBQT66xp2w4+1K+Ai +/TXEC8tQZBxl9brFyK4F7AQNGw== +-----END PRIVATE KEY----- diff --git a/coap/examples/coap_server/main/coap_server_example_main.c b/coap/examples/coap_server/main/coap_server_example_main.c new file mode 100644 index 0000000000..79f7fc1218 --- /dev/null +++ b/coap/examples/coap_server/main/coap_server_example_main.c @@ -0,0 +1,371 @@ +/* CoAP server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* + * WARNING + * libcoap is not multi-thread safe, so only this thread must make any coap_*() + * calls. Any external (to this thread) data transmitted in/out via libcoap + * therefore has to be passed in/out by xQueue*() via this thread. + */ + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event.h" + +#include "nvs_flash.h" + +#include "protocol_examples_common.h" + +#include "coap3/coap.h" + +#ifndef CONFIG_COAP_SERVER_SUPPORT +#error COAP_SERVER_SUPPORT needs to be enabled +#endif /* COAP_SERVER_SUPPORT */ + +/* The examples use simple Pre-Shared-Key configuration that you can set via + 'idf.py menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_COAP_PSK_KEY "some-agreed-preshared-key" + + Note: PSK will only be used if the URI is prefixed with coaps:// + instead of coap:// and the PSK must be one that the server supports + (potentially associated with the IDENTITY) +*/ +#define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY + +/* The examples use CoAP Logging Level that + you can set via 'idf.py menuconfig'. + + If you'd rather not, just change the below entry to a value + that is between 0 and 7 with + the config you want - ie #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL 7 +*/ +#define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL + +const static char *TAG = "CoAP_server"; + +static char espressif_data[100]; +static int espressif_data_len = 0; + +#ifdef CONFIG_COAP_MBEDTLS_PKI +/* CA cert, taken from coap_ca.pem + Server cert, taken from coap_server.crt + Server key, taken from coap_server.key + + The PEM, CRT and KEY file are examples taken from + https://github.com/eclipse/californium/tree/master/demo-certs/src/main/resources + as the Certificate test (by default) for the coap_client is against the + californium server. + + To embed it in the app binary, the PEM, CRT and KEY file is named + in the component.mk COMPONENT_EMBED_TXTFILES variable. + */ +extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start"); +extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end"); +extern uint8_t server_crt_start[] asm("_binary_coap_server_crt_start"); +extern uint8_t server_crt_end[] asm("_binary_coap_server_crt_end"); +extern uint8_t server_key_start[] asm("_binary_coap_server_key_start"); +extern uint8_t server_key_end[] asm("_binary_coap_server_key_end"); +#endif /* CONFIG_COAP_MBEDTLS_PKI */ + +#define INITIAL_DATA "Hello World!" + +/* + * The resource handler + */ +static void +hnd_espressif_get(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) +{ + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, 60, 0, + (size_t)espressif_data_len, + (const u_char *)espressif_data, + NULL, NULL); +} + +static void +hnd_espressif_put(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; + size_t offset; + size_t total; + const unsigned char *data; + + coap_resource_notify_observers(resource, NULL); + + if (strcmp (espressif_data, INITIAL_DATA) == 0) { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CREATED); + } else { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED); + } + + /* coap_get_data_large() sets size to 0 on error */ + (void)coap_get_data_large(request, &size, &data, &offset, &total); + + if (size == 0) { /* re-init */ + snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA); + espressif_data_len = strlen(espressif_data); + } else { + espressif_data_len = size > sizeof (espressif_data) ? sizeof (espressif_data) : size; + memcpy (espressif_data, data, espressif_data_len); + } +} + +static void +hnd_espressif_delete(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) +{ + coap_resource_notify_observers(resource, NULL); + snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA); + espressif_data_len = strlen(espressif_data); + coap_pdu_set_code(response, COAP_RESPONSE_CODE_DELETED); +} + +#ifdef CONFIG_COAP_MBEDTLS_PKI + +static int +verify_cn_callback(const char *cn, + const uint8_t *asn1_public_cert, + size_t asn1_length, + coap_session_t *session, + unsigned depth, + int validated, + void *arg + ) +{ + coap_log(LOG_INFO, "CN '%s' presented by server (%s)\n", + cn, depth ? "CA" : "Certificate"); + return 1; +} +#endif /* CONFIG_COAP_MBEDTLS_PKI */ + +static void +coap_log_handler (coap_log_t level, const char *message) +{ + uint32_t esp_level = ESP_LOG_INFO; + char *cp = strchr(message, '\n'); + + if (cp) { + ESP_LOG_LEVEL(esp_level, TAG, "%.*s", (int)(cp - message), message); + } else { + ESP_LOG_LEVEL(esp_level, TAG, "%s", message); + } +} + +static void coap_example_server(void *p) +{ + coap_context_t *ctx = NULL; + coap_address_t serv_addr; + coap_resource_t *resource = NULL; + + snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA); + espressif_data_len = strlen(espressif_data); + coap_set_log_handler(coap_log_handler); + coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL); + + while (1) { + coap_endpoint_t *ep = NULL; + unsigned wait_ms; + int have_dtls = 0; + + /* Prepare the CoAP server socket */ + coap_address_init(&serv_addr); + serv_addr.addr.sin6.sin6_family = AF_INET6; + serv_addr.addr.sin6.sin6_port = htons(COAP_DEFAULT_PORT); + + ctx = coap_new_context(NULL); + if (!ctx) { + ESP_LOGE(TAG, "coap_new_context() failed"); + continue; + } + coap_context_set_block_mode(ctx, + COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY); +#ifdef CONFIG_COAP_MBEDTLS_PSK + /* Need PSK setup before we set up endpoints */ + coap_context_set_psk(ctx, "CoAP", + (const uint8_t *)EXAMPLE_COAP_PSK_KEY, + sizeof(EXAMPLE_COAP_PSK_KEY) - 1); +#endif /* CONFIG_COAP_MBEDTLS_PSK */ + +#ifdef CONFIG_COAP_MBEDTLS_PKI + /* Need PKI setup before we set up endpoints */ + unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start; + unsigned int server_crt_bytes = server_crt_end - server_crt_start; + unsigned int server_key_bytes = server_key_end - server_key_start; + coap_dtls_pki_t dtls_pki; + + memset (&dtls_pki, 0, sizeof(dtls_pki)); + dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION; + if (ca_pem_bytes) { + /* + * Add in additional certificate checking. + * This list of enabled can be tuned for the specific + * requirements - see 'man coap_encryption'. + * + * Note: A list of root ca file can be setup separately using + * coap_context_set_pki_root_cas(), but the below is used to + * define what checking actually takes place. + */ + dtls_pki.verify_peer_cert = 1; + dtls_pki.check_common_ca = 1; + 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.allow_bad_md_hash = 1; + dtls_pki.allow_short_rsa_length = 1; + dtls_pki.validate_cn_call_back = verify_cn_callback; + dtls_pki.cn_call_back_arg = NULL; + dtls_pki.validate_sni_call_back = NULL; + dtls_pki.sni_call_back_arg = NULL; + } + dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF; + dtls_pki.pki_key.key.pem_buf.public_cert = server_crt_start; + dtls_pki.pki_key.key.pem_buf.public_cert_len = server_crt_bytes; + dtls_pki.pki_key.key.pem_buf.private_key = server_key_start; + dtls_pki.pki_key.key.pem_buf.private_key_len = server_key_bytes; + dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start; + dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes; + + coap_context_set_pki(ctx, &dtls_pki); +#endif /* CONFIG_COAP_MBEDTLS_PKI */ + + ep = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_UDP); + if (!ep) { + ESP_LOGE(TAG, "udp: coap_new_endpoint() failed"); + goto clean_up; + } + if (coap_tcp_is_supported()) { + ep = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_TCP); + if (!ep) { + ESP_LOGE(TAG, "tcp: coap_new_endpoint() failed"); + goto clean_up; + } + } +#if defined(CONFIG_COAP_MBEDTLS_PSK) || defined(CONFIG_COAP_MBEDTLS_PKI) + if (coap_dtls_is_supported()) { +#ifndef CONFIG_MBEDTLS_TLS_SERVER + /* This is not critical as unencrypted support is still available */ + ESP_LOGI(TAG, "MbedTLS DTLS Server Mode not configured"); +#else /* CONFIG_MBEDTLS_TLS_SERVER */ + serv_addr.addr.sin6.sin6_port = htons(COAPS_DEFAULT_PORT); + ep = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_DTLS); + if (!ep) { + ESP_LOGE(TAG, "dtls: coap_new_endpoint() failed"); + goto clean_up; + } + have_dtls = 1; +#endif /* CONFIG_MBEDTLS_TLS_SERVER */ + } + if (coap_tls_is_supported()) { +#ifndef CONFIG_MBEDTLS_TLS_SERVER + /* This is not critical as unencrypted support is still available */ + ESP_LOGI(TAG, "MbedTLS TLS Server Mode not configured"); +#else /* CONFIG_MBEDTLS_TLS_SERVER */ + serv_addr.addr.sin6.sin6_port = htons(COAPS_DEFAULT_PORT); + ep = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_TLS); + if (!ep) { + ESP_LOGE(TAG, "tls: coap_new_endpoint() failed"); + goto clean_up; + } +#endif /* CONFIG_MBEDTLS_TLS_SERVER */ + } + if (!have_dtls) { + /* This is not critical as unencrypted support is still available */ + ESP_LOGI(TAG, "MbedTLS (D)TLS Server Mode not configured"); + } +#endif /* CONFIG_COAP_MBEDTLS_PSK || CONFIG_COAP_MBEDTLS_PKI */ + resource = coap_resource_init(coap_make_str_const("Espressif"), 0); + if (!resource) { + ESP_LOGE(TAG, "coap_resource_init() failed"); + goto clean_up; + } + coap_register_handler(resource, COAP_REQUEST_GET, hnd_espressif_get); + coap_register_handler(resource, COAP_REQUEST_PUT, hnd_espressif_put); + coap_register_handler(resource, COAP_REQUEST_DELETE, hnd_espressif_delete); + /* We possibly want to Observe the GETs */ + coap_resource_set_get_observable(resource, 1); + coap_add_resource(ctx, resource); + +#if defined(CONFIG_EXAMPLE_COAP_MCAST_IPV4) || defined(CONFIG_EXAMPLE_COAP_MCAST_IPV6) + esp_netif_t *netif = NULL; + for (int i = 0; i < esp_netif_get_nr_of_ifs(); ++i) { + char buf[8]; + netif = esp_netif_next(netif); + esp_netif_get_netif_impl_name(netif, buf); +#if defined(CONFIG_EXAMPLE_COAP_MCAST_IPV4) + coap_join_mcast_group_intf(ctx, CONFIG_EXAMPLE_COAP_MULTICAST_IPV4_ADDR, buf); +#endif /* CONFIG_EXAMPLE_COAP_MCAST_IPV4 */ +#if defined(CONFIG_EXAMPLE_COAP_MCAST_IPV6) + /* When adding IPV6 esp-idf requires ifname param to be filled in */ + coap_join_mcast_group_intf(ctx, CONFIG_EXAMPLE_COAP_MULTICAST_IPV6_ADDR, buf); +#endif /* CONFIG_EXAMPLE_COAP_MCAST_IPV6 */ + } +#endif /* CONFIG_EXAMPLE_COAP_MCAST_IPV4 || CONFIG_EXAMPLE_COAP_MCAST_IPV6 */ + + wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; + + while (1) { + int result = coap_io_process(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; + } + if (result) { + /* result must have been >= wait_ms, so reset wait_ms */ + wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; + } + } + } +clean_up: + coap_free_context(ctx); + coap_cleanup(); + + vTaskDelete(NULL); +} + +void app_main(void) +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + xTaskCreate(coap_example_server, "coap", 8 * 1024, NULL, 5, NULL); +} diff --git a/coap/examples/coap_server/main/idf_component.yml b/coap/examples/coap_server/main/idf_component.yml new file mode 100644 index 0000000000..32b2b02bd7 --- /dev/null +++ b/coap/examples/coap_server/main/idf_component.yml @@ -0,0 +1,6 @@ +version: "1.0.0" +description: CoAP Server Example +dependencies: + espressif/coap: + version: "^4.3.0" + override_path: '../../../' diff --git a/coap/examples/coap_server/sdkconfig.defaults b/coap/examples/coap_server/sdkconfig.defaults new file mode 100644 index 0000000000..8bc64e0867 --- /dev/null +++ b/coap/examples/coap_server/sdkconfig.defaults @@ -0,0 +1,5 @@ +CONFIG_MBEDTLS_SSL_PROTO_DTLS=y +CONFIG_MBEDTLS_PSK_MODES=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y +CONFIG_LWIP_NETBUF_RECVINFO=y +CONFIG_COAP_SERVER_SUPPORT=y diff --git a/coap/examples/coap_server/sdkconfig.defaults.esp32h2 b/coap/examples/coap_server/sdkconfig.defaults.esp32h2 new file mode 100644 index 0000000000..ed944f6335 --- /dev/null +++ b/coap/examples/coap_server/sdkconfig.defaults.esp32h2 @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_CONNECT_ETHERNET=y \ No newline at end of file diff --git a/pytest.ini b/pytest.ini index 51dff47f02..7e0bccc6d2 100644 --- a/pytest.ini +++ b/pytest.ini @@ -20,6 +20,7 @@ markers = # env markers generic: generic runner usb_host: usb host runners + ethernet: ethernet runners # log related log_cli = True