cyclone-runtime-rust 1.0.1

Rust reference runtime for the Cyclone binary wire format: Writer, Reader, DecodeError.
Documentation
  • Coverage
  • 100%
    22 out of 22 items documented7 out of 7 items with examples
  • Size
  • Source code size: 76.11 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 590.12 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 2s Average build duration of successful builds.
  • all releases: 2s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • cyclone-protocol/runtime-rust
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • Thang-HaDuy

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 InvalidEnum error. Which u32 values an enum admits is schema knowledge, so the generated codec validates it - the runtime never could.
  • There are no blanket impls of Encode / Decode for u32, String, Vec<T> or any other Rust type. Vec<T> is an Array<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 cyclone_runtime::{from_bytes, to_bytes, Decode, DecodeError, Encode, Reader, Writer};

struct Item { id: u32, name: String }

// What a codec looks like, whoever wrote it:
impl Encode for Item {
    fn encode(&self, writer: &mut Writer) {
        writer.write_u32(self.id);
        writer.write_string(&self.name);
    }
}

impl Decode for Item {
    fn decode(reader: &mut Reader<'_>) -> Result<Self, DecodeError> {
        Ok(Item {
            id: reader.read_u32()?,
            name: reader.read_string()?,
        })
    }
}

let bytes = to_bytes(&Item { id: 42, name: "Sword".to_owned() });
assert_eq!(bytes, [0x2A, 0, 0, 0, 0x05, 0, 0, 0, b'S', b'w', b'o', b'r', b'd']);

let item = from_bytes::<Item>(&bytes)?;

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 = Reader::new(&bytes);
let item = Item::decode(&mut reader)?;
assert!(reader.is_empty(), "trailing bytes: the two ends disagree about the schema");

For arrays, the runtime writes the count and the codec writes the elements:

writer.write_array_count(items.len());
for item in items { item.encode(writer); }
let count = reader.read_array_count()?;
let mut items = Vec::new();
for _ in 0..count { items.push(Item::decode(reader)?); }

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 cyclone_runtime::{Limits, Reader};

let reader = Reader::with_limits(bytes, Limits {
    max_string_len: 1024 * 1024,
    max_bytes_len: 1024 * 1024,
    max_array_count: 100_000,
});

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