xscp
A minimal, zero-dependency Rust implementation of the XSCP (XSCP Simple Chat Protocol): a small, line-oriented, text-based chat 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, nickname, source), so a hostile payload cannot inject extra PDUs. The trailingMessagefield may contain|because parsing splits on the first two pipes only. - Borrowed, allocation-light API. PDU types hold
&strslices 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
Or in Cargo.toml:
[]
= "0.1"
Quick start
Building and parsing a request
use ;
// Build a request programmatically.
let req = try_new?;
assert_eq!;
assert_eq!;
// Parse a request received from the wire.
let raw = "CHAT|alice|hello, world!\r\n";
let parsed = parse?;
assert_eq!;
# Ok::
Building and parsing a response
use XscpResponse;
let res = try_new?;
assert_eq!;
let parsed = parse?;
assert_eq!;
# Ok::
Building and parsing a notification
use ;
let note = try_new?;
let parsed = parse?;
assert_eq!;
# Ok::
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
+------------------------------------------------------------------+
| OPCODE (4 Bytes) | Nickname (Min 3 Bytes, Max 32 Bytes) |
|------------------------------------------------------------------|
| Message (Max 472 Bytes) + \r\n (2 Bytes) |
+------------------------------------------------------------------+
| OpCode | Wire | Meaning |
|---|---|---|
| Login | LOGN |
User registration |
| Chat | CHAT |
Global message broadcast |
| Exit | EXIT |
Graceful disconnection |
Response PDU
+-----------------------------------------------------------------------+
| Status Code (1-3 ASCII digits) | Reason Phrase (Max 32 Bytes) |
+-----------------------------------------------------------------------+
Status codes follow an HTTP-like convention (numeric, ≤ 599); reason phrases are short, human-readable strings.
Notification PDU
+---------------------------------------------------------------------------+
| Notification Type (4 Bytes) | Source (Min 3 Bytes, Max 32 Bytes) |
|---------------------------------------------------------------------------|
| Message (Max 472 Bytes) + \r\n (2 Bytes) |
+---------------------------------------------------------------------------+
| Notification | Wire | Meaning |
|---|---|---|
| Broadcast | BRDC |
Message relayed to all users |
The Source field is either a user nickname or the literal XSCP_SERVER for server-originated notifications.
Security notes
XSCP fields are validated at construction and parse time:
\rand\nare rejected inside any field.|is rejected inside the opcode, nickname, source and reason phrase fields. It is allowed inside theMessagefield of requests and notifications: parsing usessplitn(3, '|'), so only the first two pipes act as delimiters and everything after is preserved verbatim as the message.- Nicknames and 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/ and 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.
Contributing
Issues and pull requests are welcome at https://github.com/ivan-amon/xscp.