# xscp
[](https://crates.io/crates/xscp)
[](https://docs.rs/xscp)
[](#license)
A minimal, zero-dependency Rust implementation of the **XSCP** (XSCP Stream Communication Protocol): a small, line-oriented, text-based communication protocol with a strict 512-byte PDU budget.
This crate provides only the **protocol primitives** — request, response and notification PDUs, with safe constructors and parsers. It is transport-agnostic: bring your own TCP, TLS, WebSocket or whatever you like, and use these types to build a client, a server, or anything in between.
## Features
- **Zero dependencies.** The crate compiles against the standard library only — no third-party code in your dependency tree.
- **Safe parsing.** All constructors and parsers reject malformed input and validate field lengths.
- **Anti-smuggling by construction.** Line terminators (`\r\n`) are forbidden inside any field, and the `|` delimiter is forbidden inside header fields (opcode, source), so a hostile payload cannot inject extra PDUs. The trailing `Message` field may contain `|` because parsing splits on the first two pipes only.
- **Borrowed, allocation-light API.** PDU types hold `&str` slices into the original buffer; parsing does not copy your data.
- **Strict wire format.** Every PDU has a documented byte budget and a fixed structure; nothing is left implicit.
## Installation
```sh
cargo add xscp
```
Or in `Cargo.toml`:
```toml
[dependencies]
xscp = "0.1"
```
## Quick start
### Building and parsing a request
```rust
use xscp::request::{XscpRequest, OpCode};
// Build a request programmatically.
let req = XscpRequest::try_new(OpCode::Send, "alice", "hello, world!")?;
assert_eq!(req.opcode(), OpCode::Send);
assert_eq!(req.source(), "alice");
// Parse a request received from the wire.
assert_eq!(parsed.message(), "hello, world!");
# Ok::<(), xscp::request::RequestError>(())
```
### Building and parsing a response
```rust
use xscp::response::XscpResponse;
let res = XscpResponse::try_new(200, "OK")?;
assert_eq!(res.status_code(), 200);
# Ok::<(), xscp::response::ResponseError>(())
```
### Building and parsing a notification
```rust
use xscp::notification::{XscpNotification, NotificationType};
let note = XscpNotification::try_new(
NotificationType::Broadcast,
"alice",
"hello, everyone!",
)?;
# Ok::<(), xscp::notification::NotificationError>(())
```
## Protocol overview
XSCP is a line-oriented, UTF-8, pipe-delimited protocol. Every PDU ends with `\r\n` and is at most **512 bytes** (responses are at most **36 bytes**).
### Request PDU
```text
+------------------------------------------------------------------+
| Message (Max 472 Bytes) + \r\n (2 Bytes) |
+------------------------------------------------------------------+
```
| Login | `LOGN` | User registration |
| Send | `SEND` | Global message broadcast |
| Exit | `EXIT` | Graceful disconnection |
### Response PDU
```text
+-----------------------------------------------------------------------+
```
Status codes follow an HTTP-like convention (numeric, ≤ 599); reason phrases are short, human-readable strings.
### Notification PDU
```text
+---------------------------------------------------------------------------+
| Message (Max 472 Bytes) + \r\n (2 Bytes) |
+---------------------------------------------------------------------------+
```
| Broadcast | `BRDC` | Message relayed to all users |
The `Source` field contains either a hostname, a user nickname, or the literal string `XSCP_SERVER` for server-originated notifications.
## Security notes
XSCP fields are validated at construction and parse time:
- `\r` and `\n` are rejected inside any field.
- `|` is rejected inside the opcode, source and reason phrase fields. It is **allowed** inside the `Message` field of requests and notifications: parsing uses `splitn(3, '|')`, so only the first two pipes act as delimiters and everything after is preserved verbatim as the message.
- Sources must be 3–32 bytes; messages must not exceed 472 bytes; reason phrases must not exceed 32 bytes.
This makes **PDU smuggling** (a hostile payload terminating its own PDU early to inject another) impossible by construction — a smuggled payload would need to embed `\r\n`, which is forbidden in every field. As a consumer of the crate you still need to enforce the 512-byte read budget on the transport side.
## Use cases
This crate is the protocol layer; it does no I/O. Typical uses:
- Implement an XSCP **client** on top of `std::net::TcpStream`, `tokio`, `async-std`, …
- Implement an XSCP **server** that accepts requests and emits responses and broadcast notifications.
- Build bridges, proxies, fuzzers or test fixtures for XSCP-speaking software.
A reference client and server live in the [`client/`](https://github.com/ivan-amon/xscp/tree/main/client) and [`server/`](https://github.com/ivan-amon/xscp/tree/main/server) crates of the workspace and are **not** published to crates.io.
## Minimum Supported Rust Version
The crate is built against Rust **2024 edition**. Bumps to the MSRV are not considered breaking changes.
## License
Licensed under the [MIT license](LICENSE).
## Contributing
Issues and pull requests are welcome at <https://github.com/ivan-amon/xscp>.