rusty_esp_core: The Shared Vocabulary Every Janus ESP32 Speaks
rusty_esp_core gives every Janus ESP32 one grammar: borrowed frames, PCM blocks, timestamps, a signed capability manifest and clock, rng and kv seams. No C.

What is rusty_esp_core?
rusty_esp_core is the Layer 0 crate of the Janus ESP32 family: the shared types every package speaks at its boundary. Borrowed media frames, PCM blocks, monotonic timestamps, one Copy error, a signed capability manifest, and Clock, Rng and Kv seams. It is no_std by default, forbids unsafe code, and has no C.
rusty_esp_core is the shared vocabulary of the Janus ESP32 family: the handful of no_std types every package speaks at its boundary. It defines borrowed media frames, PCM blocks, monotonic timestamps, one Copy error, a signed capability manifest with an honesty rule, and three hardware seams (clock, entropy and key-value store) that every backend fills in. It has no drivers, no allocator, no product types, no C and no FFI, and it is compiled with forbid(unsafe).
Because every camera, microphone and radio package builds on rusty_esp_core, it is the reason a camera frame can travel from sensor to network without being converted or copied, and the reason a home computer can trust what a device says about itself. The source is at github.com/Remade-With-Rust/rusty_esp_core.
Why every device agrees on one grammar
Layer 0, and the rule that keeps it small
Janus is built in layers, and the dependency direction never reverses. rusty_esp_core is Layer 0; nothing in the family sits beneath it. The function packages for video, audio, images, identity and the mesh all sit on top.
The rule is short. A type belongs in rusty_esp_core only if a package needs it at its boundary, and only if it can be defined without esp-hal, ESP-IDF, serde or a codec crate. If two packages want to share a type and it is not here, the answer is to add it here, never to wire one function package into another. That keeps the graph flat, so a maker can pick the audio package without dragging in the video one.
What that buys a household is quiet compatibility. A microphone from one maker and a camera from another hand the home computer the same frame, the same timestamp and the same kind of manifest, because both learned the same grammar.
One error that crosses every boundary
Rust crates usually grow their own error types, and converting between them gets heavy on a microcontroller. rusty_esp_core uses a single Copy enum with no String inside it: Unsupported, BufferTooSmall, InvalidGeometry, Timeout, Denied, Corrupt and a few more. A no_std driver and a std service return the same type, so an error can cross from firmware to the mesh without a conversion chain. Packages wrap it for detail, but the boundary stays the same.
Time is equally plain. Micros is a u64 count of microseconds since boot, carried as a value rather than an atomic, because 32-bit targets lack 64-bit atomics. There is no wall clock on the device. The host maps device time to wall time once per session, and an unmeasured offset reports an infinite error, never a silent zero.
Borrowed frames, PCM blocks and the manifest
Frames are views, not owners
A frame in rusty_esp_core is a validated window onto memory the caller already has: a DMA ring, a static arena, or a Vec in a laptop test. The FFmpeg-shaped model, where every frame owns a vector of plane vectors, costs one or more heap allocations per frame. On a part with 512 KB of internal SRAM that adds up fast.
Frame<'a> checks size, stride and format once, in its constructor, so consumers can index it without re-checking and without unsafe. A JPEG frame must start with the right marker. RGB565 and YUYV, the formats small camera sensors actually produce, are first-class. FrameMut covers in-place work, and PcmBlock<'a> does the same job for audio: interleaved samples that are frame-aligned by construction.
use rusty_esp_core::prelude::*;
let geometry = Geometry::new(320, 240, PixelFormat::Rgb565)?;
let frame = Frame::packed(geometry, clock.now(), seq, &dma_buffer[..])?;
No copy, no allocation. This borrowed-frame rule is why the family can stream a camera on chips a maker can afford.
A signed capability manifest with an honesty rule
Every Janus device describes itself with a Manifest: model, firmware, chip and a list of declared capabilities. Each declaration has a status, available, preview or planned, and a live claim must name the crate that backs it, while a planned one names none. The canonical form is plain text a person can read on a serial console:
janus/1
model=acme/doorbell-2
fw=1.4.0
chip=esp32s3
cap=image.jpeg:available:rusty_esp_image
cap=iroh.relay:planned:
The same facts always produce the same bytes, whatever order they were given in, and the rusty_esp_mid package signs those bytes with the device's own key. A device that claims something it cannot do is making a signed, attributable statement.
Parsing is deliberately strict. The parser accepts exactly the canonical form and re-encodes it to the same bytes. A newer format version or an unknown capability tag is refused as Unsupported, not half read. In testing, 400 generated manifests round-tripped byte-identical and 2,000 corruptions of a real one never caused a panic.
The seams, the build and the status
Clock, rng and kv: traits, not implementations
rusty_esp_core defines three traits and nothing behind them. Clock gives time, Rng gives entropy, and Kv gives persistent storage with keys up to 15 bytes, matching ESP-IDF's NVS limit. The seam does not encrypt; the identity package decides that. On a chip, Rng must be a hardware generator. On a laptop, the test generator is called InsecureTestRng, a name chosen so nobody wires it to a key.
The entropy seam has been checked on silicon: 1,048,576 bytes from an ESP32-S3's hardware generator, judged on five standard statistics beside the operating system's own generator as a control. The chip read 7.999828 bits per byte against the control's 7.999837. It is a smoke test, one part at one temperature, not a certification, and the ledger says so.
Two small companion crates hold decisions once: rusty_esp_alloc pins the allocator for firmware to declare, and rusty_esp_rtos names the RTOS ports. Kernels that packages share, like pixel conversion, moved to rusty_esp_dsp.
no_std, alloc, forbid(unsafe), and where it stands
rusty_esp_core is no_std by default, with alloc and std as features. There is no serde, no allocator and no async in Layer 0. The seams are synchronous on purpose, and async wrappers live where the executor is known.
cargo test --workspace
cargo check -p rusty_esp_core --no-default-features --target riscv32imac-unknown-none-elf
cargo check -p rusty_esp_core --no-default-features --features alloc --target riscv32imac-unknown-none-elf
Status as of the ledger: 26 unit tests and 3 no-panic tests pass, clippy and cargo deny are clean, and both rungs check on riscv32imac, riscv32imafc and xtensa-esp32s3-none-elf. The no-panic gate found one real bug, an overflow in the wall-clock mapping near the top of the clock range, fixed the same day. Every byte format carries a version, and the rule for changing one is that readers move first, a release before any writer. The 1.0.0 freeze is still ahead.
For how these types travel between devices, see the rusty_esp_iroh field note and the Connect vision. The embedded Rust background lives at docs.esp-rs.org and rust-lang.org.
FAQ
Quick answers for builders evaluating this technology.
Why does rusty_esp_core borrow frames instead of owning them?
An ESP32 has about 512 KB of internal SRAM. A model that allocates a new heap buffer per frame costs one or more allocations every frame. A borrowed Frame is a validated view over memory the caller already has, such as a DMA ring, so a camera frame reaches the network with no conversion and no copy.
Does rusty_esp_core work without an allocator?
Yes. It is no_std by default and needs no heap. alloc and std are optional features. It is checked on riscv32imac, riscv32imafc and xtensa-esp32s3-none-elf, with and without alloc.
What is the capability manifest?
A short, line-based text record naming a device's model, firmware, chip and each capability it declares as available, preview or planned. A live claim must name the crate behind it. The same facts always encode to the same bytes, which the device signs with its own identity key.
What are the clock, rng and kv seams?
They are the only way a core crate touches time, entropy and persistent storage. rusty_esp_core defines them as traits; backends on the chip or the laptop fill them in. On a chip Rng must be a hardware generator; the host test generator is named InsecureTestRng so nobody mistakes it for a key source.
Has rusty_esp_core run on real hardware?
It runs inside every Janus profile verified on silicon, and its entropy seam was checked with 1 MB from an ESP32-S3's hardware generator against an operating-system control. The crate's own gates are host and cross-compile checks: 26 unit tests and 3 no-panic tests.