1#![forbid(unsafe_code)]
4
5use sectorsync_core::prelude::{
6 BarrierId, BarrierState, ClientId, CommandEnvelope, CommandId, CommandPriority, ComponentId,
7 ComponentStore, EntityHandle, EntityId, EventId, EventKind, EventPriority, OwnerEpoch,
8 ReplicationPlan, Station, StationEvent, StationId, Tick,
9};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum FrameKind {
14 Replication = 0,
16 CommandAck = 1,
18 Barrier = 2,
20 Command = 3,
22 StationEvent = 4,
24 CommandDispatch = 5,
26}
27
28impl FrameKind {
29 pub const fn from_byte(byte: u8) -> Option<Self> {
31 match byte {
32 0 => Some(Self::Replication),
33 1 => Some(Self::CommandAck),
34 2 => Some(Self::Barrier),
35 3 => Some(Self::Command),
36 4 => Some(Self::StationEvent),
37 5 => Some(Self::CommandDispatch),
38 _ => None,
39 }
40 }
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct ReplicationFrame {
46 pub client_id: ClientId,
48 pub server_tick: Tick,
50 pub entity_count: u32,
52 pub estimated_payload_bytes: u32,
54 pub entities: Vec<EntityDelta>,
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct EntityDelta {
61 pub entity_id: EntityId,
63 pub owner_epoch: OwnerEpoch,
65 pub components: Vec<ComponentDelta>,
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct ComponentDelta {
72 pub component_id: ComponentId,
74 pub version: u64,
76 pub flags: u8,
78 pub bytes: Vec<u8>,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct ReplicationFrameRef<'a> {
85 pub client_id: ClientId,
87 pub server_tick: Tick,
89 pub entity_count: u32,
91 pub estimated_payload_bytes: u32,
93 encoded_entities: &'a [u8],
94 encoded_entity_count: usize,
95}
96
97impl<'a> ReplicationFrameRef<'a> {
98 pub const fn encoded_entity_count(self) -> usize {
100 self.encoded_entity_count
101 }
102
103 pub fn entities(self) -> EntityDeltaRefIter<'a> {
105 EntityDeltaRefIter {
106 cursor: Cursor::new(self.encoded_entities),
107 remaining: self.encoded_entity_count,
108 }
109 }
110
111 pub fn to_owned(self) -> ReplicationFrame {
113 ReplicationFrame {
114 client_id: self.client_id,
115 server_tick: self.server_tick,
116 entity_count: self.entity_count,
117 estimated_payload_bytes: self.estimated_payload_bytes,
118 entities: self.entities().map(EntityDeltaRef::to_owned).collect(),
119 }
120 }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub struct EntityDeltaRef<'a> {
126 pub entity_id: EntityId,
128 pub owner_epoch: OwnerEpoch,
130 encoded_components: &'a [u8],
131 encoded_component_count: usize,
132}
133
134impl<'a> EntityDeltaRef<'a> {
135 pub const fn encoded_component_count(self) -> usize {
137 self.encoded_component_count
138 }
139
140 pub fn components(self) -> ComponentDeltaRefIter<'a> {
142 ComponentDeltaRefIter {
143 cursor: Cursor::new(self.encoded_components),
144 remaining: self.encoded_component_count,
145 }
146 }
147
148 pub fn to_owned(self) -> EntityDelta {
150 EntityDelta {
151 entity_id: self.entity_id,
152 owner_epoch: self.owner_epoch,
153 components: self.components().map(ComponentDeltaRef::to_owned).collect(),
154 }
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub struct ComponentDeltaRef<'a> {
161 pub component_id: ComponentId,
163 pub version: u64,
165 pub flags: u8,
167 pub bytes: &'a [u8],
169}
170
171impl ComponentDeltaRef<'_> {
172 pub fn to_owned(self) -> ComponentDelta {
174 ComponentDelta {
175 component_id: self.component_id,
176 version: self.version,
177 flags: self.flags,
178 bytes: self.bytes.to_vec(),
179 }
180 }
181}
182
183#[derive(Clone, Debug)]
185pub struct EntityDeltaRefIter<'a> {
186 cursor: Cursor<'a>,
187 remaining: usize,
188}
189
190impl<'a> Iterator for EntityDeltaRefIter<'a> {
191 type Item = EntityDeltaRef<'a>;
192
193 fn next(&mut self) -> Option<Self::Item> {
194 if self.remaining == 0 {
195 return None;
196 }
197 let entity = decode_entity_ref(&mut self.cursor)
198 .expect("ReplicationFrameRef validates all entity bytes before iteration");
199 self.remaining -= 1;
200 Some(entity)
201 }
202
203 fn size_hint(&self) -> (usize, Option<usize>) {
204 (self.remaining, Some(self.remaining))
205 }
206}
207
208impl ExactSizeIterator for EntityDeltaRefIter<'_> {}
209
210#[derive(Clone, Debug)]
212pub struct ComponentDeltaRefIter<'a> {
213 cursor: Cursor<'a>,
214 remaining: usize,
215}
216
217impl<'a> Iterator for ComponentDeltaRefIter<'a> {
218 type Item = ComponentDeltaRef<'a>;
219
220 fn next(&mut self) -> Option<Self::Item> {
221 if self.remaining == 0 {
222 return None;
223 }
224 let component = decode_component_ref(&mut self.cursor)
225 .expect("ReplicationFrameRef validates all component bytes before iteration");
226 self.remaining -= 1;
227 Some(component)
228 }
229
230 fn size_hint(&self) -> (usize, Option<usize>) {
231 (self.remaining, Some(self.remaining))
232 }
233}
234
235impl ExactSizeIterator for ComponentDeltaRefIter<'_> {}
236
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
239pub struct ReplicationFrameLimits {
240 pub max_entity_deltas: usize,
242 pub max_components_per_entity: usize,
244 pub max_component_bytes: usize,
246}
247
248impl Default for ReplicationFrameLimits {
249 fn default() -> Self {
250 Self {
251 max_entity_deltas: 256,
252 max_components_per_entity: 16,
253 max_component_bytes: 1024,
254 }
255 }
256}
257
258#[derive(Clone, Debug, Default, PartialEq, Eq)]
260pub struct ComponentSelection {
261 pub component_ids: Vec<ComponentId>,
263}
264
265#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
267pub struct ReplicationFrameBuildStats {
268 pub planned_entities: usize,
270 pub encoded_entities: usize,
272 pub encoded_components: usize,
274 pub skipped_entities_by_limit: usize,
276 pub skipped_entities_by_frame_bytes: usize,
278 pub skipped_components_by_limit: usize,
280 pub skipped_components_by_size: usize,
282}
283
284#[derive(Clone, Debug, PartialEq, Eq)]
286pub struct ReplicationFrameBuild {
287 pub frame: ReplicationFrame,
289 pub stats: ReplicationFrameBuildStats,
291}
292
293#[derive(Clone, Copy, Debug, Default)]
295pub struct ReplicationFrameBuilder {
296 pub limits: ReplicationFrameLimits,
298}
299
300impl ReplicationFrameBuilder {
301 const BINARY_HEADER_BYTES: usize = 1 + 8 + 8 + 4 + 4 + 4;
302 const BINARY_ENTITY_METADATA_BYTES: usize = 8 + 8 + 2;
303 const BINARY_COMPONENT_METADATA_BYTES: usize = 2 + 8 + 1 + 4;
304
305 pub const fn new(limits: ReplicationFrameLimits) -> Self {
307 Self { limits }
308 }
309
310 pub fn binary_capacity_hint(
317 &self,
318 plan: &ReplicationPlan,
319 selection: &ComponentSelection,
320 ) -> usize {
321 let entities = plan.entities.len().min(self.limits.max_entity_deltas);
322 let components = selection
323 .component_ids
324 .len()
325 .min(self.limits.max_components_per_entity);
326 let fixed_bytes =
327 Self::BINARY_HEADER_BYTES.saturating_add(entities.saturating_mul(
328 Self::BINARY_ENTITY_METADATA_BYTES.saturating_add(
329 components.saturating_mul(Self::BINARY_COMPONENT_METADATA_BYTES),
330 ),
331 ));
332 let maximum_payload_bytes = entities
333 .saturating_mul(components)
334 .saturating_mul(self.limits.max_component_bytes);
335 fixed_bytes.saturating_add(plan.stats.estimated_bytes.min(maximum_payload_bytes))
336 }
337
338 pub fn sampled_binary_capacity_hint(
345 &self,
346 station: &Station,
347 plan: &ReplicationPlan,
348 components: &ComponentStore,
349 selection: &ComponentSelection,
350 ) -> usize {
351 const MAX_SAMPLES: usize = 4;
352 let candidates = plan.entities.len();
353 let samples = candidates.min(MAX_SAMPLES);
354 if samples == 0 {
355 return 0;
356 }
357 let denominator = samples.saturating_mul(2);
358 let mut sampled_bytes = 0_usize;
359 for sample in 0..samples {
360 let numerator = sample.saturating_mul(2).saturating_add(1);
361 let index = numerator.saturating_mul(candidates) / denominator;
362 let entity_bytes = self.binary_entity_bytes(
363 station,
364 plan.entities[index.min(candidates - 1)],
365 components,
366 selection,
367 );
368 if entity_bytes == 0 {
369 return 0;
370 }
371 sampled_bytes = sampled_bytes.saturating_add(entity_bytes);
372 }
373 let estimated_entity_bytes = sampled_bytes.saturating_mul(candidates).div_ceil(samples);
374 let maximum_entities = candidates.min(self.limits.max_entity_deltas);
375 let maximum_components = selection
376 .component_ids
377 .len()
378 .min(self.limits.max_components_per_entity);
379 let maximum_entity_bytes = Self::BINARY_ENTITY_METADATA_BYTES.saturating_add(
380 maximum_components.saturating_mul(
381 Self::BINARY_COMPONENT_METADATA_BYTES
382 .saturating_add(self.limits.max_component_bytes),
383 ),
384 );
385 Self::BINARY_HEADER_BYTES.saturating_add(
386 estimated_entity_bytes.min(maximum_entities.saturating_mul(maximum_entity_bytes)),
387 )
388 }
389
390 fn binary_entity_bytes(
391 &self,
392 station: &Station,
393 handle: EntityHandle,
394 components: &ComponentStore,
395 selection: &ComponentSelection,
396 ) -> usize {
397 if station.get(handle).is_none() {
398 return 0;
399 }
400 let mut bytes = Self::BINARY_ENTITY_METADATA_BYTES;
401 let mut encoded_components = 0_usize;
402 for component_id in &selection.component_ids {
403 if encoded_components >= self.limits.max_components_per_entity {
404 break;
405 }
406 let Some(blob) = components.get_blob(*component_id, handle) else {
407 continue;
408 };
409 if !blob.dirty || blob.bytes.len() > self.limits.max_component_bytes {
410 continue;
411 }
412 bytes = bytes
413 .saturating_add(Self::BINARY_COMPONENT_METADATA_BYTES)
414 .saturating_add(blob.bytes.len());
415 encoded_components += 1;
416 }
417 if encoded_components == 0 { 0 } else { bytes }
418 }
419
420 pub fn build(
422 &self,
423 client_id: ClientId,
424 server_tick: Tick,
425 station: &Station,
426 plan: &ReplicationPlan,
427 components: &ComponentStore,
428 selection: &ComponentSelection,
429 ) -> ReplicationFrameBuild {
430 let mut stats = ReplicationFrameBuildStats {
431 planned_entities: plan.entities.len(),
432 ..ReplicationFrameBuildStats::default()
433 };
434 let mut entity_deltas =
435 Vec::with_capacity(plan.entities.len().min(self.limits.max_entity_deltas));
436 let mut estimated_payload_bytes = 0_usize;
437
438 for handle in &plan.entities {
439 if entity_deltas.len() >= self.limits.max_entity_deltas {
440 stats.skipped_entities_by_limit += 1;
441 continue;
442 }
443 let Some(entity) = station.get(*handle) else {
444 continue;
445 };
446
447 let mut component_deltas = Vec::new();
448 for component_id in &selection.component_ids {
449 if component_deltas.len() >= self.limits.max_components_per_entity {
450 stats.skipped_components_by_limit += 1;
451 continue;
452 }
453 let Some(blob) = components.get_blob(*component_id, *handle) else {
454 continue;
455 };
456 if !blob.dirty {
457 continue;
458 }
459 if blob.bytes.len() > self.limits.max_component_bytes {
460 stats.skipped_components_by_size += 1;
461 continue;
462 }
463 estimated_payload_bytes = estimated_payload_bytes
464 .saturating_add(2 + 8 + 1 + 4)
465 .saturating_add(blob.bytes.len());
466 component_deltas.push(ComponentDelta {
467 component_id: *component_id,
468 version: blob.version,
469 flags: 0,
470 bytes: blob.bytes.clone(),
471 });
472 }
473
474 if component_deltas.is_empty() {
475 continue;
476 }
477
478 stats.encoded_components += component_deltas.len();
479 estimated_payload_bytes = estimated_payload_bytes.saturating_add(8 + 8 + 2);
480 entity_deltas.push(EntityDelta {
481 entity_id: entity.id,
482 owner_epoch: entity.role.owner_epoch(),
483 components: component_deltas,
484 });
485 }
486
487 stats.encoded_entities = entity_deltas.len();
488 ReplicationFrameBuild {
489 frame: ReplicationFrame {
490 client_id,
491 server_tick,
492 entity_count: u32::try_from(plan.entities.len()).unwrap_or(u32::MAX),
493 estimated_payload_bytes: u32::try_from(estimated_payload_bytes).unwrap_or(u32::MAX),
494 entities: entity_deltas,
495 },
496 stats,
497 }
498 }
499
500 #[allow(clippy::too_many_arguments)]
507 pub fn encode_binary_into(
508 &self,
509 client_id: ClientId,
510 server_tick: Tick,
511 station: &Station,
512 plan: &ReplicationPlan,
513 components: &ComponentStore,
514 selection: &ComponentSelection,
515 out: &mut Vec<u8>,
516 ) -> Result<ReplicationFrameBuildStats, BinaryEncodeError> {
517 self.encode_binary_bounded_into(
518 client_id,
519 server_tick,
520 station,
521 plan,
522 components,
523 selection,
524 usize::MAX,
525 out,
526 )
527 }
528
529 #[allow(clippy::too_many_arguments)]
534 pub fn encode_binary_bounded_into(
535 &self,
536 client_id: ClientId,
537 server_tick: Tick,
538 station: &Station,
539 plan: &ReplicationPlan,
540 components: &ComponentStore,
541 selection: &ComponentSelection,
542 max_frame_bytes: usize,
543 out: &mut Vec<u8>,
544 ) -> Result<ReplicationFrameBuildStats, BinaryEncodeError> {
545 if max_frame_bytes < Self::BINARY_HEADER_BYTES {
546 return Err(BinaryEncodeError::FrameBudgetTooSmall {
547 budget: max_frame_bytes,
548 required: Self::BINARY_HEADER_BYTES,
549 });
550 }
551 let frame_start = out.len();
552 let mut stats = ReplicationFrameBuildStats {
553 planned_entities: plan.entities.len(),
554 ..ReplicationFrameBuildStats::default()
555 };
556 let mut estimated_payload_bytes = 0_usize;
557
558 out.push(FrameKind::Replication as u8);
559 out.extend_from_slice(&client_id.get().to_le_bytes());
560 out.extend_from_slice(&server_tick.get().to_le_bytes());
561 out.extend_from_slice(
562 &u32::try_from(plan.entities.len())
563 .unwrap_or(u32::MAX)
564 .to_le_bytes(),
565 );
566 let estimated_payload_offset = out.len();
567 out.extend_from_slice(&0_u32.to_le_bytes());
568 let entity_count_offset = out.len();
569 out.extend_from_slice(&0_u32.to_le_bytes());
570
571 for handle in &plan.entities {
572 if stats.encoded_entities >= self.limits.max_entity_deltas {
573 stats.skipped_entities_by_limit += 1;
574 continue;
575 }
576 let Some(entity) = station.get(*handle) else {
577 continue;
578 };
579
580 let entity_start = out.len();
581 let estimated_before_entity = estimated_payload_bytes;
582 out.extend_from_slice(&entity.id.get().to_le_bytes());
583 out.extend_from_slice(&entity.role.owner_epoch().get().to_le_bytes());
584 let component_count_offset = out.len();
585 out.extend_from_slice(&0_u16.to_le_bytes());
586 let mut component_count = 0_usize;
587
588 for component_id in &selection.component_ids {
589 if component_count >= self.limits.max_components_per_entity {
590 stats.skipped_components_by_limit += 1;
591 continue;
592 }
593 let Some(blob) = components.get_blob(*component_id, *handle) else {
594 continue;
595 };
596 if !blob.dirty {
597 continue;
598 }
599 if blob.bytes.len() > self.limits.max_component_bytes {
600 stats.skipped_components_by_size += 1;
601 continue;
602 }
603
604 out.extend_from_slice(&component_id.get().to_le_bytes());
605 out.extend_from_slice(&blob.version.to_le_bytes());
606 out.push(0);
607 write_bytes("replication.component.bytes", &blob.bytes, out)?;
608 estimated_payload_bytes = estimated_payload_bytes
609 .saturating_add(2 + 8 + 1 + 4)
610 .saturating_add(blob.bytes.len());
611 component_count += 1;
612 }
613
614 if component_count == 0 {
615 out.truncate(entity_start);
616 continue;
617 }
618
619 if out.len().saturating_sub(frame_start) > max_frame_bytes {
620 out.truncate(entity_start);
621 estimated_payload_bytes = estimated_before_entity;
622 stats.skipped_entities_by_frame_bytes =
623 stats.skipped_entities_by_frame_bytes.saturating_add(1);
624 break;
625 }
626
627 let component_count =
628 u16::try_from(component_count).map_err(|_| BinaryEncodeError::TooManyItems {
629 field: "replication.entity.components",
630 actual: component_count,
631 })?;
632 out[component_count_offset..component_count_offset + 2]
633 .copy_from_slice(&component_count.to_le_bytes());
634 stats.encoded_components += usize::from(component_count);
635 stats.encoded_entities += 1;
636 estimated_payload_bytes = estimated_payload_bytes.saturating_add(8 + 8 + 2);
637 }
638
639 let encoded_entities =
640 u32::try_from(stats.encoded_entities).map_err(|_| BinaryEncodeError::TooManyItems {
641 field: "replication.entities",
642 actual: stats.encoded_entities,
643 })?;
644 out[estimated_payload_offset..estimated_payload_offset + 4].copy_from_slice(
645 &u32::try_from(estimated_payload_bytes)
646 .unwrap_or(u32::MAX)
647 .to_le_bytes(),
648 );
649 out[entity_count_offset..entity_count_offset + 4]
650 .copy_from_slice(&encoded_entities.to_le_bytes());
651 Ok(stats)
652 }
653}
654
655#[derive(Clone, Debug, PartialEq, Eq)]
657pub struct CommandAckFrame {
658 pub client_id: ClientId,
660 pub command_id: CommandId,
662 pub server_tick: Tick,
664 pub accepted: bool,
666 pub reason_code: u16,
668}
669
670#[derive(Clone, Debug, PartialEq, Eq)]
676pub struct CommandFrame {
677 pub client_id: ClientId,
679 pub command_id: CommandId,
681 pub entity_id: EntityId,
683 pub sequence: u64,
685 pub kind: u32,
687 pub priority: CommandPriority,
689 pub payload: Vec<u8>,
691}
692
693impl CommandFrame {
694 pub fn into_envelope(self, received_at: Tick) -> CommandEnvelope {
696 CommandEnvelope {
697 id: self.command_id,
698 client_id: self.client_id,
699 entity_id: self.entity_id,
700 sequence: self.sequence,
701 received_at,
702 kind: self.kind,
703 priority: self.priority,
704 payload: self.payload,
705 }
706 }
707
708 pub fn from_envelope(envelope: &CommandEnvelope) -> Self {
711 Self {
712 client_id: envelope.client_id,
713 command_id: envelope.id,
714 entity_id: envelope.entity_id,
715 sequence: envelope.sequence,
716 kind: envelope.kind,
717 priority: envelope.priority,
718 payload: envelope.payload.clone(),
719 }
720 }
721}
722
723#[derive(Clone, Debug, PartialEq, Eq)]
728pub struct CommandDispatchFrame {
729 pub station_id: StationId,
731 pub client_id: ClientId,
733 pub command_id: CommandId,
735 pub entity_id: EntityId,
737 pub sequence: u64,
739 pub received_at: Tick,
741 pub kind: u32,
743 pub priority: CommandPriority,
745 pub payload: Vec<u8>,
747}
748
749impl CommandDispatchFrame {
750 pub fn from_envelope(station_id: StationId, envelope: &CommandEnvelope) -> Self {
752 Self {
753 station_id,
754 client_id: envelope.client_id,
755 command_id: envelope.id,
756 entity_id: envelope.entity_id,
757 sequence: envelope.sequence,
758 received_at: envelope.received_at,
759 kind: envelope.kind,
760 priority: envelope.priority,
761 payload: envelope.payload.clone(),
762 }
763 }
764
765 pub fn into_envelope(self) -> CommandEnvelope {
767 CommandEnvelope {
768 id: self.command_id,
769 client_id: self.client_id,
770 entity_id: self.entity_id,
771 sequence: self.sequence,
772 received_at: self.received_at,
773 kind: self.kind,
774 priority: self.priority,
775 payload: self.payload,
776 }
777 }
778}
779
780#[derive(Clone, Debug, PartialEq, Eq)]
782pub struct StationEventFrame {
783 pub event_id: EventId,
785 pub source_station: StationId,
787 pub target_station: StationId,
789 pub source_tick: Tick,
791 pub target_tick: Tick,
793 pub priority: EventPriority,
795 pub kind: EventKind,
797}
798
799impl StationEventFrame {
800 pub fn from_event(event: &StationEvent) -> Self {
802 Self {
803 event_id: event.id,
804 source_station: event.source,
805 target_station: event.target,
806 source_tick: event.source_tick,
807 target_tick: event.target_tick,
808 priority: event.priority,
809 kind: event.kind.clone(),
810 }
811 }
812
813 pub fn into_event(self) -> StationEvent {
815 StationEvent {
816 id: self.event_id,
817 source: self.source_station,
818 target: self.target_station,
819 source_tick: self.source_tick,
820 target_tick: self.target_tick,
821 priority: self.priority,
822 kind: self.kind,
823 }
824 }
825}
826
827#[derive(Clone, Debug, PartialEq, Eq)]
829pub struct BarrierFrame {
830 pub client_id: ClientId,
832 pub barrier_id: BarrierId,
834 pub server_tick: Tick,
836 pub state: BarrierState,
838}
839
840#[derive(Clone, Debug, PartialEq, Eq)]
842pub enum RuntimeFrame {
843 Replication(ReplicationFrame),
845 Command(CommandFrame),
847 CommandDispatch(CommandDispatchFrame),
849 CommandAck(CommandAckFrame),
851 Barrier(BarrierFrame),
853 StationEvent(StationEventFrame),
855}
856
857pub trait FrameEncoder {
859 type Error;
861
862 fn encode_replication(
864 &mut self,
865 frame: &ReplicationFrame,
866 out: &mut Vec<u8>,
867 ) -> Result<(), Self::Error>;
868
869 fn encode_command_ack(
871 &mut self,
872 frame: &CommandAckFrame,
873 out: &mut Vec<u8>,
874 ) -> Result<(), Self::Error>;
875
876 fn encode_command(
878 &mut self,
879 frame: &CommandFrame,
880 out: &mut Vec<u8>,
881 ) -> Result<(), Self::Error>;
882
883 fn encode_command_dispatch(
885 &mut self,
886 frame: &CommandDispatchFrame,
887 out: &mut Vec<u8>,
888 ) -> Result<(), Self::Error>;
889
890 fn encode_station_event(
892 &mut self,
893 frame: &StationEventFrame,
894 out: &mut Vec<u8>,
895 ) -> Result<(), Self::Error>;
896
897 fn encode_barrier(
899 &mut self,
900 frame: &BarrierFrame,
901 out: &mut Vec<u8>,
902 ) -> Result<(), Self::Error>;
903}
904
905pub trait FrameDecoder {
907 type Error;
909
910 fn decode(&mut self, input: &[u8]) -> Result<RuntimeFrame, Self::Error>;
912}
913
914#[derive(Clone, Debug, PartialEq, Eq)]
916pub enum BinaryDecodeError {
917 Empty,
919 UnknownFrameKind(u8),
921 Truncated {
923 needed: usize,
925 available: usize,
927 },
928 InvalidBarrierState(u8),
930 InvalidCommandPriority(u8),
932 InvalidEventPriority(u8),
934 InvalidEventKind(u8),
936 TrailingBytes(usize),
938}
939
940impl core::fmt::Display for BinaryDecodeError {
941 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
942 match self {
943 Self::Empty => f.write_str("empty frame"),
944 Self::UnknownFrameKind(kind) => write!(f, "unknown frame kind {kind}"),
945 Self::Truncated { needed, available } => {
946 write!(f, "truncated frame: needed {needed}, available {available}")
947 }
948 Self::InvalidBarrierState(state) => write!(f, "invalid barrier state {state}"),
949 Self::InvalidCommandPriority(priority) => {
950 write!(f, "invalid command priority {priority}")
951 }
952 Self::InvalidEventPriority(priority) => write!(f, "invalid event priority {priority}"),
953 Self::InvalidEventKind(kind) => write!(f, "invalid event kind {kind}"),
954 Self::TrailingBytes(bytes) => write!(f, "frame has {bytes} trailing bytes"),
955 }
956 }
957}
958
959impl std::error::Error for BinaryDecodeError {}
960
961#[derive(Clone, Debug, PartialEq, Eq)]
963pub enum ReplicationFrameRefDecodeError {
964 Binary(BinaryDecodeError),
966 UnexpectedFrameKind(FrameKind),
968}
969
970impl core::fmt::Display for ReplicationFrameRefDecodeError {
971 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
972 match self {
973 Self::Binary(error) => error.fmt(f),
974 Self::UnexpectedFrameKind(actual) => {
975 write!(f, "expected Replication frame, found {actual:?}")
976 }
977 }
978 }
979}
980
981impl std::error::Error for ReplicationFrameRefDecodeError {
982 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
983 match self {
984 Self::Binary(error) => Some(error),
985 Self::UnexpectedFrameKind(_) => None,
986 }
987 }
988}
989
990impl From<BinaryDecodeError> for ReplicationFrameRefDecodeError {
991 fn from(value: BinaryDecodeError) -> Self {
992 Self::Binary(value)
993 }
994}
995
996#[derive(Clone, Debug, PartialEq, Eq)]
998pub enum BinaryEncodeError {
999 FrameBudgetTooSmall {
1001 budget: usize,
1003 required: usize,
1005 },
1006 TooManyItems {
1008 field: &'static str,
1010 actual: usize,
1012 },
1013 PayloadTooLarge {
1015 field: &'static str,
1017 actual: usize,
1019 },
1020}
1021
1022impl core::fmt::Display for BinaryEncodeError {
1023 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1024 match self {
1025 Self::FrameBudgetTooSmall { budget, required } => write!(
1026 f,
1027 "replication frame budget {budget} bytes is smaller than {required}-byte header"
1028 ),
1029 Self::TooManyItems { field, actual } => {
1030 write!(f, "{field} has too many items: {actual}")
1031 }
1032 Self::PayloadTooLarge { field, actual } => {
1033 write!(f, "{field} payload is too large: {actual} bytes")
1034 }
1035 }
1036 }
1037}
1038
1039impl std::error::Error for BinaryEncodeError {}
1040
1041#[derive(Clone, Copy, Debug, Default)]
1043pub struct BinaryFrameDecoder;
1044
1045impl BinaryFrameDecoder {
1046 pub fn decode_replication<'a>(
1049 &mut self,
1050 input: &'a [u8],
1051 ) -> Result<ReplicationFrameRef<'a>, ReplicationFrameRefDecodeError> {
1052 let mut cursor = Cursor::new(input);
1053 let kind_byte = cursor.read_u8()?;
1054 let kind = FrameKind::from_byte(kind_byte)
1055 .ok_or(BinaryDecodeError::UnknownFrameKind(kind_byte))?;
1056 if kind != FrameKind::Replication {
1057 return Err(ReplicationFrameRefDecodeError::UnexpectedFrameKind(kind));
1058 }
1059 let frame = decode_replication_ref(&mut cursor)?;
1060 cursor.finish()?;
1061 Ok(frame)
1062 }
1063}
1064
1065impl FrameDecoder for BinaryFrameDecoder {
1066 type Error = BinaryDecodeError;
1067
1068 fn decode(&mut self, input: &[u8]) -> Result<RuntimeFrame, Self::Error> {
1069 let mut cursor = Cursor::new(input);
1070 let kind_byte = cursor.read_u8()?;
1071 let kind = FrameKind::from_byte(kind_byte)
1072 .ok_or(BinaryDecodeError::UnknownFrameKind(kind_byte))?;
1073 let frame = match kind {
1074 FrameKind::Replication => {
1075 RuntimeFrame::Replication(decode_replication_owned(&mut cursor)?)
1076 }
1077 FrameKind::CommandAck => RuntimeFrame::CommandAck(CommandAckFrame {
1078 client_id: ClientId::new(cursor.read_u64()?),
1079 command_id: CommandId::new(cursor.read_u64()?),
1080 server_tick: Tick::new(cursor.read_u64()?),
1081 accepted: cursor.read_u8()? != 0,
1082 reason_code: cursor.read_u16()?,
1083 }),
1084 FrameKind::Command => RuntimeFrame::Command(CommandFrame {
1085 client_id: ClientId::new(cursor.read_u64()?),
1086 command_id: CommandId::new(cursor.read_u64()?),
1087 entity_id: EntityId::new(cursor.read_u64()?),
1088 sequence: cursor.read_u64()?,
1089 kind: cursor.read_u32()?,
1090 priority: decode_command_priority(cursor.read_u8()?)?,
1091 payload: {
1092 let byte_len = cursor.read_u32()? as usize;
1093 cursor.read_bytes(byte_len)?
1094 },
1095 }),
1096 FrameKind::CommandDispatch => RuntimeFrame::CommandDispatch(CommandDispatchFrame {
1097 station_id: StationId::new(cursor.read_u32()?),
1098 client_id: ClientId::new(cursor.read_u64()?),
1099 command_id: CommandId::new(cursor.read_u64()?),
1100 entity_id: EntityId::new(cursor.read_u64()?),
1101 sequence: cursor.read_u64()?,
1102 received_at: Tick::new(cursor.read_u64()?),
1103 kind: cursor.read_u32()?,
1104 priority: decode_command_priority(cursor.read_u8()?)?,
1105 payload: {
1106 let byte_len = cursor.read_u32()? as usize;
1107 cursor.read_bytes(byte_len)?
1108 },
1109 }),
1110 FrameKind::Barrier => RuntimeFrame::Barrier(BarrierFrame {
1111 client_id: ClientId::new(cursor.read_u64()?),
1112 barrier_id: BarrierId::new(cursor.read_u64()?),
1113 server_tick: Tick::new(cursor.read_u64()?),
1114 state: decode_barrier_state(cursor.read_u8()?)?,
1115 }),
1116 FrameKind::StationEvent => RuntimeFrame::StationEvent(StationEventFrame {
1117 event_id: EventId::new(cursor.read_u64()?),
1118 source_station: StationId::new(cursor.read_u32()?),
1119 target_station: StationId::new(cursor.read_u32()?),
1120 source_tick: Tick::new(cursor.read_u64()?),
1121 target_tick: Tick::new(cursor.read_u64()?),
1122 priority: decode_event_priority(cursor.read_u8()?)?,
1123 kind: decode_event_kind(&mut cursor)?,
1124 }),
1125 };
1126 cursor.finish()?;
1127 Ok(frame)
1128 }
1129}
1130
1131#[derive(Clone, Copy, Debug, Default)]
1133pub struct BinaryFrameEncoder;
1134
1135impl BinaryFrameEncoder {
1136 pub fn encode_command_dispatch_envelope(
1141 &mut self,
1142 station_id: StationId,
1143 envelope: &CommandEnvelope,
1144 out: &mut Vec<u8>,
1145 ) -> Result<(), BinaryEncodeError> {
1146 out.push(FrameKind::CommandDispatch as u8);
1147 out.extend_from_slice(&station_id.get().to_le_bytes());
1148 out.extend_from_slice(&envelope.client_id.get().to_le_bytes());
1149 out.extend_from_slice(&envelope.id.get().to_le_bytes());
1150 out.extend_from_slice(&envelope.entity_id.get().to_le_bytes());
1151 out.extend_from_slice(&envelope.sequence.to_le_bytes());
1152 out.extend_from_slice(&envelope.received_at.get().to_le_bytes());
1153 out.extend_from_slice(&envelope.kind.to_le_bytes());
1154 out.push(encode_command_priority(envelope.priority));
1155 write_bytes("command_dispatch.payload", &envelope.payload, out)
1156 }
1157}
1158
1159impl FrameEncoder for BinaryFrameEncoder {
1160 type Error = BinaryEncodeError;
1161
1162 fn encode_replication(
1163 &mut self,
1164 frame: &ReplicationFrame,
1165 out: &mut Vec<u8>,
1166 ) -> Result<(), Self::Error> {
1167 out.push(FrameKind::Replication as u8);
1168 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
1169 out.extend_from_slice(&frame.server_tick.get().to_le_bytes());
1170 out.extend_from_slice(&frame.entity_count.to_le_bytes());
1171 out.extend_from_slice(&frame.estimated_payload_bytes.to_le_bytes());
1172 write_len_u32("replication.entities", frame.entities.len(), out)?;
1173 for entity in &frame.entities {
1174 out.extend_from_slice(&entity.entity_id.get().to_le_bytes());
1175 out.extend_from_slice(&entity.owner_epoch.get().to_le_bytes());
1176 write_len_u16(
1177 "replication.entity.components",
1178 entity.components.len(),
1179 out,
1180 )?;
1181 for component in &entity.components {
1182 out.extend_from_slice(&component.component_id.get().to_le_bytes());
1183 out.extend_from_slice(&component.version.to_le_bytes());
1184 out.push(component.flags);
1185 write_bytes("replication.component.bytes", &component.bytes, out)?;
1186 }
1187 }
1188 Ok(())
1189 }
1190
1191 fn encode_command_ack(
1192 &mut self,
1193 frame: &CommandAckFrame,
1194 out: &mut Vec<u8>,
1195 ) -> Result<(), Self::Error> {
1196 out.push(FrameKind::CommandAck as u8);
1197 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
1198 out.extend_from_slice(&frame.command_id.get().to_le_bytes());
1199 out.extend_from_slice(&frame.server_tick.get().to_le_bytes());
1200 out.push(u8::from(frame.accepted));
1201 out.extend_from_slice(&frame.reason_code.to_le_bytes());
1202 Ok(())
1203 }
1204
1205 fn encode_command(
1206 &mut self,
1207 frame: &CommandFrame,
1208 out: &mut Vec<u8>,
1209 ) -> Result<(), Self::Error> {
1210 out.push(FrameKind::Command as u8);
1211 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
1212 out.extend_from_slice(&frame.command_id.get().to_le_bytes());
1213 out.extend_from_slice(&frame.entity_id.get().to_le_bytes());
1214 out.extend_from_slice(&frame.sequence.to_le_bytes());
1215 out.extend_from_slice(&frame.kind.to_le_bytes());
1216 out.push(encode_command_priority(frame.priority));
1217 write_bytes("command.payload", &frame.payload, out)?;
1218 Ok(())
1219 }
1220
1221 fn encode_command_dispatch(
1222 &mut self,
1223 frame: &CommandDispatchFrame,
1224 out: &mut Vec<u8>,
1225 ) -> Result<(), Self::Error> {
1226 out.push(FrameKind::CommandDispatch as u8);
1227 out.extend_from_slice(&frame.station_id.get().to_le_bytes());
1228 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
1229 out.extend_from_slice(&frame.command_id.get().to_le_bytes());
1230 out.extend_from_slice(&frame.entity_id.get().to_le_bytes());
1231 out.extend_from_slice(&frame.sequence.to_le_bytes());
1232 out.extend_from_slice(&frame.received_at.get().to_le_bytes());
1233 out.extend_from_slice(&frame.kind.to_le_bytes());
1234 out.push(encode_command_priority(frame.priority));
1235 write_bytes("command_dispatch.payload", &frame.payload, out)
1236 }
1237
1238 fn encode_station_event(
1239 &mut self,
1240 frame: &StationEventFrame,
1241 out: &mut Vec<u8>,
1242 ) -> Result<(), Self::Error> {
1243 out.push(FrameKind::StationEvent as u8);
1244 out.extend_from_slice(&frame.event_id.get().to_le_bytes());
1245 out.extend_from_slice(&frame.source_station.get().to_le_bytes());
1246 out.extend_from_slice(&frame.target_station.get().to_le_bytes());
1247 out.extend_from_slice(&frame.source_tick.get().to_le_bytes());
1248 out.extend_from_slice(&frame.target_tick.get().to_le_bytes());
1249 out.push(encode_event_priority(frame.priority));
1250 encode_event_kind(&frame.kind, out);
1251 Ok(())
1252 }
1253
1254 fn encode_barrier(
1255 &mut self,
1256 frame: &BarrierFrame,
1257 out: &mut Vec<u8>,
1258 ) -> Result<(), Self::Error> {
1259 out.push(FrameKind::Barrier as u8);
1260 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
1261 out.extend_from_slice(&frame.barrier_id.get().to_le_bytes());
1262 out.extend_from_slice(&frame.server_tick.get().to_le_bytes());
1263 out.push(encode_barrier_state(frame.state));
1264 Ok(())
1265 }
1266}
1267
1268fn write_len_u16(
1269 field: &'static str,
1270 len: usize,
1271 out: &mut Vec<u8>,
1272) -> Result<(), BinaryEncodeError> {
1273 let len =
1274 u16::try_from(len).map_err(|_| BinaryEncodeError::TooManyItems { field, actual: len })?;
1275 out.extend_from_slice(&len.to_le_bytes());
1276 Ok(())
1277}
1278
1279fn write_len_u32(
1280 field: &'static str,
1281 len: usize,
1282 out: &mut Vec<u8>,
1283) -> Result<(), BinaryEncodeError> {
1284 let len =
1285 u32::try_from(len).map_err(|_| BinaryEncodeError::TooManyItems { field, actual: len })?;
1286 out.extend_from_slice(&len.to_le_bytes());
1287 Ok(())
1288}
1289
1290fn write_bytes(
1291 field: &'static str,
1292 bytes: &[u8],
1293 out: &mut Vec<u8>,
1294) -> Result<(), BinaryEncodeError> {
1295 let len = u32::try_from(bytes.len()).map_err(|_| BinaryEncodeError::PayloadTooLarge {
1296 field,
1297 actual: bytes.len(),
1298 })?;
1299 out.extend_from_slice(&len.to_le_bytes());
1300 out.extend_from_slice(bytes);
1301 Ok(())
1302}
1303
1304fn encode_barrier_state(state: BarrierState) -> u8 {
1305 match state {
1306 BarrierState::Running => 0,
1307 BarrierState::Requested => 1,
1308 BarrierState::WaitingTickBoundary => 2,
1309 BarrierState::Frozen => 3,
1310 BarrierState::Resuming => 4,
1311 }
1312}
1313
1314fn decode_barrier_state(state: u8) -> Result<BarrierState, BinaryDecodeError> {
1315 match state {
1316 0 => Ok(BarrierState::Running),
1317 1 => Ok(BarrierState::Requested),
1318 2 => Ok(BarrierState::WaitingTickBoundary),
1319 3 => Ok(BarrierState::Frozen),
1320 4 => Ok(BarrierState::Resuming),
1321 _ => Err(BinaryDecodeError::InvalidBarrierState(state)),
1322 }
1323}
1324
1325fn encode_command_priority(priority: CommandPriority) -> u8 {
1326 match priority {
1327 CommandPriority::Normal => 0,
1328 CommandPriority::High => 1,
1329 CommandPriority::Low => 2,
1330 }
1331}
1332
1333fn decode_command_priority(priority: u8) -> Result<CommandPriority, BinaryDecodeError> {
1334 match priority {
1335 0 => Ok(CommandPriority::Normal),
1336 1 => Ok(CommandPriority::High),
1337 2 => Ok(CommandPriority::Low),
1338 _ => Err(BinaryDecodeError::InvalidCommandPriority(priority)),
1339 }
1340}
1341
1342fn encode_event_priority(priority: EventPriority) -> u8 {
1343 match priority {
1344 EventPriority::Critical => 0,
1345 EventPriority::Important => 1,
1346 EventPriority::BestEffort => 2,
1347 }
1348}
1349
1350fn decode_event_priority(priority: u8) -> Result<EventPriority, BinaryDecodeError> {
1351 match priority {
1352 0 => Ok(EventPriority::Critical),
1353 1 => Ok(EventPriority::Important),
1354 2 => Ok(EventPriority::BestEffort),
1355 _ => Err(BinaryDecodeError::InvalidEventPriority(priority)),
1356 }
1357}
1358
1359fn encode_event_kind(kind: &EventKind, out: &mut Vec<u8>) {
1360 match kind {
1361 EventKind::Custom(kind) => {
1362 out.push(0);
1363 out.extend_from_slice(&kind.to_le_bytes());
1364 }
1365 EventKind::HandoffPrepare { entity_id } => {
1366 out.push(1);
1367 out.extend_from_slice(&entity_id.get().to_le_bytes());
1368 }
1369 EventKind::HandoffCommit {
1370 entity_id,
1371 owner_epoch,
1372 } => {
1373 out.push(2);
1374 out.extend_from_slice(&entity_id.get().to_le_bytes());
1375 out.extend_from_slice(&owner_epoch.get().to_le_bytes());
1376 }
1377 }
1378}
1379
1380fn decode_event_kind(cursor: &mut Cursor<'_>) -> Result<EventKind, BinaryDecodeError> {
1381 match cursor.read_u8()? {
1382 0 => Ok(EventKind::Custom(cursor.read_u32()?)),
1383 1 => Ok(EventKind::HandoffPrepare {
1384 entity_id: EntityId::new(cursor.read_u64()?),
1385 }),
1386 2 => Ok(EventKind::HandoffCommit {
1387 entity_id: EntityId::new(cursor.read_u64()?),
1388 owner_epoch: OwnerEpoch::new(cursor.read_u64()?),
1389 }),
1390 kind => Err(BinaryDecodeError::InvalidEventKind(kind)),
1391 }
1392}
1393
1394fn decode_replication_ref<'a>(
1395 cursor: &mut Cursor<'a>,
1396) -> Result<ReplicationFrameRef<'a>, BinaryDecodeError> {
1397 let client_id = ClientId::new(cursor.read_u64()?);
1398 let server_tick = Tick::new(cursor.read_u64()?);
1399 let entity_count = cursor.read_u32()?;
1400 let estimated_payload_bytes = cursor.read_u32()?;
1401 let encoded_entity_count = cursor.read_u32()? as usize;
1402 let entities_start = cursor.offset;
1403 for _ in 0..encoded_entity_count {
1404 decode_entity_ref(cursor)?;
1405 }
1406 let encoded_entities = &cursor.input[entities_start..cursor.offset];
1407 Ok(ReplicationFrameRef {
1408 client_id,
1409 server_tick,
1410 entity_count,
1411 estimated_payload_bytes,
1412 encoded_entities,
1413 encoded_entity_count,
1414 })
1415}
1416
1417fn decode_replication_owned(
1418 cursor: &mut Cursor<'_>,
1419) -> Result<ReplicationFrame, BinaryDecodeError> {
1420 let client_id = ClientId::new(cursor.read_u64()?);
1421 let server_tick = Tick::new(cursor.read_u64()?);
1422 let entity_count = cursor.read_u32()?;
1423 let estimated_payload_bytes = cursor.read_u32()?;
1424 let encoded_entity_count = cursor.read_u32()? as usize;
1425 let mut entities = Vec::with_capacity(encoded_entity_count);
1426 for _ in 0..encoded_entity_count {
1427 let entity_id = EntityId::new(cursor.read_u64()?);
1428 let owner_epoch = OwnerEpoch::new(cursor.read_u64()?);
1429 let encoded_component_count = cursor.read_u16()? as usize;
1430 let mut components = Vec::with_capacity(encoded_component_count);
1431 for _ in 0..encoded_component_count {
1432 components.push(decode_component_ref(cursor)?.to_owned());
1433 }
1434 entities.push(EntityDelta {
1435 entity_id,
1436 owner_epoch,
1437 components,
1438 });
1439 }
1440 Ok(ReplicationFrame {
1441 client_id,
1442 server_tick,
1443 entity_count,
1444 estimated_payload_bytes,
1445 entities,
1446 })
1447}
1448
1449fn decode_entity_ref<'a>(cursor: &mut Cursor<'a>) -> Result<EntityDeltaRef<'a>, BinaryDecodeError> {
1450 let entity_id = EntityId::new(cursor.read_u64()?);
1451 let owner_epoch = OwnerEpoch::new(cursor.read_u64()?);
1452 let encoded_component_count = cursor.read_u16()? as usize;
1453 let components_start = cursor.offset;
1454 for _ in 0..encoded_component_count {
1455 decode_component_ref(cursor)?;
1456 }
1457 let encoded_components = &cursor.input[components_start..cursor.offset];
1458 Ok(EntityDeltaRef {
1459 entity_id,
1460 owner_epoch,
1461 encoded_components,
1462 encoded_component_count,
1463 })
1464}
1465
1466fn decode_component_ref<'a>(
1467 cursor: &mut Cursor<'a>,
1468) -> Result<ComponentDeltaRef<'a>, BinaryDecodeError> {
1469 let component_id = ComponentId::new(cursor.read_u16()?);
1470 let version = cursor.read_u64()?;
1471 let flags = cursor.read_u8()?;
1472 let byte_len = cursor.read_u32()? as usize;
1473 let bytes = cursor.read_slice(byte_len)?;
1474 Ok(ComponentDeltaRef {
1475 component_id,
1476 version,
1477 flags,
1478 bytes,
1479 })
1480}
1481
1482#[derive(Clone, Debug)]
1483struct Cursor<'a> {
1484 input: &'a [u8],
1485 offset: usize,
1486}
1487
1488impl<'a> Cursor<'a> {
1489 fn new(input: &'a [u8]) -> Self {
1490 Self { input, offset: 0 }
1491 }
1492
1493 fn read_u8(&mut self) -> Result<u8, BinaryDecodeError> {
1494 self.require(1)?;
1495 let value = self.input[self.offset];
1496 self.offset += 1;
1497 Ok(value)
1498 }
1499
1500 fn read_u16(&mut self) -> Result<u16, BinaryDecodeError> {
1501 let bytes = self.read_array::<2>()?;
1502 Ok(u16::from_le_bytes(bytes))
1503 }
1504
1505 fn read_u32(&mut self) -> Result<u32, BinaryDecodeError> {
1506 let bytes = self.read_array::<4>()?;
1507 Ok(u32::from_le_bytes(bytes))
1508 }
1509
1510 fn read_u64(&mut self) -> Result<u64, BinaryDecodeError> {
1511 let bytes = self.read_array::<8>()?;
1512 Ok(u64::from_le_bytes(bytes))
1513 }
1514
1515 fn read_array<const N: usize>(&mut self) -> Result<[u8; N], BinaryDecodeError> {
1516 self.require(N)?;
1517 let mut out = [0_u8; N];
1518 out.copy_from_slice(&self.input[self.offset..self.offset + N]);
1519 self.offset += N;
1520 Ok(out)
1521 }
1522
1523 fn read_bytes(&mut self, len: usize) -> Result<Vec<u8>, BinaryDecodeError> {
1524 Ok(self.read_slice(len)?.to_vec())
1525 }
1526
1527 fn read_slice(&mut self, len: usize) -> Result<&'a [u8], BinaryDecodeError> {
1528 self.require(len)?;
1529 let bytes = &self.input[self.offset..self.offset + len];
1530 self.offset += len;
1531 Ok(bytes)
1532 }
1533
1534 fn require(&self, count: usize) -> Result<(), BinaryDecodeError> {
1535 let needed = self.offset.saturating_add(count);
1536 if needed > self.input.len() {
1537 Err(BinaryDecodeError::Truncated {
1538 needed,
1539 available: self.input.len(),
1540 })
1541 } else {
1542 Ok(())
1543 }
1544 }
1545
1546 fn finish(&self) -> Result<(), BinaryDecodeError> {
1547 if self.offset == self.input.len() {
1548 Ok(())
1549 } else {
1550 Err(BinaryDecodeError::TrailingBytes(
1551 self.input.len().saturating_sub(self.offset),
1552 ))
1553 }
1554 }
1555}
1556
1557#[cfg(test)]
1558mod tests {
1559 use super::*;
1560
1561 #[test]
1562 fn binary_codec_roundtrips_replication_frame() {
1563 let frame = ReplicationFrame {
1564 client_id: ClientId::new(9),
1565 server_tick: Tick::new(42),
1566 entity_count: 17,
1567 estimated_payload_bytes: 544,
1568 entities: vec![EntityDelta {
1569 entity_id: EntityId::new(100),
1570 owner_epoch: OwnerEpoch::new(2),
1571 components: vec![
1572 ComponentDelta {
1573 component_id: ComponentId::new(1),
1574 version: 9,
1575 flags: 0,
1576 bytes: vec![1, 2, 3, 4],
1577 },
1578 ComponentDelta {
1579 component_id: ComponentId::new(2),
1580 version: 10,
1581 flags: 1,
1582 bytes: vec![5, 6],
1583 },
1584 ],
1585 }],
1586 };
1587 let mut encoder = BinaryFrameEncoder;
1588 let mut bytes = Vec::new();
1589 encoder
1590 .encode_replication(&frame, &mut bytes)
1591 .expect("encoder is infallible");
1592
1593 let borrowed = BinaryFrameDecoder
1594 .decode_replication(&bytes)
1595 .expect("borrowed decode should work");
1596 assert_eq!(borrowed.client_id, frame.client_id);
1597 assert_eq!(borrowed.server_tick, frame.server_tick);
1598 assert_eq!(borrowed.encoded_entity_count(), 1);
1599 let mut entities = borrowed.entities();
1600 assert_eq!(entities.len(), 1);
1601 let entity = entities.next().expect("entity should be borrowed");
1602 assert_eq!(entity.entity_id, frame.entities[0].entity_id);
1603 assert_eq!(entity.encoded_component_count(), 2);
1604 let mut components = entity.components();
1605 assert_eq!(components.len(), 2);
1606 let first = components.next().expect("component should be borrowed");
1607 assert_eq!(first.bytes, frame.entities[0].components[0].bytes);
1608 let input_start = bytes.as_ptr() as usize;
1609 let input_end = input_start.saturating_add(bytes.len());
1610 let payload_start = first.bytes.as_ptr() as usize;
1611 assert!(payload_start >= input_start);
1612 assert!(payload_start.saturating_add(first.bytes.len()) <= input_end);
1613 assert_eq!(borrowed.to_owned(), frame);
1614
1615 let decoded = BinaryFrameDecoder
1616 .decode(&bytes)
1617 .expect("decode should work");
1618 assert_eq!(decoded, RuntimeFrame::Replication(frame));
1619 }
1620
1621 #[test]
1622 fn borrowed_replication_decode_rejects_wrong_kind_truncation_and_trailing_bytes() {
1623 assert_eq!(
1624 BinaryFrameDecoder.decode_replication(&[FrameKind::CommandAck as u8]),
1625 Err(ReplicationFrameRefDecodeError::UnexpectedFrameKind(
1626 FrameKind::CommandAck
1627 ))
1628 );
1629
1630 let frame = ReplicationFrame {
1631 client_id: ClientId::new(9),
1632 server_tick: Tick::new(42),
1633 entity_count: 1,
1634 estimated_payload_bytes: 1,
1635 entities: vec![EntityDelta {
1636 entity_id: EntityId::new(100),
1637 owner_epoch: OwnerEpoch::new(2),
1638 components: vec![ComponentDelta {
1639 component_id: ComponentId::new(1),
1640 version: 9,
1641 flags: 0,
1642 bytes: vec![1],
1643 }],
1644 }],
1645 };
1646 let mut bytes = Vec::new();
1647 BinaryFrameEncoder
1648 .encode_replication(&frame, &mut bytes)
1649 .expect("frame should encode");
1650 let truncated = &bytes[..bytes.len() - 1];
1651 assert!(matches!(
1652 BinaryFrameDecoder.decode_replication(truncated),
1653 Err(ReplicationFrameRefDecodeError::Binary(
1654 BinaryDecodeError::Truncated { .. }
1655 ))
1656 ));
1657 bytes.push(0xff);
1658 assert_eq!(
1659 BinaryFrameDecoder.decode_replication(&bytes),
1660 Err(ReplicationFrameRefDecodeError::Binary(
1661 BinaryDecodeError::TrailingBytes(1)
1662 ))
1663 );
1664 }
1665
1666 #[test]
1667 fn binary_codec_roundtrips_command_ack_frame() {
1668 let frame = CommandAckFrame {
1669 client_id: ClientId::new(1),
1670 command_id: CommandId::new(2),
1671 server_tick: Tick::new(3),
1672 accepted: false,
1673 reason_code: 7,
1674 };
1675 let mut encoder = BinaryFrameEncoder;
1676 let mut bytes = Vec::new();
1677 encoder
1678 .encode_command_ack(&frame, &mut bytes)
1679 .expect("encoder is infallible");
1680
1681 let decoded = BinaryFrameDecoder
1682 .decode(&bytes)
1683 .expect("decode should work");
1684 assert_eq!(decoded, RuntimeFrame::CommandAck(frame));
1685 }
1686
1687 #[test]
1688 fn binary_codec_roundtrips_command_frame() {
1689 let frame = CommandFrame {
1690 client_id: ClientId::new(1),
1691 command_id: CommandId::new(2),
1692 entity_id: EntityId::new(3),
1693 sequence: 4,
1694 kind: 5,
1695 priority: CommandPriority::High,
1696 payload: vec![9, 8, 7],
1697 };
1698 let mut encoder = BinaryFrameEncoder;
1699 let mut bytes = Vec::new();
1700 encoder
1701 .encode_command(&frame, &mut bytes)
1702 .expect("encoder is infallible");
1703
1704 let decoded = BinaryFrameDecoder
1705 .decode(&bytes)
1706 .expect("decode should work");
1707 assert_eq!(decoded, RuntimeFrame::Command(frame));
1708 }
1709
1710 #[test]
1711 fn binary_codec_roundtrips_command_dispatch_frame() {
1712 let frame = CommandDispatchFrame {
1713 station_id: StationId::new(10),
1714 client_id: ClientId::new(1),
1715 command_id: CommandId::new(2),
1716 entity_id: EntityId::new(3),
1717 sequence: 4,
1718 received_at: Tick::new(99),
1719 kind: 5,
1720 priority: CommandPriority::High,
1721 payload: vec![9, 8, 7],
1722 };
1723 let mut encoder = BinaryFrameEncoder;
1724 let mut bytes = Vec::new();
1725 encoder
1726 .encode_command_dispatch(&frame, &mut bytes)
1727 .expect("encoder is infallible");
1728 let envelope = CommandEnvelope {
1729 id: frame.command_id,
1730 client_id: frame.client_id,
1731 entity_id: frame.entity_id,
1732 sequence: frame.sequence,
1733 received_at: frame.received_at,
1734 kind: frame.kind,
1735 priority: frame.priority,
1736 payload: frame.payload.clone(),
1737 };
1738 let mut direct = Vec::new();
1739 encoder
1740 .encode_command_dispatch_envelope(frame.station_id, &envelope, &mut direct)
1741 .expect("direct encoder is infallible");
1742 assert_eq!(direct, bytes);
1743
1744 let decoded = BinaryFrameDecoder
1745 .decode(&bytes)
1746 .expect("decode should work");
1747 assert_eq!(decoded, RuntimeFrame::CommandDispatch(frame));
1748 }
1749
1750 #[test]
1751 fn command_frame_converts_to_runtime_envelope() {
1752 let frame = CommandFrame {
1753 client_id: ClientId::new(1),
1754 command_id: CommandId::new(2),
1755 entity_id: EntityId::new(3),
1756 sequence: 4,
1757 kind: 5,
1758 priority: CommandPriority::Low,
1759 payload: vec![1, 2, 3],
1760 };
1761
1762 let envelope = frame.clone().into_envelope(Tick::new(99));
1763 assert_eq!(envelope.id, frame.command_id);
1764 assert_eq!(envelope.received_at, Tick::new(99));
1765 assert_eq!(CommandFrame::from_envelope(&envelope), frame);
1766 }
1767
1768 #[test]
1769 fn command_dispatch_frame_preserves_stamped_envelope_tick() {
1770 let envelope = CommandEnvelope {
1771 id: CommandId::new(2),
1772 client_id: ClientId::new(1),
1773 entity_id: EntityId::new(3),
1774 sequence: 4,
1775 received_at: Tick::new(99),
1776 kind: 5,
1777 priority: CommandPriority::Low,
1778 payload: vec![1, 2, 3],
1779 };
1780
1781 let frame = CommandDispatchFrame::from_envelope(StationId::new(10), &envelope);
1782 assert_eq!(frame.station_id, StationId::new(10));
1783 assert_eq!(frame.received_at, Tick::new(99));
1784 assert_eq!(frame.into_envelope(), envelope);
1785 }
1786
1787 #[test]
1788 fn binary_codec_roundtrips_barrier_frame() {
1789 let frame = BarrierFrame {
1790 client_id: ClientId::new(1),
1791 barrier_id: BarrierId::new(99),
1792 server_tick: Tick::new(11),
1793 state: BarrierState::Frozen,
1794 };
1795 let mut encoder = BinaryFrameEncoder;
1796 let mut bytes = Vec::new();
1797 encoder
1798 .encode_barrier(&frame, &mut bytes)
1799 .expect("encoder is infallible");
1800
1801 let decoded = BinaryFrameDecoder
1802 .decode(&bytes)
1803 .expect("decode should work");
1804 assert_eq!(decoded, RuntimeFrame::Barrier(frame));
1805 }
1806
1807 #[test]
1808 fn binary_codec_roundtrips_station_event_frame() {
1809 let frames = [
1810 StationEventFrame {
1811 event_id: EventId::new(1),
1812 source_station: StationId::new(10),
1813 target_station: StationId::new(11),
1814 source_tick: Tick::new(2),
1815 target_tick: Tick::new(3),
1816 priority: EventPriority::Critical,
1817 kind: EventKind::Custom(7),
1818 },
1819 StationEventFrame {
1820 event_id: EventId::new(2),
1821 source_station: StationId::new(10),
1822 target_station: StationId::new(11),
1823 source_tick: Tick::new(2),
1824 target_tick: Tick::new(3),
1825 priority: EventPriority::Important,
1826 kind: EventKind::HandoffPrepare {
1827 entity_id: EntityId::new(99),
1828 },
1829 },
1830 StationEventFrame {
1831 event_id: EventId::new(3),
1832 source_station: StationId::new(10),
1833 target_station: StationId::new(11),
1834 source_tick: Tick::new(2),
1835 target_tick: Tick::new(3),
1836 priority: EventPriority::BestEffort,
1837 kind: EventKind::HandoffCommit {
1838 entity_id: EntityId::new(99),
1839 owner_epoch: OwnerEpoch::new(5),
1840 },
1841 },
1842 ];
1843
1844 for frame in frames {
1845 let mut encoder = BinaryFrameEncoder;
1846 let mut bytes = Vec::new();
1847 encoder
1848 .encode_station_event(&frame, &mut bytes)
1849 .expect("encoder is infallible");
1850
1851 let decoded = BinaryFrameDecoder
1852 .decode(&bytes)
1853 .expect("decode should work");
1854 assert_eq!(decoded, RuntimeFrame::StationEvent(frame));
1855 }
1856 }
1857
1858 #[test]
1859 fn station_event_frame_converts_to_runtime_event() {
1860 let event = StationEvent {
1861 id: EventId::new(1),
1862 source: StationId::new(10),
1863 target: StationId::new(11),
1864 source_tick: Tick::new(2),
1865 target_tick: Tick::new(3),
1866 priority: EventPriority::Critical,
1867 kind: EventKind::HandoffPrepare {
1868 entity_id: EntityId::new(99),
1869 },
1870 };
1871
1872 let frame = StationEventFrame::from_event(&event);
1873 assert_eq!(frame.event_id, event.id);
1874 assert_eq!(frame.clone().into_event(), event);
1875 }
1876
1877 #[test]
1878 fn frame_builder_materializes_dirty_component_deltas() {
1879 use sectorsync_core::prelude::{
1880 Bounds, ComponentDescriptor, ComponentMigrationMode, ComponentSyncMode, InstanceId,
1881 NodeId, PolicyId, Position3, ReplicationPlan, StationConfig, Vec3, Vec3LeCodec,
1882 };
1883
1884 let mut station = Station::new(StationConfig {
1885 station_id: sectorsync_core::prelude::StationId::new(1),
1886 node_id: NodeId::new(1),
1887 instance_id: InstanceId::new(1),
1888 tick_rate_hz: 20,
1889 });
1890 let handle = station
1891 .spawn_owned(
1892 EntityId::new(10),
1893 Position3::new(0.0, 0.0, 0.0),
1894 Bounds::Point,
1895 PolicyId::new(0),
1896 )
1897 .expect("spawn should work");
1898
1899 let descriptor = ComponentDescriptor::sparse_blob(
1900 ComponentId::new(1),
1901 "velocity",
1902 ComponentSyncMode::Delta,
1903 ComponentMigrationMode::Copy,
1904 12,
1905 );
1906 let mut components = ComponentStore::default();
1907 components
1908 .set_typed(
1909 &descriptor,
1910 handle,
1911 3,
1912 &Vec3LeCodec,
1913 &Vec3::new(1.0, 2.0, 3.0),
1914 )
1915 .expect("typed component should encode");
1916
1917 let builder = ReplicationFrameBuilder::new(ReplicationFrameLimits {
1918 max_entity_deltas: 8,
1919 max_components_per_entity: 4,
1920 max_component_bytes: 64,
1921 });
1922 let plan = ReplicationPlan {
1923 entities: vec![handle],
1924 stats: sectorsync_core::prelude::ReplicationStats::default(),
1925 };
1926 let selection = ComponentSelection {
1927 component_ids: vec![ComponentId::new(1)],
1928 };
1929 let build = builder.build(
1930 ClientId::new(5),
1931 Tick::new(9),
1932 &station,
1933 &plan,
1934 &components,
1935 &selection,
1936 );
1937
1938 assert_eq!(build.stats.encoded_entities, 1);
1939 assert_eq!(build.stats.encoded_components, 1);
1940 assert_eq!(build.frame.entities[0].entity_id, EntityId::new(10));
1941 assert_eq!(build.frame.entities[0].components[0].version, 3);
1942
1943 let mut materialized_bytes = Vec::new();
1944 BinaryFrameEncoder
1945 .encode_replication(&build.frame, &mut materialized_bytes)
1946 .expect("materialized frame should encode");
1947 let mut direct_bytes = Vec::new();
1948 let direct_stats = builder
1949 .encode_binary_into(
1950 ClientId::new(5),
1951 Tick::new(9),
1952 &station,
1953 &plan,
1954 &components,
1955 &selection,
1956 &mut direct_bytes,
1957 )
1958 .expect("plan should encode directly");
1959
1960 assert_eq!(direct_stats, build.stats);
1961 assert_eq!(direct_bytes, materialized_bytes);
1962 assert_eq!(
1963 builder.sampled_binary_capacity_hint(&station, &plan, &components, &selection),
1964 direct_bytes.len()
1965 );
1966 assert_bounded_binary_encoding(
1967 builder,
1968 &station,
1969 &plan,
1970 &components,
1971 &selection,
1972 direct_bytes.len(),
1973 );
1974 }
1975
1976 fn assert_bounded_binary_encoding(
1977 builder: ReplicationFrameBuilder,
1978 station: &Station,
1979 plan: &ReplicationPlan,
1980 components: &ComponentStore,
1981 selection: &ComponentSelection,
1982 unbounded_len: usize,
1983 ) {
1984 let mut bounded_bytes = Vec::new();
1985 let bounded = builder
1986 .encode_binary_bounded_into(
1987 ClientId::new(5),
1988 Tick::new(9),
1989 station,
1990 plan,
1991 components,
1992 selection,
1993 unbounded_len - 1,
1994 &mut bounded_bytes,
1995 )
1996 .expect("bounded frame should roll back the oversized entity");
1997 assert_eq!(bounded.encoded_entities, 0);
1998 assert_eq!(bounded.encoded_components, 0);
1999 assert_eq!(bounded.skipped_entities_by_frame_bytes, 1);
2000 assert!(bounded_bytes.len() < unbounded_len);
2001 let RuntimeFrame::Replication(frame) = BinaryFrameDecoder
2002 .decode(&bounded_bytes)
2003 .expect("bounded empty frame should remain valid")
2004 else {
2005 panic!("expected replication frame");
2006 };
2007 assert!(frame.entities.is_empty());
2008
2009 let mut prefix = vec![9, 8, 7];
2010 assert_eq!(
2011 builder.encode_binary_bounded_into(
2012 ClientId::new(5),
2013 Tick::new(9),
2014 station,
2015 plan,
2016 components,
2017 selection,
2018 ReplicationFrameBuilder::BINARY_HEADER_BYTES - 1,
2019 &mut prefix,
2020 ),
2021 Err(BinaryEncodeError::FrameBudgetTooSmall {
2022 budget: ReplicationFrameBuilder::BINARY_HEADER_BYTES - 1,
2023 required: ReplicationFrameBuilder::BINARY_HEADER_BYTES,
2024 })
2025 );
2026 assert_eq!(prefix, [9, 8, 7]);
2027 }
2028
2029 #[test]
2030 fn binary_capacity_hint_is_bounded_by_active_builder_limits() {
2031 let builder = ReplicationFrameBuilder::new(ReplicationFrameLimits {
2032 max_entity_deltas: 2,
2033 max_components_per_entity: 2,
2034 max_component_bytes: 32,
2035 });
2036 let plan = ReplicationPlan {
2037 entities: vec![
2038 EntityHandle::new(1, 0),
2039 EntityHandle::new(2, 0),
2040 EntityHandle::new(3, 0),
2041 ],
2042 stats: sectorsync_core::prelude::ReplicationStats {
2043 estimated_bytes: usize::MAX,
2044 ..sectorsync_core::prelude::ReplicationStats::default()
2045 },
2046 };
2047 let selection = ComponentSelection {
2048 component_ids: vec![
2049 ComponentId::new(1),
2050 ComponentId::new(2),
2051 ComponentId::new(3),
2052 ],
2053 };
2054
2055 let fixed_bytes = 29 + 2 * (18 + 2 * 15);
2056 let maximum_payload_bytes = 2 * 2 * 32;
2057 assert_eq!(
2058 builder.binary_capacity_hint(&plan, &selection),
2059 fixed_bytes + maximum_payload_bytes
2060 );
2061 }
2062
2063 #[test]
2064 fn sampled_capacity_hint_falls_back_when_any_sample_has_no_dirty_data() {
2065 use sectorsync_core::prelude::{
2066 Bounds, ComponentDescriptor, ComponentMigrationMode, ComponentSyncMode, InstanceId,
2067 NodeId, PolicyId, Position3, ReplicationStats, StationConfig, U32LeCodec,
2068 };
2069
2070 let mut station = Station::new(StationConfig {
2071 station_id: StationId::new(1),
2072 node_id: NodeId::new(1),
2073 instance_id: InstanceId::new(1),
2074 tick_rate_hz: 20,
2075 });
2076 let descriptor = ComponentDescriptor::sparse_blob(
2077 ComponentId::new(1),
2078 "health",
2079 ComponentSyncMode::Delta,
2080 ComponentMigrationMode::Copy,
2081 4,
2082 );
2083 let mut components = ComponentStore::default();
2084 let mut handles = Vec::new();
2085 for entity in 0_u64..4 {
2086 let handle = station
2087 .spawn_owned(
2088 EntityId::new(entity),
2089 Position3::new(0.0, 0.0, 0.0),
2090 Bounds::Point,
2091 PolicyId::new(1),
2092 )
2093 .expect("entity ids are unique");
2094 if entity < 3 {
2095 components
2096 .set_typed(&descriptor, handle, 1, &U32LeCodec, &100)
2097 .expect("component should fit");
2098 }
2099 handles.push(handle);
2100 }
2101 let plan = ReplicationPlan {
2102 entities: handles,
2103 stats: ReplicationStats {
2104 selected: 4,
2105 estimated_bytes: 128,
2106 ..ReplicationStats::default()
2107 },
2108 };
2109 let selection = ComponentSelection {
2110 component_ids: vec![ComponentId::new(1)],
2111 };
2112
2113 assert_eq!(
2114 ReplicationFrameBuilder::default().sampled_binary_capacity_hint(
2115 &station,
2116 &plan,
2117 &components,
2118 &selection,
2119 ),
2120 0
2121 );
2122 }
2123}