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]
MessageIduniquely identifies the logical message a fragment belongs to.FragmentIndexrecords the fragment's zero-based order.FragmentHeaderbundles the identifier, index, and a boolean that signals whether the fragment is the last one in the sequence.FragmentSeriestracks 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");