Packets, payloads, and serialization

Updated Nov 29, 2025

Packets drive routing. Implement the Packet trait (or use the bundled Envelope) to expose a message identifier, optional correlation id, and raw payload. PacketParts rebuilds packets after middleware has finished editing frames, and inherit_correlation patches mismatched response identifiers while logging the discrepancy.[^8] Serializers plug into the Serializer trait; WireframeApp defaults to BincodeSerializer but can run any implementation that meets the trait bounds.[^4]

When FrameMetadata::parse succeeds, the framework extracts identifiers from metadata without deserializing the payload. If parsing fails, it falls back to full deserialization.[^9][^6] Application messages implement the Message trait, gaining to_bytes and from_bytes helpers that use bincode with the standard configuration.[^10]

Fragmentation metadata

wireframe::fragment exposes small, copy-friendly structs to describe the transport-level fragmentation state.[^41]

  • MessageId uniquely identifies the logical message a fragment belongs to.
  • FragmentIndex records the fragment's zero-based order.
  • FragmentHeader bundles the identifier, index, and a boolean that signals whether the fragment is the last one in the sequence.
  • FragmentSeries tracks the next expected fragment index and reports mismatches via structured errors.

Protocol implementers can emit a FragmentHeader for every physical frame and feed the header back into FragmentSeries to guarantee ordering before a fully reassembled message is surfaced to handlers. Behavioural tests can reuse the same types to assert that new codecs obey the transport invariants without spinning up a full server.[^42][^43]

The standalone Fragmenter helper now slices oversized payloads into capped fragments while stamping the shared MessageId and sequential FragmentIndex. Each call returns a FragmentBatch that reports whether the message required fragmentation and yields individual FragmentFrame values for serialization or logging. This keeps transport experiments lightweight while the full adapter layer evolves. The helper is fallible—FragmentationError surfaces encoding failures or index overflows—so production code should bubble the error up or log it rather than unwrapping.

use std::num::NonZeroUsize;
use wireframe::fragment::Fragmenter;

let fragmenter = Fragmenter::new(NonZeroUsize::new(512).unwrap());
let payload = [0_u8; 1400];
let batch = fragmenter.fragment_bytes(&payload).expect("fragment");
assert_eq!(batch.len(), 3);

for fragment in batch.fragments() {
    tracing::info!(
        msg_id = fragment.header().message_id().get(),
        index = fragment.header().fragment_index().get(),
        final = fragment.header().is_last_fragment(),
        payload_len = fragment.payload().len(),
    );
}

A companion Reassembler mirrors the helper on the inbound path. It buffers fragments per MessageId, rejects out-of-order fragments, and enforces a maximum assembled size while exposing purge_expired to clear stale partial messages after a configurable timeout. When the final fragment arrives, it returns a ReassembledMessage that can be decoded into the original type.

use std::{num::NonZeroUsize, time::Duration};
use wireframe::fragment::{
    FragmentHeader,
    FragmentIndex,
    MessageId,
    Reassembler,
};

let mut reassembler =
    Reassembler::new(NonZeroUsize::new(512).expect("non-zero capacity"), Duration::from_secs(30));

let header = FragmentHeader::new(MessageId::new(9), FragmentIndex::zero(), true);
let complete = reassembler
    .push(header, [0_u8; 12])
    .expect("fragments accepted")
    .expect("single fragment completes the message");

// Decode when ready:
// let message: MyType = complete.decode().expect("decode");