cyclone-runtime
Rust Reference Implementation of the runtime layer of the Cyclone binary wire format.
User Model → Cyclone Compiler / Derive → Generated Codec → cyclone-runtime → Bytes
This crate is the last step and nothing else. It turns primitives into bytes and bytes back into primitives, exactly as RFC-0002 specifies.
Scope
Provides
| Item | Role |
|---|---|
Writer |
appends primitives, strings and byte blobs to a Vec<u8> |
Reader<'a> |
reads them back from a borrowed slice, rejecting malformed input |
Limits |
allocation guards for untrusted input |
DecodeError |
every way a byte stream can fail to conform |
Encode / Decode |
the traits a codec implements |
to_bytes / from_bytes |
the two calls that use those traits |
Does not provide - and will not: schema parsing, annotation reading, codec
generation, proc macros, reflection, a registry, a dynamic resolver, runtime
type discovery, or a serialization framework. No dependency outside std.
Three consequences worth stating up front:
- There is no
write_model/read_model. A model is its fields, written back to back in declaration order with nothing in between (RFC-0002 §5). - There is no
InvalidEnumerror. Whichu32values an enum admits is schema knowledge, so the generated codec validates it - the runtime never could. - There are no blanket impls of
Encode/Decodeforu32,String,Vec<T>or any other Rust type.Vec<T>is anArray<T>only because a schema said so, and a runtime that guessed it from the Rust type would be deciding the wire format by inference.
Where the impls come from
The runtime cannot tell, and does not care:
| Route | What it is |
|---|---|
cyclone-codegen-rust |
#[derive(Network)] writes the impls in place |
cyclone-cli |
the official compiler writes them into a *.codec.rs file you own |
| by hand | equally valid |
Usage
use ;
// What a codec looks like, whoever wrote it:
let bytes = to_bytes;
assert_eq!;
let item = ?;
to_bytes and from_bytes add no framing of their own - they construct a
Writer / Reader and call the trait. Drive those types directly when several
values share one buffer, when a pre-sized buffer avoids reallocation, or when
you need Reader::with_limits.
from_bytes decodes one value and returns it; leftover bytes are not an
error. A stream that must end exactly at the value (RFC-0002 §9) needs the check
made explicitly:
let mut reader = new;
let item = decode?;
assert!;
For arrays, the runtime writes the count and the codec writes the elements:
writer.write_array_count;
for item in items
let count = reader.read_array_count?;
let mut items = Vecnew;
for _ in 0..count
Wire format at a glance
| Type | Bytes |
|---|---|
bool |
1 - 0x00 or 0x01, nothing else |
i8 / u8 |
1 |
i16 / u16 |
2, Little Endian |
i32 / u32 |
4, Little Endian |
i64 / u64 |
8, Little Endian |
f32 / f64 |
4 / 8 - raw IEEE 754 bits, never normalized |
String |
u32 UTF-8 byte length, then the bytes |
Bytes |
u32 length, then the raw bytes |
Array<T> |
u32 element count, then each element |
Enum |
always u32 |
Model |
its fields concatenated in declaration order |
No varint, no padding, no alignment, no tag id, no object header.
Decoding untrusted input
Reader::new applies Limits::UNLIMITED (u32::MAX everywhere) - the wire
format's own ceiling and nothing tighter. Anything reading from the network
should lower it:
use ;
let reader = with_limits;
Two bounds are enforced, and every length is checked before anything is allocated:
| Bound | Condition | Error | Nature |
|---|---|---|---|
| Byte stream | length > remaining bytes |
UnexpectedEof |
normative - always rejected |
| Configuration | length > Limits::… |
LengthOverflow |
per-peer, not part of the protocol |
The configured limit is additive: no configuration can permit a length larger than the bytes actually remaining (RFC-0002 §10.1).
Decoding never panics on malformed input, and a failed read leaves the cursor
where it was - an error cannot desynchronize a caller that inspects it and
carries on. #![forbid(unsafe_code)].
Conformance
tests/conformance.rs runs the RFC-0003 vectors, each test named for the IDs it
covers: §3 primitives, §4 float (including NaN payload preservation and
-0.0), §5 string, §6 bytes, §7 array, §8 model and enum, §9 rejects, §10
round-trip. tests/codec.rs covers the Encode / Decode traits and the two
helpers.
cargo test
The two vectors excluded are N-040 and N-041 - enum values outside the declared set. They depend on schema knowledge and belong to the generated codec.
References
- RFC-0001 - What Cyclone is
- RFC-0002 - Wire Format Specification
- RFC-0003 - Conformance