Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/freertos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# SPDX-License-Identifier: Apache-2.0
#
# The FreeRTOS port on ESP-IDF: the sample built for a RISC-V and two Xtensa
# chips with every feature, and once with most features off.
name: freertos

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
esp-idf:
name: ESP-IDF, ${{ matrix.target }}, ${{ matrix.features }}
runs-on: ubuntu-latest
container:
image: espressif/idf:v6.1
strategy:
fail-fast: false
matrix:
include:
- target: esp32c6
features: all
- target: esp32s3
features: all
- target: esp32
features: all
- target: esp32c6
features: small
defaults:
run:
shell: bash
working-directory: ports/freertos/esp-idf/samples/minimal
steps:
- uses: actions/checkout@v4

- name: Build
run: |
. $IDF_PATH/export.sh
defaults="sdkconfig.defaults"
if [ "${{ matrix.features }}" = small ]; then
defaults="$defaults;sdkconfig.ci.small"
fi
idf.py -D IDF_TARGET=${{ matrix.target }} \
-D "SDKCONFIG_DEFAULTS=$defaults" build
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ so the client looks like a native library to the people who use that RTOS.
| Port | Status | How you add it |
|---|---|---|
| [Zephyr](ports/zephyr/README.md) | Verified on ESP32-C6, ESP32-S3, and ESP32 | A Zephyr module: a west project, Kconfig, profiles |
| FreeRTOS (ESP-IDF first) | Planned | An ESP-IDF component; plain CMake for other FreeRTOS builds |
| [FreeRTOS](ports/freertos/README.md): ESP-IDF first | Builds for ESP32-C6, ESP32-S3, and ESP32; tested on an ESP32-C6 | An ESP-IDF component with Kconfig; the FreeRTOS kernel layer is ready for other FreeRTOS platforms |
| Eclipse ThreadX | Planned | CMake library with a user configuration header |
| [POSIX](ports/posix/README.md): Linux and macOS hosts; QNX and VxWorks RTP later | Run against Cumulocity on macOS; builds and passes its tests on Linux and macOS | A CMake library with CMake options |

Expand All @@ -33,7 +33,11 @@ tiny-edge/
├── ports/
│ ├── zephyr/ build glue, Kconfig, profiles, samples, tests, and the
│ │ Zephyr-only sources
│ └── posix/ CMake library, options, sample, and tests
│ ├── freertos/ FreeRTOS kernel layer, and esp-idf/: the ESP-IDF
│ │ component (tiny_edge) and its sample
│ ├── posix/ CMake library, options, sample, and tests
│ └── common/ not a port: BSD sockets with mbedTLS, and the device
│ key, shared by the POSIX and ESP-IDF ports
├── provisioning/ optional library: set a device up over Improv Wi-Fi
│ (include/tedge/provisioning.h)
├── tools/onboard/ tedge-onboard: set a device up from a computer
Expand All @@ -57,15 +61,15 @@ configuration header, so the core builds the same way everywhere.
**`ports/<rtos>/`** holds what does depend on the RTOS. A port is small and
follows the RTOS's own conventions for:

