Caio's Stuff
ESP32 Server: Distributing HTTP/2 streams over TLS
Intended to showcase the possibilities of the WTX project rather than serve as a production tool. In regards to the exchange of remote data most embedded devices resort to MQTT.
Deploying program and serving responses
The last post about embedded is outdated: The WTX crate introduced breaking changes, the Embassy project introduced breaking changes and the ESP crates introduced breaking changes.
This post will probably also become outdated in the following years but it will at least deliver yet another never-done-before use-case: Make an ESP32 board process HTTP/2 requests over TLS 1.3 using Rust.
Well, at least I am not aware of anyone else that did this before so let me know if I am mistaken.
Topics are organized as introductory explanations followed by code comments. The full project is available at data.tar.xz.
Technologies
Receiving flow
Board
This project is implemented on the ESP32-WROVER-E, a 32-bit dual-core development board with Wi-Fi, 448 KiB of ROM, 520 KiB of RAM, Bluetooth, 4 MiB of Flash, built-in PCB antenna, 40 different pins, a camera and several other features. Quite affordable considering the place where I live.
Broadly speaking, ESP32 is a series of low-cost, low-power system-on-chip microcontrollers created and developed by Espressif Systems, a Chinese company based in Shanghai.
ESP crates
The embedded world is generally dominated by C toolchains but thankfully we are now witnessing a growing shift towards Rust-based alternatives. Moreover, Espressif Systems provides official libraries, documentation and tools that can help creating, compiling, debugging and deploying binaries.
Taking advantage of the official Rust crates, we are going to use them to basically communicate with the hardware.
Bootstrap the application.
Connect to a local network using the Wi-Fi.
Generate random seeds suitable for cryptographic operations.
Reserve memory for heap allocations.
Embassy
Embassy is a modern embedded framework offering high-level async interfaces for bare-metal hardware. Here we use embassy-executor, embassy-net and embassy-time to manage the runtime, TCP streams and elapsed time.
WTX
WTX is, among other things, a RFC5280, RFC6455, RFC9113 and RFC9846 implementation intended to allow the development of web applications through a built-in server framework, a built-in PostgreSQL connector, a built-in WebSocket handler and a built-in gRPC manager. There is also a built-in API client framework that facilitates the maintainability of large endpoints.
Some associated benchmarks are available at https://github.com/MDA2AV/HttpArena, https://github.com/diesel-rs/metrics and https://c410-f3r.github.io/wtx-bench.
Certificates
This section is mandatory! Without a custom certificate the server will not work on local networks!
WTX has a strict set of X.509 rules derived from the x509-limbo testsuite that prevents the utilization of files created from a default OpenSSL CLI template, as such, it is necessary to provide additional information to forge certificates.
The recurring stumbling block is that Intermediate CAs or Root CAs need a Subject Key Identifier and if an Authority Key Identifier extension exists in a self-signed certificate, it must match the SKI.
CERTS_DIR="$(dirname $0)/../.certs"
openssl genpkey -algorithm ed25519 -out $CERTS_DIR/key.pem<br>openssl req -new -key $CERTS_DIR/key.pem -subj "/C=FI/CN=vahid" -out $CERTS_DIR/key.csr<br>openssl genpkey -algorithm ed25519 -out $CERTS_DIR/root-ca.key<br>openssl req -x509 -sha256 -days 3650 -subj "/C=FI/CN=vahid Root CA" \<br>-key $CERTS_DIR/root-ca.key \<br>-out $CERTS_DIR/root-ca.crt \<br>-addext "authorityKeyIdentifier=keyid:always,issuer" \<br>-addext "basicConstraints=critical,CA:TRUE" \<br>-addext "subjectKeyIdentifier=hash"<br>cat 'EOF' > $CERTS_DIR/localhost.ext<br>authorityKeyIdentifier=keyid,issuer<br>basicConstraints=CA:FALSE<br>subjectAltName = @alt_names<br>subjectKeyIdentifier=hash<br>[alt_names]<br>DNS.1 = localhost<br>IP.1 = 127.0.0.1<br>IP.2 = BOARD_IP<br>EOF<br>openssl x509 -req -CA $CERTS_DIR/root-ca.crt -CAkey $CERTS_DIR/root-ca.key \<br>-in $CERTS_DIR/key.csr -out $CERTS_DIR/cert.pem \<br>-days 398 -CAcreateserial -extfile $CERTS_DIR/localhost.ext<br>cat $CERTS_DIR/cert.pem $CERTS_DIR/root-ca.crt > $CERTS_DIR/fullchain.pem<br>rm $CERTS_DIR/key.csr<br>rm $CERTS_DIR/localhost.ext<br>rm $CERTS_DIR/root-ca.srl<br>Replace BOARD_IP with the IP your board gets at runtime or switch from DHCP to a static IP.
You can find a longer explanation about the additional files that are required to make the code build in the last post about embedded or dive into the project's directory. For a quick review, .cargo/config.toml, rust-toolchain and build.rs demand special handling.
Code comments
Just a few notes to clarify what is going on. In the end the application will listen to connections on port 9000 to deliver a simple "Hello World" from GET requests.
Initialization
According to esp-hal, esp_hal::rng::Rng produces random...