# USMP — Unified Secure Multi-transport Protocol
Lightweight, mutually authenticated, AES-256-GCM encrypted communication for ESP32.
No TLS stack. No certificates. Three function calls.
```bash
idf_component_manager add metaloomlabs/usmp
```
---
## Why USMP
Full TLS is 60–100 KB of flash and significant RAM. Raw TCP has no security.
USMP sits in between: a 4-message handshake gives you mutual authentication and a fresh session key, then every DATA frame is AES-256-GCM encrypted with replay protection. The entire C core has zero platform dependencies.
**Guarantees — no insecure mode:**
- Mutual authentication (HMAC-SHA256 + PSK, both sides verify)
- Forward secrecy (X25519 ephemeral key exchange per session)
- Encryption (AES-256-GCM, mandatory)
- Replay protection (monotonic sequence numbers)
---
## Requirements
- ESP-IDF v5.0 or later
- Python counterpart: `pip install usmp` (the `USMPServer` / `USMPClient` gateway)
---
## Installation
**Via IDF Component Manager (recommended):**
```bash
idf.py add-dependency "metaloomlabs/usmp"
```
**Manual:** clone the repo and add `ports/usmp-esp32` as a component.
---
## Quickstart
### 1. Configure `sdkconfig` / `menuconfig`
No extra Kconfig entries required. USMP uses standard ESP-IDF `esp_wifi`, `lwip`, and `mbedtls` (only for AES-GCM and SHA-256 primitives — no TLS handshake).
### 2. Implement the five port hooks in your project
Create `usmp_port_impl.c`:
```c
#include "usmp_port.h"
#include "esp_random.h"
#include "esp_timer.h"
#include "esp_log.h"
#include <string.h>
static const uint8_t DEVICE_ID[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01};
void usmp_port_get_device_id(uint8_t out[6]) {
memcpy(out, DEVICE_ID, 6);
}
void usmp_port_random(uint8_t *buf, size_t len) {
esp_fill_random(buf, len);
}
void usmp_port_delay_ms(uint32_t ms) {
vTaskDelay(pdMS_TO_TICKS(ms));
}
uint32_t usmp_port_millis(void) {
return (uint32_t)(esp_timer_get_time() / 1000ULL);
}
void usmp_port_log(const char *msg) {
ESP_LOGI("USMP", "%s", msg);
}
```
### 3. Connect and communicate
```c
#include "usmp.h"
#include "usmp_transport.h" // TCP transport
static const char PSK[] = "usmp-dev-psk-change-me-before-prod";
void app_main(void) {
// ... WiFi connect ...
usmp_transport_t transport;
usmp_tcp_transport_init(&transport, "192.168.1.100", 9000);
usmp_ctx_t ctx;
usmp_config_t cfg = {
.psk = (const uint8_t *)PSK,
.psk_len = strlen(PSK),
.keepalive_ms = 5000,
};
if (usmp_connect(&ctx, &transport, &cfg) != USMP_OK) {
ESP_LOGE("APP", "Handshake failed");
return;
}
// Send
uint8_t msg[] = "hello";
usmp_send(&ctx, msg, sizeof(msg) - 1);
// Receive (blocking, with timeout)
uint8_t buf[256];
int n = usmp_recv(&ctx, buf, sizeof(buf), 3000);
if (n > 0) {
ESP_LOGI("APP", "Got %d bytes", n);
}
// Keepalive / reconnect loop
while (true) {
usmp_keepalive_tick(&ctx);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
```
### 4. Run the Python gateway
```bash
pip install usmp
```
```python
import asyncio
from usmp import USMPServer
PSK = b"usmp-dev-psk-change-me-before-prod"
async def on_data(session, data):
print(f"[{session.session_id.hex()}] got: {data}")
await session.send(data) # echo back
async def main():
server = USMPServer(host="0.0.0.0", port=9000, psk=PSK, on_data=on_data)
await server.start()
await asyncio.Event().wait()
asyncio.run(main())
```
---
## Handshake overview
```py
Device Server
│── HELLO (device_id, pub_C) ──►│
│◄─ CHALLENGE (nonce, pub_S) ───│
│── HELLO_ACK (HMAC) ──────────►│
│◄─ SESSION_OK (session_id) ────│
↓ session established ↓
│══ DATA (AES-256-GCM) ════════►│
│◄═ DATA (AES-256-GCM) ════════│
```
Session key derivation: `HKDF-SHA256(X25519(priv_C, pub_S), salt=nonce, info="usmp-v1"||pub_C||pub_S)`
---
## Packet types
| Value | Name | Direction |
|-------|------------|-----------------|
| 0x01 | HELLO | Device → Server |
| 0x02 | CHALLENGE | Server → Device |
| 0x03 | HELLO_ACK | Device → Server |
| 0x04 | SESSION_OK | Server → Device |
| 0x05 | DATA | Both |
| 0x06 | PING | Both |
| 0x07 | PONG | Both |
| 0x08 | BYE | Both |
| 0xFF | ERROR | Both |
---
## Transport abstraction
`usmp_transport_t` is a struct of five function pointers (`send`, `recv`, `close`, `reconnect`, `available`). TCP over Wi-Fi is provided. UART with COBS framing is planned for v0.4.0.
---
## Roadmap
| Version | Feature |
|---------|---------|
| v0.2.x | ✅ TCP/Wi-Fi, mutual auth, AES-256-GCM, Python SDK, 61 tests |
| v0.3.0 | ESP-IDF Component Registry + PlatformIO publish |
| v0.4.0 | UART transport (COBS framing) |
| v0.5.0 | Discovery CLI + mDNS |
| v0.6.0 | OTA firmware (Ed25519 signed, atomic swap) |
| v1.0.0 | Cloud bridge, Arduino Library Manager |
---
## License
MIT — see [LICENSE](https://github.com/metaloomlabs/usmp/blob/main/LICENSE)
0ced1de00193a83a78233b4d6427d5bf8a88d53d
idf.py add-dependency "metaloomhq/usmp^0.2.5"