| Concern | Zephyr port | POSIX port | Other ports |
|---|---|---|---|
| Packaging | Zephyr module, west project | CMake library | The RTOS's component or library format |
| Configuration | Kconfig `CONFIG_TEDGE_*`, mapped to `TEDGE_*` | CMake options `TEDGE_*` | `tedge_user_config.h`, in the style of `FreeRTOSConfig.h` and `tx_user.h` (ESP-IDF uses Kconfig) |
| Threads and locks | `k_thread`, `k_mutex`, work queues | pthreads | The RTOS's task and mutex API |
| Network and TLS | Zephyr sockets with TLS credentials | BSD sockets with mbedTLS | The RTOS's sockets with its usual TLS stack (mbedTLS, NetX Secure, OpenSSL) |
| Persistent settings | Settings subsystem | A file per key | NVS, a file, or the RTOS's key-value store |
| Firmware update | MCUboot | Not yet | MCUboot, or the platform's own OTA mechanism |
| Logging and shell | Zephyr logging and shell | Standard error | The RTOS's logging |
| Concern | Zephyr port | FreeRTOS port (ESP-IDF) | POSIX port | Other ports |
|---|---|---|---|---|
| Packaging | Zephyr module, west project | ESP-IDF component | CMake library | The RTOS's component or library format |
| Configuration | Kconfig `CONFIG_TEDGE_*`, mapped to `TEDGE_*` | Kconfig `CONFIG_TEDGE_*`, mapped to `TEDGE_*` | CMake options `TEDGE_*` | `tedge_user_config.h`, in the style of `FreeRTOSConfig.h` and `tx_user.h` |
| Threads and locks | `k_thread`, `k_mutex`, work queues | Static FreeRTOS tasks, semaphores, event groups, software timers | pthreads | The RTOS's task and mutex API |
| Network and TLS | Zephyr sockets with TLS credentials | lwIP's BSD sockets with mbedTLS (shared with POSIX) | BSD sockets with mbedTLS | The RTOS's sockets with its usual TLS stack (mbedTLS, NetX Secure, OpenSSL) |
| Persistent settings | Settings subsystem | NVS | A file per key | The RTOS's key-value store, or a file |
| Firmware update | MCUboot | OTA partitions with the bootloader's rollback | Not yet | MCUboot, or the platform's own OTA mechanism |
| Logging and shell | Zephyr logging and shell | `ESP_LOG`, and `esp_console` commands for the cloud shell | Standard error | The RTOS's logging |

The port interface sits at the transport level: a port provides a TLS byte
stream, and the core speaks MQTT, HTTP, and WebSocket over it. One protocol
Expand Down
3 changes: 2 additions & 1 deletion core/src/tedge_cert_renew.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <mbedtls/x509_crt.h>

#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
Expand Down Expand Up @@ -113,7 +114,7 @@ int tedge_cert_twin(char *buf, size_t len)
strftime(iso, sizeof(iso), "%Y-%m-%dT%H:%M:%SZ", &tm);
return (snprintf(buf, len,
"{\"expires\":\"%s\",\"daysRemaining\":%d,"
"\"renewals\":%u}",
"\"renewals\":%" PRIu32 "}",
iso, days, renewals) < (int)len)
? 0
: -ENOSPC;
Expand Down
9 changes: 5 additions & 4 deletions core/src/tedge_parameters.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <tedge/port/log.h>

