ssh_stamp/lib.rs
1// SPDX-FileCopyrightText: 2026 Roman Valls Guimera <brainstorm@nopcode.org>
2// SPDX-FileCopyrightText: 2026 Julio Beltran Ortega <jubeormk1@gmail.com>
3// SPDX-FileCopyrightText: 2026 Gabriel Ku Wei Bin <gabriel.ku@fsfe.org>
4// SPDX-FileCopyrightText: 2026 Anthony Tambasco <anthony.tambasco@fastmail.com>
5// SPDX-FileCopyrightText: 2026 Marko Malenic <mmalenic1@gmail.com>
6//
7// SPDX-License-Identifier: GPL-3.0-or-later
8
9//! Platform-agnostic core of `ssh-stamp`.
10//!
11//! Hosts the SSH state machine, configuration handling, and the
12//! [`platform::PlatformServices`] / [`serial::BufferedSerial`] traits that a
13//! per-MCU adapter crate (e.g. `ssh-stamp-esp32`) implements.
14//!
15//! # Architecture
16//!
17//! `ssh-stamp` is firmware that turns a microcontroller into an SSH-accessible
18//! serial bridge. Connect via SSH to the device, and your terminal session is
19//! bridged directly to the device's UART.
20//!
21//! The design separates platform-agnostic logic (this crate) from
22//! platform-specific implementations (port crates like `ssh-stamp-esp32`).
23//! All hardware access flows through traits defined in [`ssh_stamp_hal`] or
24//! through [`platform::PlatformServices`].
25//!
26//! ## Crate structure
27//!
28//! - [`ssh_stamp_hal`] — hardware abstraction traits (`WifiHal`,
29//! `NetworkProviderHal`, `OtaActions`, etc.)
30//! - [`ssh_stamp_esp32`](https://docs.rs/ssh-stamp-esp32) — ESP32 port:
31//! trait implementations, bootable binary, per-target UART pin assignments
32//! - [`ssh_stamp_esp32_boards`](https://docs.rs/ssh-stamp-esp32-boards) —
33//! per-board pin mappings and the board catalog
34//! - [`ssh_stamp_ota`] — SFTP-based OTA update server (TLV header parsing,
35//! chunked flash writes, device reset; includes the `packer` host
36//! utility)
37//!
38//! `ssh-stamp-ota` depends on `ssh-stamp-hal` for [`OtaActions`](ssh_stamp_hal::OtaActions)
39//! and is in turn depended on by `ssh-stamp` for SFTP-based updates.
40//!
41//! ## Repository layout
42//!
43//! Platform-agnostic crates live at the repository root. Everything
44//! specific to one chip manufacturer lives under
45//! `boards/ssh-stamp-<manufacturer>/`, one directory per manufacturer:
46//!
47//! ```text
48//! ssh-stamp/
49//! ├── src/ ssh-stamp (this crate)
50//! ├── ssh-stamp-hal/ hardware abstraction traits
51//! ├── ssh-stamp-ota/ SFTP OTA server and `packer` tool
52//! ├── xtask/ build, flash and test runner
53//! └── boards/
54//! └── ssh-stamp-esp/ Espressif
55//! ├── ssh-stamp-esp32/ port crate: HAL impls and firmware binary
56//! ├── ssh-stamp-esp32-boards/ board support: `boards/*.toml` pin maps
57//! └── ssh-stamp-esp32-hil/ hardware-in-the-loop tests
58//! ```
59//!
60//! A new manufacturer gets its own `boards/ssh-stamp-<manufacturer>/`
61//! directory with the same three crates; see "Adding a new port" in
62//! [`ssh_stamp_hal`].
63//!
64//! ## Key modules
65//!
66//! - [`app`] — entry points [`prepare_ap_config`] and [`run_app`]
67//! - [`handle`] — SSH event handlers (auth, channels, env vars)
68//! - [`serve`] — SSH connection loop
69//! - [`serial`] — UART bridge trait and bridge function
70//! - [`config`] — [`SSHStampConfig`](crate::config::SSHStampConfig) struct and serialization
71//! - [`store`] — Flash load/save/create
72//! - [`platform`] — [`PlatformServices`](crate::platform::PlatformServices) trait (save config, reset, OTA)
73//!
74//! # Hacking
75//!
76//! ## Architectural invariants
77//!
78//! - **`src/` is platform-agnostic.** It must not import `esp-hal`,
79//! `esp-radio`, `esp-storage`, or any platform-specific crate. All hardware
80//! access goes through `ssh-stamp-hal` traits or `PlatformServices`.
81//! - **Peripherals are owned by the state machine**, not globals. UART is an
82//! exclusive resource consumed by the serial bridge once SSH attaches.
83//! - **Dependency graph is acyclic:** `ssh-stamp-hal <- ssh-stamp <-
84//! ssh-stamp-<port>`. `ssh-stamp` must not depend on any port crate.
85//!
86//! ## Adding a new SSH env var handler
87//!
88//! Edit `handle::session_env`. Add a new match arm for the variable name.
89//! Follow the existing pattern: acquire the config lock, apply the change,
90//! set `ctx.config_changed = true`, and call `a.succeed()`.
91//!
92//! ## Configuration
93//!
94//! On first boot (`first_login = true`), the device generates a random SSID
95//! and WPA2 PSK (printed to the serial console) and accepts any SSH
96//! connection. The client provisions a public key via the `SSH_STAMP_PUBKEY`
97//! environment variable. Subsequent connections require that key.
98//!
99//! `WiFi` SSID and PSK can be changed at any time via the `SSH_STAMP_WIFI_SSID`
100//! and `SSH_STAMP_WIFI_PSK` env vars. Changes are persisted to flash and the
101//! device performs a software reset.
102//!
103//! The serial bridge line settings follow the same route through the
104//! `SSH_STAMP_UART_BAUD`, `SSH_STAMP_UART_DATA_BITS`, `SSH_STAMP_UART_PARITY`
105//! and `SSH_STAMP_UART_STOP_BITS` env vars, defaulting to 115200 8N1.
106//!
107//! ## Testing
108//!
109//! Host-side OTA TLV tests:
110//! ```bash
111//! cargo +stable test --package ssh-stamp-ota --target x86_64-unknown-linux-gnu
112//! ```
113//!
114//! Manual testing requires a hardware target, a `WiFi` client, an SSH client,
115//! and a serial device connected to the UART pins for bridge testing.
116//!
117//! [`prepare_ap_config`]: app::prepare_ap_config
118//! [`run_app`]: app::run_app
119//!
120//! ## Build-time configuration
121//!
122//! The heap and buffer sizes below are declared in `build.rs` via `esp-config`
123//! and overridable at build time with the matching environment variable. Note that
124//! these are build time config variables, they are not used at runtime.
125#![doc = include_str!(concat!(env!("OUT_DIR"), "/ssh_stamp_config_table.md"))]
126// `no_std` on device; under `cargo test` the std test harness needs std, same
127// pattern as the `ssh-stamp-ota` crate. `src/` stays platform-agnostic either way.
128#![cfg_attr(not(test), no_std)]
129#![forbid(unsafe_code)]
130#![deny(clippy::mem_forget)]
131#![deny(unused_imports)]
132#![deny(unused_variables)]
133
134extern crate alloc;
135
136pub mod app;
137#[cfg(feature = "can")]
138pub mod can;
139pub mod config;
140pub mod errors;
141pub mod handle;
142pub mod mem_probe;
143pub mod platform;
144pub mod serial;
145pub mod serve;
146pub mod settings;
147pub mod store;