# telepath-server
RPC **server** library for embedded targets (`no_std`, no alloc).
Target-side Telepath RPC server. Runs on the MCU in `#![no_std]` mode.
Receives COBS-framed postcard requests from the host, dispatches them to
registered command handlers, and sends COBS-framed postcard responses back.
## Architecture
```mermaid
flowchart LR
T[Transport] -->|bytes| FA[FrameAccumulator]
FA -->|COBS frame| CD[cobs_decode]
CD -->|raw bytes| PD["postcard::from_bytes"]
PD -->|typed args| D[dispatch]
D -->|result| PS["postcard::to_slice"]
PS -->|raw bytes| CE[cobs_encode]
CE -->|COBS frame| T
```
## Key API
### `TelepathServer<T, N>`
| `new(transport, commands)` | Create a server with a transport and a static command slice |
| `poll()` | Drain available bytes, process any complete frames; call in a loop |
| `dispatch(cmd_id, input, output)` | Manually dispatch a decoded payload (useful for testing) |
| `find_command(id)` | Look up a command by ID (linear scan over the static slice) |
`T` must implement `transport::Transport`. `N` is the internal buffer
size; use `512` or larger to accommodate max-payload frames.
### `transport::Transport` trait
```rust
pub trait Transport {
fn read(&mut self, buf: &mut [u8]) -> usize;
fn write(&mut self, buf: &[u8]) -> usize;
}
```
Both methods are non-blocking and return the number of bytes transferred.
### `CommandMetadata`
```rust
pub struct CommandMetadata {
pub name: &'static str,
pub id: u16,
pub invoke: ShimFn, // fn(&[u8], &mut [u8], &ResourceRegistry) -> Result<usize, DispatchError>
pub args_schema: SchemaFn, // fn(&mut [u8]) -> Result<usize, ()>
pub ret_schema: SchemaFn, // fn(&mut [u8]) -> Result<usize, ()>
pub arg_names: &'static str, // comma-separated, e.g. "a,b"
}
```
For details on the generated `invoke`, `args_schema`, and `ret_schema` functions,
see [telepath-macros § Signature contract](../telepath-macros/README.md#signature-contract).
Register commands by passing a `&'static [CommandMetadata]` to `new()`.
Use `telepath_server::commands()` (linkme-collected at link time) to
pass all `#[command]`-annotated functions automatically.
### `ResourceRegistry`
Type-keyed container for `#[resource]`-injected values. Re-exported as
`telepath_server::ResourceRegistry`. Each resource type may appear at most
once; registering a second value of the same type panics at runtime
(fail-fast to prevent silent shadowing). Resources are added via the
`TelepathServer::resource(value)` builder method — see Usage below.
## Usage
```rust
use telepath_server::{command, TelepathServer};
#[command]
fn ping() -> u32 { 0xDEAD_BEEF }
// Resource args are injected from the server's ResourceRegistry, not over the wire.
#[command]
fn set_led(#[resource] led: &mut MyLed, on: bool) -> bool { led.set(on) }
let mut server = TelepathServer::<MyTransport, 512>::new(
transport,
telepath_server::commands(),
)
.resource(MyLed::new(pin)); // register each #[resource] type before polling
loop {
server.poll();
}
```
> **Note:** The `#[command]` macro generates a shim that calls
> `postcard::from_bytes` / `postcard::to_slice` directly. Your firmware
> crate must add `postcard` as a direct dependency in its `Cargo.toml`
> in addition to `telepath-server`.
## Build
This crate targets both native (for tests) and `thumbv7em-none-eabi` (for firmware).
```
# Host tests
cargo test -p telepath-server
# Cross-compiled (requires the target to be added)
rustup target add thumbv7em-none-eabi
cargo build -p telepath-server --target thumbv7em-none-eabi
```
## Limitations
- Upstream (target → host) framing uses COBS in this MVP. rzCOBS is planned
for Stage C (see [#76](https://github.com/tarotene/telepath/issues/76)).