#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -184,7 +185,7 @@ static int check_value(const struct tedge_parameter *p, const char *raw,
}
if (v < p->min || v > p->max) {
snprintf(reason, reason_len,
"%s: %ld is outside %d..%d", p->name, v,
"%s: %ld is outside %" PRId32 "..%" PRId32, p->name, v,
p->min, p->max);
return -ERANGE;
}
Expand Down Expand Up @@ -308,7 +309,7 @@ static void load_stored(struct param_set *set)
case TEDGE_PARAM_TYPE_BOOL:
continue;
case TEDGE_PARAM_TYPE_INT:
snprintf(raw, sizeof(raw), "%d", v->v.i);
snprintf(raw, sizeof(raw), "%" PRId32, v->v.i);
break;
case TEDGE_PARAM_TYPE_STRING:
case TEDGE_PARAM_TYPE_ENUM:
Expand Down Expand Up @@ -375,7 +376,7 @@ static void publish(const struct param_set *set)
v->v.b ? "true" : "false");
break;
case TEDGE_PARAM_TYPE_INT:
n = tedge_appendf(json, size, n, "%d", v->v.i);
n = tedge_appendf(json, size, n, "%" PRId32, v->v.i);
break;
case TEDGE_PARAM_TYPE_STRING:
case TEDGE_PARAM_TYPE_ENUM:
Expand Down Expand Up @@ -435,7 +436,7 @@ static int check_declaration(const struct tedge_parameter *params, size_t count)
TEDGE_LOG_ERR("params: '%s': min above max", p->name);
return -EINVAL;
}
snprintf(raw, sizeof(raw), "%d", p->def.i);
snprintf(raw, sizeof(raw), "%" PRId32, p->def.i);
if (check_value(p, raw, reason, sizeof(reason)) != 0) {
TEDGE_LOG_ERR("params: default %s", reason);
return -EINVAL;
Expand Down
30 changes: 30 additions & 0 deletions ports/common/cmake/tedge_ca_bundle.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: Apache-2.0
#
# The trust anchors the core embeds (core/src/tedge_c8y.c includes
# tedge_ca_bundle.inc): every PEM file in a space-separated list,
# concatenated, as comma-separated bytes.
#
# include(<tiny-edge>/ports/common/cmake/tedge_ca_bundle.cmake)
# tedge_ca_bundle(<out_dir> "<files>")
#
# A relative path is relative to the repository. A change to a file
# reconfigures the build.

function(tedge_ca_bundle out_dir files)
get_filename_component(_root ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../.. ABSOLUTE)
separate_arguments(_files UNIX_COMMAND "${files}")
set(_bundle "")
foreach(_pem ${_files})
if(NOT IS_ABSOLUTE ${_pem})
set(_pem ${_root}/${_pem})
endif()
if(NOT EXISTS ${_pem})
message(FATAL_ERROR "TEDGE_C8Y_CA_FILES: no such file: ${_pem}")
endif()
file(READ ${_pem} _content HEX)
string(APPEND _bundle "${_content}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${_pem})
endforeach()
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," _bundle "${_bundle}")
file(CONFIGURE OUTPUT ${out_dir}/tedge_ca_bundle.inc CONTENT "${_bundle}\n")
endfunction()
60 changes: 60 additions & 0 deletions ports/common/include/tedge_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: Apache-2.0 */

/* What the ports share: code for platforms that offer BSD sockets and
* mbedTLS 4 (POSIX, and ESP-IDF's lwIP), and what it needs from each
* platform. Not part of the port interface, nor of the public API.
*
* - tedge_sockets.c implements the connections and TLS of
* <tedge/port/net.h>: everything but the interface queries
* (tedge_port_net_is_up, _watch, _is_own, _on_subnet), which differ by
* platform.
* - tedge_mbedtls_key.c implements <tedge/port/crypto.h> but
* tedge_port_random(): the device key, generated with PSA and kept as
* PEM where the platform keeps it, and the CSR signed with it.
*/

#ifndef TEDGE_COMMON_H_
#define TEDGE_COMMON_H_

#include <mbedtls/pk.h>

#include <stddef.h>

/* Several client threads hold TLS sessions at once, and PSA's key slots
* and random generator are shared between them. */
#if !defined(MBEDTLS_THREADING_C)
#error "tiny-edge needs mbedTLS built with MBEDTLS_THREADING_C"
#endif

/* --- Provided by the shared code ----------------------------------------- */

/** Start PSA crypto, once (tedge_sockets.c). @return 0, or -EIO. */
int tedge_common_psa_init(void);

/** Load the device key into @p pk (initialised by the caller); only in
* builds with enrollment (tedge_mbedtls_key.c).
* @return 0; -ENOENT when there is no key yet; or another negative errno. */
int tedge_common_device_pk(mbedtls_pk_context *pk);

/* --- Provided by the platform -------------------------------------------- */

/**
* Read the stored device key, PEM, into @p pem.
*
* @return Its length (which may be more than @p cap: then it did not fit);
* -ENOENT when there is none; or another negative errno.
*/
int tedge_common_key_load(char *pem, size_t cap);

/** Store the device key, @p len bytes of PEM, readable by the device only.
* @return 0, or a negative errno. */
int tedge_common_key_store(const char *pem, size_t len);

/** Where the key is kept, for log lines ("/var/lib/x/device-key.pem"). */
const char *tedge_common_key_where(void);

/** A socket was opened for the client: set what the platform needs on
* every one (close-on-exec, no SIGPIPE). It is non-blocking already. */
void tedge_common_socket_opened(int fd);

#endif /* TEDGE_COMMON_H_ */
138 changes: 138 additions & 0 deletions ports/common/src/tedge_mbedtls_key.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* SPDX-License-Identifier: Apache-2.0
*
* The device key of <tedge/port/crypto.h> with mbedTLS 4 and PSA, for any
* platform that keeps the key as PEM in its own storage (a file on POSIX,
* NVS on ESP-IDF: tedge_common_key_load() and _store()). The key is
* generated once in PSA, written out, and parsed again for each use.
*
* The platform adds tedge_port_random().
*/

#include <tedge/port/crypto.h>
#include <tedge/port/log.h>
#include <tedge/port/os.h>

#include "tedge_common.h"

#include <mbedtls/pk.h>
#include <mbedtls/platform_util.h>
#include <mbedtls/x509_csr.h>
#include <psa/crypto.h>

#include <errno.h>
#include <string.h>

TEDGE_LOG_DECLARE();

/* A P-256 key in PEM is about 230 bytes. */
#define KEY_PEM_MAX 512

int tedge_common_device_pk(mbedtls_pk_context *pk)
{
unsigned char pem[KEY_PEM_MAX];
int n, ret = tedge_common_psa_init();

if (ret != 0) {
return ret;
}
n = tedge_common_key_load((char *)pem, sizeof(pem) - 1);
if (n < 0) {
return n;
}
if ((size_t)n >= sizeof(pem)) {
return -EFBIG;
}
pem[n] = '\0';
/* PEM is parsed with its NUL. */
ret = mbedtls_pk_parse_key(pk, pem, (size_t)n + 1, NULL, 0);
mbedtls_platform_zeroize(pem, sizeof(pem));
if (ret != 0) {
TEDGE_LOG_ERR("the device key (%s) is not usable (-0x%04x)",
tedge_common_key_where(), (unsigned int)-ret);
return -EINVAL;
}
return 0;
}

int tedge_port_device_key_ensure(void)
{
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
unsigned char pem[KEY_PEM_MAX];
mbedtls_pk_context pk;
psa_key_id_t id = 0;
psa_status_t st;
int ret;

mbedtls_pk_init(&pk);
ret = tedge_common_device_pk(&pk);
mbedtls_pk_free(&pk);
if (ret != -ENOENT) {
return ret; /* 0: generated on an earlier start */
}

/* Generated in PSA, written out once as PEM, then forgotten by PSA:
* the platform's storage is the key's only home. */
psa_set_key_type(&attr, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
psa_set_key_bits(&attr, 256);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH |
PSA_KEY_USAGE_SIGN_MESSAGE |
PSA_KEY_USAGE_EXPORT);
psa_set_key_algorithm(&attr, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
st = psa_generate_key(&attr, &id);
psa_reset_key_attributes(&attr);
if (st != PSA_SUCCESS) {
TEDGE_LOG_ERR("generating the device key failed (%d)", (int)st);
return -EIO;
}
mbedtls_pk_init(&pk);
ret = mbedtls_pk_copy_from_psa(id, &pk);
if (ret == 0) {
ret = mbedtls_pk_write_key_pem(&pk, pem, sizeof(pem));
}
mbedtls_pk_free(&pk);
(void)psa_destroy_key(id);
if (ret != 0) {
TEDGE_LOG_ERR("exporting the device key failed (-0x%04x)",
(unsigned int)-ret);
return -EIO;
}
ret = tedge_common_key_store((const char *)pem, strlen((char *)pem));
mbedtls_platform_zeroize(pem, sizeof(pem));
if (ret != 0) {
TEDGE_LOG_ERR("could not store the device key in %s (%d)",
tedge_common_key_where(), ret);
return ret;
}
TEDGE_LOG_INF("device key generated (P-256, %s)", tedge_common_key_where());
return 0;
}

int tedge_port_device_csr(const char *subject, char *pem, size_t cap)
{
mbedtls_x509write_csr csr;
mbedtls_pk_context pk;
int ret;

mbedtls_x509write_csr_init(&csr);
mbedtls_pk_init(&pk);
ret = tedge_common_device_pk(&pk);
if (ret != 0) {
goto out;
}
mbedtls_x509write_csr_set_md_alg(&csr, MBEDTLS_MD_SHA256);
mbedtls_x509write_csr_set_key(&csr, &pk);
ret = mbedtls_x509write_csr_set_subject_name(&csr, subject);
if (ret == 0) {
ret = mbedtls_x509write_csr_pem(&csr, (unsigned char *)pem, cap);
}
if (ret != 0) {
TEDGE_LOG_ERR("writing the CSR failed (-0x%04x)", (unsigned int)-ret);
ret = -EIO;
goto out;
}
ret = (int)strlen(pem);
out:
mbedtls_pk_free(&pk);
mbedtls_x509write_csr_free(&csr);
return ret;
}
Loading
Loading