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