1use aligned_vec::{AVec, ConstAlign};
61use bytes::Bytes;
62use core::ops::Range;
63use thiserror::Error;
64
65use crate::value::{EventType, Metadata, Payload, SchemaVersion};
66
67pub const PAYLOAD_ALIGN: usize = 16;
69
70pub(crate) const HEADER_FIXED_SIZE: usize = 11;
76
77pub(crate) const VERSION_OFFSET: usize = 0;
79
80pub(crate) const SCHEMA_VERSION_OFFSET: usize = 1;
82
83pub(crate) const EVENT_TYPE_LEN_OFFSET: usize = 5;
85
86pub(crate) const META_LEN_OFFSET: usize = 7;
88
89const HEADER_FIXED_SIZE_V1: usize = 19;
93
94const SCHEMA_VERSION_OFFSET_V1: usize = 9;
97const EVENT_TYPE_LEN_OFFSET_V1: usize = 13;
98const META_LEN_OFFSET_V1: usize = 15;
99
100pub(crate) const META_LEN_ABSENT: u32 = u32::MAX;
102
103#[inline]
108const fn align_padding(offset: usize, align: usize) -> usize {
109 (align - (offset % align)) % align
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub(crate) enum FrameFormatVersion {
121 V1,
124 V2,
126}
127
128impl FrameFormatVersion {
129 pub(crate) const CURRENT: Self = Self::V2;
131
132 #[inline]
134 const fn to_u8(self) -> u8 {
135 match self {
136 Self::V1 => 1,
137 Self::V2 => 2,
138 }
139 }
140
141 #[inline]
145 const fn from_u8(byte: u8) -> Option<Self> {
146 match byte {
147 1 => Some(Self::V1),
148 2 => Some(Self::V2),
149 _ => None,
150 }
151 }
152}
153
154#[derive(Debug, Clone, Copy)]
173pub(crate) struct FrameHeader {
174 pub(crate) format_version: FrameFormatVersion,
175 pub(crate) schema_version: u32,
176 event_type_len: u16,
177 metadata_len: Option<u32>,
178}
179
180impl FrameHeader {
181 pub(crate) const SIZE: usize = HEADER_FIXED_SIZE;
183
184 fn from_validated_lengths(
192 format_version: FrameFormatVersion,
193 schema_version: u32,
194 event_type_len: usize,
195 metadata_len: Option<usize>,
196 ) -> Self {
197 #[allow(
198 clippy::expect_used,
199 reason = "validated by EventType::from_bytes invariant: length ≤ u16::MAX"
200 )]
201 let event_type_len_u16 = u16::try_from(event_type_len)
202 .expect("event_type length validated by EventType invariant");
203 let metadata_len_u32 = metadata_len.map(|n| {
204 #[allow(
205 clippy::expect_used,
206 reason = "validated by Metadata::from_bytes invariant: length ≤ MAX_METADATA_LEN"
207 )]
208 let v = u32::try_from(n).expect("metadata length validated by Metadata invariant");
209 v
210 });
211 Self {
212 format_version,
213 schema_version,
214 event_type_len: event_type_len_u16,
215 metadata_len: metadata_len_u32,
216 }
217 }
218
219 fn write_into(&self, buf: &mut AVec<u8, ConstAlign<PAYLOAD_ALIGN>>) {
223 let meta_field = self.metadata_len.unwrap_or(META_LEN_ABSENT);
224 buf.extend_from_slice(&[self.format_version.to_u8()]);
225 buf.extend_from_slice(&self.schema_version.to_le_bytes());
226 buf.extend_from_slice(&self.event_type_len.to_le_bytes());
227 buf.extend_from_slice(&meta_field.to_le_bytes());
228 }
229
230 pub(crate) fn read_from(value: &[u8]) -> Result<Self, DecodeError> {
246 if value.len() < Self::SIZE {
248 return Err(DecodeError::ValueTooShort {
249 min: Self::SIZE,
250 actual: value.len(),
251 });
252 }
253 let version_byte = value[VERSION_OFFSET];
254 let format_version = FrameFormatVersion::from_u8(version_byte).ok_or(
255 DecodeError::UnsupportedFrameVersion {
256 version: version_byte,
257 },
258 )?;
259 let (schema_off, et_len_off, meta_off) = match format_version {
260 FrameFormatVersion::V1 => {
261 if value.len() < HEADER_FIXED_SIZE_V1 {
262 return Err(DecodeError::ValueTooShort {
263 min: HEADER_FIXED_SIZE_V1,
264 actual: value.len(),
265 });
266 }
267 (
268 SCHEMA_VERSION_OFFSET_V1,
269 EVENT_TYPE_LEN_OFFSET_V1,
270 META_LEN_OFFSET_V1,
271 )
272 }
273 FrameFormatVersion::V2 => (
274 SCHEMA_VERSION_OFFSET,
275 EVENT_TYPE_LEN_OFFSET,
276 META_LEN_OFFSET,
277 ),
278 };
279 let schema_version = u32::from_le_bytes([
280 value[schema_off],
281 value[schema_off + 1],
282 value[schema_off + 2],
283 value[schema_off + 3],
284 ]);
285 let event_type_len = u16::from_le_bytes([value[et_len_off], value[et_len_off + 1]]);
286 let meta_field = u32::from_le_bytes([
287 value[meta_off],
288 value[meta_off + 1],
289 value[meta_off + 2],
290 value[meta_off + 3],
291 ]);
292 let metadata_len = if meta_field == META_LEN_ABSENT {
293 None
294 } else {
295 Some(meta_field)
296 };
297 Ok(Self {
298 format_version,
299 schema_version,
300 event_type_len,
301 metadata_len,
302 })
303 }
304}
305
306#[derive(Debug, Clone)]
317struct FrameLayout {
318 event_type: Range<u32>,
319 metadata: Option<Range<u32>>,
320 payload: Range<u32>,
321 padding: usize,
322 total: usize,
323}
324
325#[inline]
327const fn length_overflow(header: usize, padding: usize, payload: usize) -> WireError {
328 WireError::FrameLengthOverflow {
329 header,
330 padding,
331 payload,
332 }
333}
334
335impl FrameLayout {
336 fn compute_from_validated_lengths(
349 event_type_len: usize,
350 metadata_len: Option<usize>,
351 payload_len: usize,
352 ) -> Result<Self, WireError> {
353 let meta_len_usize = metadata_len.unwrap_or(0);
354
355 let pre_payload_len = HEADER_FIXED_SIZE
356 .checked_add(event_type_len)
357 .and_then(|n| n.checked_add(meta_len_usize))
358 .ok_or_else(|| length_overflow(HEADER_FIXED_SIZE, 0, payload_len))?;
359
360 let padding = align_padding(pre_payload_len, PAYLOAD_ALIGN);
361 let total = pre_payload_len
362 .checked_add(padding)
363 .and_then(|n| n.checked_add(payload_len))
364 .ok_or_else(|| length_overflow(pre_payload_len, padding, payload_len))?;
365
366 let overflow = || length_overflow(pre_payload_len, padding, payload_len);
367
368 let event_type_start = u32::try_from(HEADER_FIXED_SIZE).map_err(|_| overflow())?;
369 let event_type_len_u32 = u32::try_from(event_type_len).map_err(|_| overflow())?;
370 let event_type_end = event_type_start
371 .checked_add(event_type_len_u32)
372 .ok_or_else(overflow)?;
373
374 let metadata_range = metadata_len
375 .map(|n| -> Result<Range<u32>, WireError> {
376 let n_u32 = u32::try_from(n).map_err(|_| overflow())?;
377 let end = event_type_end.checked_add(n_u32).ok_or_else(overflow)?;
378 Ok(event_type_end..end)
379 })
380 .transpose()?;
381
382 let payload_start_usize = pre_payload_len.checked_add(padding).ok_or_else(overflow)?;
383 let payload_start = u32::try_from(payload_start_usize).map_err(|_| overflow())?;
384 let payload_len_u32 = u32::try_from(payload_len).map_err(|_| overflow())?;
385 let payload_end = payload_start
386 .checked_add(payload_len_u32)
387 .ok_or_else(overflow)?;
388
389 Ok(Self {
390 event_type: event_type_start..event_type_end,
391 metadata: metadata_range,
392 payload: payload_start..payload_end,
393 padding,
394 total,
395 })
396 }
397}
398
399#[derive(Debug)]
405pub struct EncodedFrame {
406 pub value: Bytes,
407 pub offsets: FrameOffsets,
408}
409
410#[derive(Debug, Clone)]
415pub struct FrameOffsets {
416 pub event_type: Range<u32>,
417 pub metadata: Option<Range<u32>>,
418 pub payload: Range<u32>,
419}
420
421#[derive(Debug, Error)]
428#[non_exhaustive]
429pub enum WireError {
430 #[error(
431 "frame length overflow combining header={header}, padding={padding}, payload={payload}"
432 )]
433 FrameLengthOverflow {
434 header: usize,
435 padding: usize,
436 payload: usize,
437 },
438}
439
440#[derive(Debug)]
442pub struct DecodedFrame {
443 pub schema_version: SchemaVersion,
444 pub offsets: FrameOffsets,
445}
446
447#[derive(Debug, Error)]
449#[non_exhaustive]
450pub enum DecodeError {
451 #[error("value too short: need at least {min} bytes, got {actual}")]
452 ValueTooShort { min: usize, actual: usize },
453 #[error(
457 "unsupported frame format version on wire: got {version}, this build supports up to {}",
458 FrameFormatVersion::CURRENT.to_u8()
459 )]
460 UnsupportedFrameVersion { version: u8 },
461 #[error("event type length {et_len} extends past value (len={value_len})")]
462 EventTypeTruncated { et_len: usize, value_len: usize },
463 #[error("metadata length {meta_len} extends past value (len={value_len})")]
464 MetadataTruncated { meta_len: u32, value_len: usize },
465 #[error("computed offset overflows u32 (value len={value_len})")]
466 OffsetOverflow { value_len: usize },
467 #[error("corrupt schema_version on wire: got 0, must be > 0")]
472 CorruptSchemaVersion,
473}
474
475#[derive(Debug)]
488struct FramePlan<'a> {
489 header: FrameHeader,
490 event_type_bytes: &'a [u8],
491 metadata: Option<&'a [u8]>,
492 payload: &'a [u8],
493 layout: FrameLayout,
494}
495
496fn plan<'a>(
498 schema_version: SchemaVersion,
499 event_type: &'a EventType,
500 payload: &'a Payload,
501 metadata: Option<&'a Metadata>,
502) -> Result<FramePlan<'a>, WireError> {
503 let event_type_bytes = event_type.as_bytes();
504 let metadata_bytes = metadata.map(Metadata::as_slice);
505 let payload_bytes = payload.as_slice();
506
507 let layout = FrameLayout::compute_from_validated_lengths(
508 event_type_bytes.len(),
509 metadata_bytes.map(<[u8]>::len),
510 payload_bytes.len(),
511 )?;
512 let header = FrameHeader::from_validated_lengths(
513 FrameFormatVersion::CURRENT,
514 schema_version.get(),
515 event_type_bytes.len(),
516 metadata_bytes.map(<[u8]>::len),
517 );
518 Ok(FramePlan {
519 header,
520 event_type_bytes,
521 metadata: metadata_bytes,
522 payload: payload_bytes,
523 layout,
524 })
525}
526
527fn execute(plan: FramePlan<'_>) -> EncodedFrame {
529 let mut buf: AVec<u8, ConstAlign<PAYLOAD_ALIGN>> =
530 AVec::with_capacity(PAYLOAD_ALIGN, plan.layout.total);
531 plan.header.write_into(&mut buf);
532 buf.extend_from_slice(plan.event_type_bytes);
533 if let Some(m) = plan.metadata {
534 buf.extend_from_slice(m);
535 }
536 buf.resize(buf.len() + plan.layout.padding, 0u8);
537 buf.extend_from_slice(plan.payload);
538
539 EncodedFrame {
540 value: Bytes::from_owner(buf),
541 offsets: FrameOffsets {
542 event_type: plan.layout.event_type,
543 metadata: plan.layout.metadata,
544 payload: plan.layout.payload,
545 },
546 }
547}
548
549pub fn encode_frame(
580 schema_version: SchemaVersion,
581 event_type: &EventType,
582 payload: &Payload,
583 metadata: Option<&Metadata>,
584) -> Result<EncodedFrame, WireError> {
585 plan(schema_version, event_type, payload, metadata).map(execute)
586}
587
588pub fn decode_frame(value: &[u8]) -> Result<DecodedFrame, DecodeError> {
606 let header = FrameHeader::read_from(value)?;
607 let header_size = match header.format_version {
611 FrameFormatVersion::V1 => HEADER_FIXED_SIZE_V1,
612 FrameFormatVersion::V2 => HEADER_FIXED_SIZE,
613 };
614 decode_frame_body(value, header, header_size)
615}
616
617fn decode_frame_body(
620 value: &[u8],
621 header: FrameHeader,
622 header_size: usize,
623) -> Result<DecodedFrame, DecodeError> {
624 let schema_version = SchemaVersion::from_u32(header.schema_version)
625 .map_err(|_| DecodeError::CorruptSchemaVersion)?;
626 let et_len = usize::from(header.event_type_len);
627
628 let et_start = header_size;
629 let et_end = et_start
630 .checked_add(et_len)
631 .ok_or(DecodeError::OffsetOverflow {
632 value_len: value.len(),
633 })?;
634 if value.len() < et_end {
635 return Err(DecodeError::EventTypeTruncated {
636 et_len,
637 value_len: value.len(),
638 });
639 }
640
641 let (metadata_range, post_meta) = match header.metadata_len {
642 None => (None, et_end),
643 Some(meta_len) => {
644 let meta_len_usize =
645 usize::try_from(meta_len).map_err(|_| DecodeError::OffsetOverflow {
646 value_len: value.len(),
647 })?;
648 let meta_end =
649 et_end
650 .checked_add(meta_len_usize)
651 .ok_or(DecodeError::OffsetOverflow {
652 value_len: value.len(),
653 })?;
654 if value.len() < meta_end {
655 return Err(DecodeError::MetadataTruncated {
656 meta_len,
657 value_len: value.len(),
658 });
659 }
660 let m_start_u32 = u32::try_from(et_end).map_err(|_| DecodeError::OffsetOverflow {
661 value_len: value.len(),
662 })?;
663 let m_end_u32 = u32::try_from(meta_end).map_err(|_| DecodeError::OffsetOverflow {
664 value_len: value.len(),
665 })?;
666 (Some(m_start_u32..m_end_u32), meta_end)
667 }
668 };
669
670 let padding = align_padding(post_meta, PAYLOAD_ALIGN);
671 let payload_start = post_meta
672 .checked_add(padding)
673 .ok_or(DecodeError::OffsetOverflow {
674 value_len: value.len(),
675 })?;
676 let payload_end = value.len();
677 if payload_start > payload_end {
678 return Err(DecodeError::OffsetOverflow {
679 value_len: value.len(),
680 });
681 }
682
683 let et_start_u32 = u32::try_from(et_start).map_err(|_| DecodeError::OffsetOverflow {
684 value_len: value.len(),
685 })?;
686 let et_end_u32 = u32::try_from(et_end).map_err(|_| DecodeError::OffsetOverflow {
687 value_len: value.len(),
688 })?;
689 let payload_start_u32 =
690 u32::try_from(payload_start).map_err(|_| DecodeError::OffsetOverflow {
691 value_len: value.len(),
692 })?;
693 let payload_end_u32 = u32::try_from(payload_end).map_err(|_| DecodeError::OffsetOverflow {
694 value_len: value.len(),
695 })?;
696
697 Ok(DecodedFrame {
698 schema_version,
699 offsets: FrameOffsets {
700 event_type: et_start_u32..et_end_u32,
701 metadata: metadata_range,
702 payload: payload_start_u32..payload_end_u32,
703 },
704 })
705}
706
707#[cfg(test)]
708#[allow(
709 clippy::as_conversions,
710 clippy::cast_possible_truncation,
711 clippy::panic,
712 clippy::redundant_clone,
713 clippy::single_match_else,
714 reason = "test code: index arithmetic, prop_assert_eq macro expansions, \
715 and `panic!(\"expected X, got {other:?}\")` arms surface failing test diagnostics"
716)]
717mod tests {
718 use super::*;
719 use crate::value::{MAX_EVENT_TYPE_LEN, MAX_METADATA_LEN, MAX_PAYLOAD_LEN};
720 use proptest::prelude::*;
721
722 fn payload_ptr_aligned(frame: &EncodedFrame) -> bool {
723 let start = usize::try_from(frame.offsets.payload.start).expect("u32 fits usize");
724 let end = usize::try_from(frame.offsets.payload.end).expect("u32 fits usize");
725 let payload_slice = &frame.value[start..end];
726 payload_slice.as_ptr().addr().is_multiple_of(PAYLOAD_ALIGN)
727 }
728
729 fn et(s: &str) -> EventType {
732 EventType::from_bytes(Bytes::copy_from_slice(s.as_bytes())).expect("test event_type valid")
733 }
734
735 fn pl(b: &[u8]) -> Payload {
737 Payload::from_bytes(Bytes::copy_from_slice(b)).expect("test payload valid")
738 }
739
740 fn md(b: &[u8]) -> Metadata {
742 Metadata::from_bytes(Bytes::copy_from_slice(b)).expect("test metadata non-empty + valid")
743 }
744
745 fn sv1() -> SchemaVersion {
746 SchemaVersion::INITIAL
747 }
748
749 fn align_and_offset() -> impl Strategy<Value = (usize, usize)> {
763 (0u32..16).prop_flat_map(|align_pow| {
764 let align = 1usize << align_pow;
765 let offset = prop_oneof![
767 1 => Just(0usize),
768 1 => Just(1usize),
769 1 => Just(align.saturating_sub(1)),
770 1 => Just(align),
771 1 => Just(align + 1),
772 10 => 0usize..1_000_000,
773 ];
774 (Just(align), offset)
775 })
776 }
777
778 fn u32_strategy() -> impl Strategy<Value = u32> {
779 prop_oneof![
780 1 => Just(0u32),
781 1 => Just(1u32),
782 1 => Just(u32::MAX - 1),
783 1 => Just(u32::MAX),
784 10 => any::<u32>(),
785 ]
786 }
787
788 fn schema_version_strategy() -> impl Strategy<Value = SchemaVersion> {
792 prop_oneof![
793 1 => Just(1u32),
794 1 => Just(2u32),
795 1 => Just(u32::MAX - 1),
796 1 => Just(u32::MAX),
797 10 => 1u32..=u32::MAX,
798 ]
799 .prop_map(|v| SchemaVersion::from_u32(v).expect("nonzero strategy"))
800 }
801
802 fn u16_strategy() -> impl Strategy<Value = u16> {
803 prop_oneof![
804 1 => Just(0u16),
805 1 => Just(1u16),
806 1 => Just(u16::MAX - 1),
807 1 => Just(u16::MAX),
808 10 => any::<u16>(),
809 ]
810 }
811
812 fn frame_body_length() -> impl Strategy<Value = usize> {
816 prop_oneof![
817 1 => Just(0usize),
818 1 => Just(1usize),
819 1 => Just(PAYLOAD_ALIGN - 1),
820 1 => Just(PAYLOAD_ALIGN),
821 1 => Just(PAYLOAD_ALIGN + 1),
822 10 => 0usize..=4096,
823 ]
824 }
825
826 fn event_type_str_strategy() -> impl Strategy<Value = String> {
831 prop_oneof![
832 1 => Just(String::new()),
833 1 => Just("a".to_owned()),
834 10 => prop::collection::vec(any::<char>(), 0..=256)
835 .prop_map(|chars| chars.into_iter().collect::<String>()),
836 ]
837 }
838
839 fn metadata_bytes_strategy() -> impl Strategy<Value = Option<Vec<u8>>> {
840 prop_oneof![
844 1 => Just(None),
845 1 => Just(Some(vec![0u8])),
846 10 => prop::option::of(prop::collection::vec(any::<u8>(), 1..512)),
847 ]
848 }
849
850 fn payload_bytes_strategy() -> impl Strategy<Value = Vec<u8>> {
851 prop_oneof![
852 1 => Just(Vec::<u8>::new()),
853 1 => Just(vec![0u8]),
854 10 => prop::collection::vec(any::<u8>(), 0..2048),
855 ]
856 }
857
858 prop_compose! {
862 fn valid_frame_inputs()(
863 schema_version in schema_version_strategy(),
864 event_type in event_type_str_strategy(),
865 metadata in metadata_bytes_strategy(),
866 payload in payload_bytes_strategy(),
867 ) -> (SchemaVersion, String, Option<Vec<u8>>, Vec<u8>) {
868 (schema_version, event_type, metadata, payload)
869 }
870 }
871
872 proptest! {
873 #[test]
874 fn payload_pointer_is_16_aligned(
875 (schema_version, event_type, metadata, payload) in valid_frame_inputs(),
876 ) {
877 let et_v = et(&event_type);
878 let pl_v = pl(&payload);
879 let md_v = metadata.as_deref().map(md);
880 let frame = encode_frame(schema_version, &et_v, &pl_v, md_v.as_ref())
881 .expect("encode_frame succeeds on bounded inputs");
882 prop_assert!(payload_ptr_aligned(&frame));
883 }
884
885 #[test]
886 fn ranges_recover_each_field(
887 (schema_version, event_type, metadata, payload) in valid_frame_inputs(),
888 ) {
889 let et_v = et(&event_type);
890 let pl_v = pl(&payload);
891 let md_v = metadata.as_deref().map(md);
892 let frame = encode_frame(schema_version, &et_v, &pl_v, md_v.as_ref())
893 .expect("encode_frame succeeds on bounded inputs");
894 let v = &frame.value;
895 prop_assert_eq!(
896 &v[frame.offsets.event_type.start as usize..frame.offsets.event_type.end as usize],
897 event_type.as_bytes()
898 );
899 prop_assert_eq!(
900 &v[frame.offsets.payload.start as usize..frame.offsets.payload.end as usize],
901 payload.as_slice()
902 );
903 if let (Some(meta), Some(range)) = (metadata.as_deref(), frame.offsets.metadata) {
904 prop_assert_eq!(
905 &v[range.start as usize..range.end as usize],
906 meta
907 );
908 }
909 }
910
911 #[test]
912 fn header_fields_are_recoverable(
913 (schema_version, event_type, metadata, payload) in valid_frame_inputs(),
914 ) {
915 let et_v = et(&event_type);
916 let pl_v = pl(&payload);
917 let md_v = metadata.as_deref().map(md);
918 let frame = encode_frame(schema_version, &et_v, &pl_v, md_v.as_ref())
919 .expect("encode_frame succeeds on bounded inputs");
920 let v = &frame.value;
921
922 let mut sv_buf = [0u8; 4];
923 sv_buf.copy_from_slice(&v[SCHEMA_VERSION_OFFSET..EVENT_TYPE_LEN_OFFSET]);
924 prop_assert_eq!(u32::from_le_bytes(sv_buf), schema_version.get());
925
926 let mut et_len_buf = [0u8; 2];
927 et_len_buf.copy_from_slice(&v[EVENT_TYPE_LEN_OFFSET..EVENT_TYPE_LEN_OFFSET + 2]);
928 prop_assert_eq!(usize::from(u16::from_le_bytes(et_len_buf)), event_type.len());
929
930 let mut ml_buf = [0u8; 4];
931 ml_buf.copy_from_slice(&v[META_LEN_OFFSET..META_LEN_OFFSET + 4]);
932 let ml = u32::from_le_bytes(ml_buf);
933 match metadata.as_deref() {
934 Some(m) => prop_assert_eq!(usize::try_from(ml).unwrap(), m.len()),
935 None => prop_assert_eq!(ml, META_LEN_ABSENT),
936 }
937 }
938
939 #[test]
940 fn encoded_frame_carries_v2_version_byte(
941 (schema_version, event_type, metadata, payload) in valid_frame_inputs(),
942 ) {
943 let et_v = et(&event_type);
944 let pl_v = pl(&payload);
945 let md_v = metadata.as_deref().map(md);
946 let frame = encode_frame(schema_version, &et_v, &pl_v, md_v.as_ref())
947 .expect("encode_frame succeeds on bounded inputs");
948 prop_assert_eq!(frame.value[VERSION_OFFSET], 2);
950 let header = FrameHeader::read_from(&frame.value)
952 .expect("header reads back from a freshly built frame");
953 prop_assert_eq!(header.format_version, FrameFormatVersion::V2);
954 }
955 }
956
957 #[test]
958 fn empty_payload_still_aligned() {
959 let frame = encode_frame(sv1(), &et("X"), &pl(b""), None).expect("trivial frame builds");
960 assert!(payload_ptr_aligned(&frame));
961 assert_eq!(frame.offsets.payload.start, frame.offsets.payload.end);
962 }
963
964 #[test]
965 fn empty_event_type_permitted() {
966 let frame = encode_frame(sv1(), &et(""), &pl(b"data"), None)
967 .expect("empty event_type accepted at wire layer");
968 assert!(payload_ptr_aligned(&frame));
969 }
970
971 #[test]
972 fn max_event_type_accepted() {
973 let huge = "a".repeat(MAX_EVENT_TYPE_LEN);
974 encode_frame(sv1(), &et(&huge), &pl(b"d"), None).expect("max-length event_type accepted");
975 }
976
977 #[test]
978 fn meta_len_u32_max_is_absent_sentinel() {
979 let frame =
980 encode_frame(sv1(), &et("X"), &pl(b"d"), None).expect("none-metadata frame builds");
981 let mut ml_buf = [0u8; 4];
982 ml_buf.copy_from_slice(&frame.value[META_LEN_OFFSET..META_LEN_OFFSET + 4]);
983 assert_eq!(u32::from_le_bytes(ml_buf), META_LEN_ABSENT);
984 assert!(frame.offsets.metadata.is_none());
985 }
986
987 proptest! {
988 #[test]
989 fn build_then_decode_round_trips(
990 (schema_version, event_type, metadata, payload) in valid_frame_inputs(),
991 ) {
992 let et_v = et(&event_type);
993 let pl_v = pl(&payload);
994 let md_v = metadata.as_deref().map(md);
995 let frame = encode_frame(schema_version, &et_v, &pl_v, md_v.as_ref())
996 .expect("encode_frame succeeds on bounded inputs");
997 let decoded = decode_frame(&frame.value).expect("decode_frame succeeds on a built frame");
998 prop_assert_eq!(decoded.schema_version, schema_version);
999 prop_assert_eq!(decoded.offsets.event_type.clone(), frame.offsets.event_type.clone());
1000 prop_assert_eq!(decoded.offsets.metadata.clone(), frame.offsets.metadata.clone());
1001 prop_assert_eq!(decoded.offsets.payload.clone(), frame.offsets.payload.clone());
1002 }
1003 }
1004
1005 #[test]
1006 fn decode_rejects_truncated_value() {
1007 let too_short = vec![0u8; HEADER_FIXED_SIZE - 1];
1008 assert!(matches!(
1009 decode_frame(&too_short),
1010 Err(DecodeError::ValueTooShort { .. })
1011 ));
1012 }
1013
1014 #[test]
1015 fn decode_rejects_truncated_event_type() {
1016 let mut buf = vec![0u8; HEADER_FIXED_SIZE];
1019 buf[VERSION_OFFSET] = 2;
1020 buf[SCHEMA_VERSION_OFFSET..EVENT_TYPE_LEN_OFFSET].copy_from_slice(&1u32.to_le_bytes());
1021 buf[EVENT_TYPE_LEN_OFFSET..EVENT_TYPE_LEN_OFFSET + 2]
1022 .copy_from_slice(&100u16.to_le_bytes());
1023 buf[META_LEN_OFFSET..META_LEN_OFFSET + 4].copy_from_slice(&META_LEN_ABSENT.to_le_bytes());
1024 assert!(matches!(
1025 decode_frame(&buf),
1026 Err(DecodeError::EventTypeTruncated { .. })
1027 ));
1028 }
1029
1030 #[test]
1031 fn decode_rejects_truncated_metadata() {
1032 let mut buf = vec![0u8; HEADER_FIXED_SIZE];
1034 buf[VERSION_OFFSET] = 2;
1035 buf[SCHEMA_VERSION_OFFSET..EVENT_TYPE_LEN_OFFSET].copy_from_slice(&1u32.to_le_bytes());
1036 buf[EVENT_TYPE_LEN_OFFSET..EVENT_TYPE_LEN_OFFSET + 2].copy_from_slice(&0u16.to_le_bytes());
1037 buf[META_LEN_OFFSET..META_LEN_OFFSET + 4].copy_from_slice(&100u32.to_le_bytes());
1038 assert!(matches!(
1039 decode_frame(&buf),
1040 Err(DecodeError::MetadataTruncated { .. })
1041 ));
1042 }
1043
1044 #[test]
1051 fn decode_rejects_corrupt_schema_version_zero() {
1052 let frame = encode_frame(sv1(), &et("X"), &pl(b"p"), None).expect("encode");
1056 let mut bytes_vec = frame.value.to_vec();
1057 bytes_vec[SCHEMA_VERSION_OFFSET..EVENT_TYPE_LEN_OFFSET].fill(0);
1058 let tampered = Bytes::from(bytes_vec);
1059 assert!(matches!(
1060 decode_frame(&tampered),
1061 Err(DecodeError::CorruptSchemaVersion)
1062 ));
1063 }
1064
1065 fn adversarial_decode_bytes() -> impl Strategy<Value = Vec<u8>> {
1086 let header_shaped = (
1091 prop_oneof![10 => Just(2u8), 1 => any::<u8>()],
1092 any::<u32>(),
1093 0u16..=64,
1094 prop_oneof![Just(META_LEN_ABSENT), 0u32..=64],
1095 prop::collection::vec(any::<u8>(), 0..=512),
1096 )
1097 .prop_map(|(version, sv, et_len, meta_len, body)| {
1098 let mut buf = Vec::with_capacity(HEADER_FIXED_SIZE + body.len());
1099 buf.extend_from_slice(&[version]);
1100 buf.extend_from_slice(&sv.to_le_bytes());
1101 buf.extend_from_slice(&et_len.to_le_bytes());
1102 buf.extend_from_slice(&meta_len.to_le_bytes());
1103 buf.extend_from_slice(&body);
1104 buf
1105 });
1106
1107 prop_oneof![
1108 1 => Just(Vec::<u8>::new()),
1109 1 => Just(vec![0u8]),
1110 1 => prop::collection::vec(any::<u8>(), HEADER_FIXED_SIZE - 1..=HEADER_FIXED_SIZE - 1),
1111 1 => prop::collection::vec(any::<u8>(), HEADER_FIXED_SIZE..=HEADER_FIXED_SIZE),
1112 1 => prop::collection::vec(any::<u8>(), HEADER_FIXED_SIZE + 1..=HEADER_FIXED_SIZE + 1),
1113 5 => prop::collection::vec(any::<u8>(), 0..=4096),
1114 5 => header_shaped,
1115 ]
1116 }
1117
1118 proptest! {
1119 #[test]
1120 fn decode_never_panics(bytes in adversarial_decode_bytes()) {
1121 let _ = decode_frame(&bytes);
1127 }
1128
1129 #[test]
1134 fn decode_offsets_in_bounds_on_success(bytes in adversarial_decode_bytes()) {
1135 if let Ok(decoded) = decode_frame(&bytes) {
1136 let len_u32 = u32::try_from(bytes.len()).unwrap_or(u32::MAX);
1137 prop_assert!(decoded.offsets.event_type.start <= decoded.offsets.event_type.end);
1138 prop_assert!(decoded.offsets.event_type.end <= len_u32);
1139 if let Some(meta) = decoded.offsets.metadata {
1140 prop_assert!(meta.start <= meta.end);
1141 prop_assert!(meta.end <= len_u32);
1142 }
1143 prop_assert!(decoded.offsets.payload.start <= decoded.offsets.payload.end);
1144 prop_assert!(decoded.offsets.payload.end <= len_u32);
1145 }
1146 }
1147 }
1148
1149 #[test]
1154 fn align_padding_zero_offset_yields_zero() {
1155 assert_eq!(align_padding(0, PAYLOAD_ALIGN), 0);
1156 }
1157
1158 #[test]
1159 fn align_padding_one_below_boundary_yields_one() {
1160 assert_eq!(align_padding(15, PAYLOAD_ALIGN), 1);
1161 }
1162
1163 #[test]
1164 fn align_padding_on_boundary_yields_zero() {
1165 assert_eq!(align_padding(PAYLOAD_ALIGN, PAYLOAD_ALIGN), 0);
1166 }
1167
1168 #[test]
1169 fn align_padding_one_above_boundary_yields_fifteen() {
1170 assert_eq!(align_padding(PAYLOAD_ALIGN + 1, PAYLOAD_ALIGN), 15);
1171 }
1172
1173 proptest! {
1174 #[test]
1179 fn align_padding_invariants(
1180 (align, offset) in align_and_offset(),
1181 ) {
1182 let pad = align_padding(offset, align);
1183
1184 prop_assert!(
1186 (offset + pad).is_multiple_of(align),
1187 "offset={offset} align={align} pad={pad} not multiple",
1188 );
1189
1190 prop_assert!(pad < align, "pad {pad} >= align {align}");
1193
1194 prop_assert_eq!(pad == 0, offset.is_multiple_of(align));
1196 }
1197 }
1198
1199 fn fresh_buf() -> AVec<u8, ConstAlign<PAYLOAD_ALIGN>> {
1204 AVec::with_capacity(PAYLOAD_ALIGN, 64)
1205 }
1206
1207 #[test]
1208 fn frame_header_write_into_writes_all_fields_at_correct_offsets() {
1209 let header = FrameHeader {
1211 format_version: FrameFormatVersion::V2,
1212 schema_version: 0x090A_0B0C,
1213 event_type_len: 0x0D0E,
1214 metadata_len: Some(0x0F10_1112),
1215 };
1216 let mut buf = fresh_buf();
1217 header.write_into(&mut buf);
1218
1219 assert_eq!(buf.len(), FrameHeader::SIZE);
1221
1222 assert_eq!(buf[VERSION_OFFSET], 2);
1224
1225 assert_eq!(
1229 &buf[SCHEMA_VERSION_OFFSET..EVENT_TYPE_LEN_OFFSET],
1230 &0x090A_0B0Cu32.to_le_bytes(),
1231 );
1232 assert_eq!(
1233 &buf[EVENT_TYPE_LEN_OFFSET..EVENT_TYPE_LEN_OFFSET + 2],
1234 &0x0D0Eu16.to_le_bytes(),
1235 );
1236 assert_eq!(
1237 &buf[META_LEN_OFFSET..META_LEN_OFFSET + 4],
1238 &0x0F10_1112u32.to_le_bytes(),
1239 );
1240 }
1241
1242 #[test]
1243 fn frame_header_none_metadata_encodes_sentinel() {
1244 let header = FrameHeader {
1245 format_version: FrameFormatVersion::V2,
1246 schema_version: 1,
1247 event_type_len: 0,
1248 metadata_len: None,
1249 };
1250 let mut buf = fresh_buf();
1251 header.write_into(&mut buf);
1252 let mut ml = [0u8; 4];
1253 ml.copy_from_slice(&buf[META_LEN_OFFSET..META_LEN_OFFSET + 4]);
1254 assert_eq!(u32::from_le_bytes(ml), META_LEN_ABSENT);
1257 let read = FrameHeader::read_from(&buf).expect("read back");
1258 assert!(read.metadata_len.is_none());
1259 }
1260
1261 #[test]
1262 fn frame_header_some_zero_metadata_distinct_from_none() {
1263 let with_empty = FrameHeader {
1266 format_version: FrameFormatVersion::V2,
1267 schema_version: 1,
1268 event_type_len: 0,
1269 metadata_len: Some(0),
1270 };
1271 let mut buf = fresh_buf();
1272 with_empty.write_into(&mut buf);
1273 let mut ml = [0u8; 4];
1274 ml.copy_from_slice(&buf[META_LEN_OFFSET..META_LEN_OFFSET + 4]);
1275 assert_eq!(u32::from_le_bytes(ml), 0);
1276 assert_ne!(u32::from_le_bytes(ml), META_LEN_ABSENT);
1277
1278 let read = FrameHeader::read_from(&buf).expect("read back");
1279 assert_eq!(read.metadata_len, Some(0));
1280 }
1281
1282 #[test]
1283 fn frame_header_read_from_rejects_buffer_below_size() {
1284 for too_short_len in 0..FrameHeader::SIZE {
1286 let buf = vec![0u8; too_short_len];
1287 match FrameHeader::read_from(&buf) {
1288 Err(DecodeError::ValueTooShort { min, actual }) => {
1289 assert_eq!(min, FrameHeader::SIZE);
1290 assert_eq!(actual, too_short_len);
1291 }
1292 other => panic!("expected ValueTooShort for len={too_short_len}, got {other:?}"),
1293 }
1294 }
1295 }
1296
1297 #[test]
1298 fn frame_header_read_from_accepts_exactly_size() {
1299 let mut buf = vec![0u8; FrameHeader::SIZE];
1300 buf[VERSION_OFFSET] = 2;
1301 let header = FrameHeader::read_from(&buf).expect("accepts at SIZE");
1302 assert_eq!(header.format_version, FrameFormatVersion::V2);
1303 assert_eq!(header.schema_version, 0);
1304 assert_eq!(header.event_type_len, 0);
1305 assert_eq!(header.metadata_len, Some(0));
1306 }
1307
1308 proptest! {
1309 #[test]
1310 fn frame_header_round_trip(
1311 schema_version in u32_strategy(),
1312 et_raw in u16_strategy(),
1313 meta_choice in 0u32..4,
1314 ) {
1315 let metadata_len = match meta_choice {
1317 0 => None,
1318 1 => Some(0u32),
1319 2 => Some(u32::MAX - 2),
1320 _ => Some((u32::MAX - 1) / 2),
1321 };
1322 let original = FrameHeader {
1323 format_version: FrameFormatVersion::V2,
1324 schema_version,
1325 event_type_len: et_raw,
1326 metadata_len,
1327 };
1328 let mut buf = fresh_buf();
1329 original.write_into(&mut buf);
1330 prop_assert_eq!(buf.len(), FrameHeader::SIZE);
1331
1332 let read = FrameHeader::read_from(&buf).expect("round-trip read");
1333 prop_assert_eq!(read.format_version, original.format_version);
1334 prop_assert_eq!(read.schema_version, original.schema_version);
1335 prop_assert_eq!(read.event_type_len, original.event_type_len);
1336 prop_assert_eq!(read.metadata_len, original.metadata_len);
1337 }
1338 }
1339
1340 #[test]
1345 fn layout_concrete_no_metadata_example() {
1346 let layout = FrameLayout::compute_from_validated_lengths(2, None, 1).expect("ok");
1350 assert_eq!(layout.padding, 3);
1351 assert_eq!(layout.event_type, 11..13);
1352 assert_eq!(layout.metadata, None);
1353 assert_eq!(layout.payload, 16..17);
1354 assert_eq!(layout.total, 17);
1355 }
1356
1357 #[test]
1358 fn layout_concrete_with_metadata_example() {
1359 let layout = FrameLayout::compute_from_validated_lengths(2, Some(3), 4).expect("ok");
1362 assert_eq!(layout.event_type, 11..13);
1363 assert_eq!(layout.metadata, Some(13..16));
1364 assert_eq!(layout.padding, 0);
1365 assert_eq!(layout.payload, 16..20);
1366 assert_eq!(layout.total, 20);
1367 }
1368
1369 proptest! {
1370 #[test]
1371 fn layout_structural_invariants(
1372 et_len_raw in frame_body_length(),
1373 meta in prop::option::of(frame_body_length()),
1374 payload_len in frame_body_length(),
1375 ) {
1376 let et_len = et_len_raw.min(MAX_EVENT_TYPE_LEN);
1378 let meta_capped = meta.map(|n| n.min(MAX_METADATA_LEN));
1380 let payload_len_capped = payload_len.min(MAX_PAYLOAD_LEN);
1382 let layout = FrameLayout::compute_from_validated_lengths(
1383 et_len,
1384 meta_capped,
1385 payload_len_capped,
1386 ).expect("bounded inputs compute");
1387
1388 prop_assert_eq!(
1390 usize::try_from(layout.event_type.start).unwrap(),
1391 HEADER_FIXED_SIZE,
1392 );
1393
1394 prop_assert_eq!(
1396 (layout.event_type.end - layout.event_type.start) as usize,
1397 et_len,
1398 );
1399 match (meta_capped, layout.metadata.clone()) {
1400 (None, None) => {},
1401 (Some(meta_len), Some(range)) => {
1402 prop_assert_eq!((range.end - range.start) as usize, meta_len);
1403 }
1404 _ => prop_assert!(false, "metadata Option mismatch between input and layout"),
1405 }
1406 prop_assert_eq!(
1407 (layout.payload.end - layout.payload.start) as usize,
1408 payload_len_capped,
1409 );
1410
1411 if let Some(m) = layout.metadata.clone() {
1413 prop_assert!(layout.event_type.end <= m.start);
1414 prop_assert!(m.end <= layout.payload.start);
1415 } else {
1416 prop_assert!(layout.event_type.end <= layout.payload.start);
1417 }
1418
1419 let payload_start = usize::try_from(layout.payload.start).unwrap();
1422 prop_assert!(payload_start.is_multiple_of(PAYLOAD_ALIGN));
1423
1424 prop_assert!(layout.padding < PAYLOAD_ALIGN);
1427
1428 prop_assert_eq!(layout.total, usize::try_from(layout.payload.end).unwrap());
1430
1431 let body_total = et_len
1433 + meta_capped.unwrap_or(0)
1434 + layout.padding
1435 + payload_len_capped;
1436 prop_assert_eq!(layout.total, HEADER_FIXED_SIZE + body_total);
1437 }
1438 }
1439
1440 #[test]
1445 fn plan_then_execute_matches_encode_frame_concrete() {
1446 let sv = SchemaVersion::from_u32(2).expect("nonzero");
1448 let et_v = et("Evt");
1449 let pl_v = pl(b"payload");
1450 let md_v = md(b"meta");
1451 let one_shot = encode_frame(sv, &et_v, &pl_v, Some(&md_v)).expect("ok");
1452 let staged = execute(plan(sv, &et_v, &pl_v, Some(&md_v)).expect("plan ok"));
1453 assert_eq!(one_shot.value.as_ref(), staged.value.as_ref());
1454 assert_eq!(one_shot.offsets.event_type, staged.offsets.event_type);
1455 assert_eq!(one_shot.offsets.metadata, staged.offsets.metadata);
1456 assert_eq!(one_shot.offsets.payload, staged.offsets.payload);
1457 }
1458
1459 #[test]
1462 fn execute_buffer_length_equals_layout_total() {
1463 let cases: Vec<(EventType, Option<Metadata>, Payload)> = vec![
1464 (et(""), None, pl(b"")),
1465 (et("X"), None, pl(b"")),
1466 (et("Evt"), Some(md(b"meta")), pl(b"payload")),
1467 (et("LongerType"), Some(md(b"x")), pl(b"x")),
1468 ];
1469 for (et_v, md_v, pl_v) in cases {
1470 let p = plan(sv1(), &et_v, &pl_v, md_v.as_ref()).expect("plan ok");
1471 let total = p.layout.total;
1472 let frame = execute(p);
1473 assert_eq!(frame.value.len(), total);
1474 }
1475 }
1476
1477 #[test]
1478 fn execute_padding_bytes_are_zero() {
1479 let frame = encode_frame(sv1(), &et("x"), &pl(b"payload"), None).expect("ok");
1481 let pad_start = usize::try_from(frame.offsets.event_type.end).unwrap();
1482 let pad_end = usize::try_from(frame.offsets.payload.start).unwrap();
1483 assert!(pad_end > pad_start, "expected at least one padding byte");
1484 for (i, byte) in frame.value[pad_start..pad_end].iter().enumerate() {
1485 assert_eq!(
1486 *byte,
1487 0,
1488 "padding byte at offset {} is {:#x}",
1489 pad_start + i,
1490 byte
1491 );
1492 }
1493 }
1494
1495 proptest! {
1496 #[test]
1497 fn plan_execute_equals_encode_frame(
1498 (schema_version, event_type, metadata, payload) in valid_frame_inputs(),
1499 ) {
1500 let et_v = et(&event_type);
1501 let pl_v = pl(&payload);
1502 let md_v = metadata.as_deref().map(md);
1503 let one_shot = encode_frame(
1504 schema_version, &et_v, &pl_v, md_v.as_ref(),
1505 ).expect("valid inputs encode");
1506 let staged = execute(
1507 plan(schema_version, &et_v, &pl_v, md_v.as_ref())
1508 .expect("valid inputs plan"),
1509 );
1510 prop_assert_eq!(one_shot.value.as_ref(), staged.value.as_ref());
1512 prop_assert_eq!(one_shot.offsets.event_type, staged.offsets.event_type);
1513 prop_assert_eq!(one_shot.offsets.metadata, staged.offsets.metadata);
1514 prop_assert_eq!(one_shot.offsets.payload, staged.offsets.payload);
1515 }
1516
1517 #[test]
1518 fn execute_invariants(
1519 (schema_version, event_type, metadata, payload) in valid_frame_inputs(),
1520 ) {
1521 let et_v = et(&event_type);
1522 let pl_v = pl(&payload);
1523 let md_v = metadata.as_deref().map(md);
1524 let p = plan(schema_version, &et_v, &pl_v, md_v.as_ref())
1525 .expect("valid inputs plan");
1526 let layout_total = p.layout.total;
1527 let event_type_range = p.layout.event_type.clone();
1528 let metadata_range = p.layout.metadata.clone();
1529 let payload_range = p.layout.payload.clone();
1530 let frame = execute(p);
1531
1532 prop_assert_eq!(frame.value.len(), layout_total);
1534
1535 let payload_slice_start = usize::try_from(payload_range.start).unwrap();
1537 let ptr = frame.value[payload_slice_start..].as_ptr().addr();
1538 prop_assert!(ptr.is_multiple_of(PAYLOAD_ALIGN));
1539
1540 let et_start = usize::try_from(event_type_range.start).unwrap();
1542 let et_end = usize::try_from(event_type_range.end).unwrap();
1543 prop_assert_eq!(&frame.value[et_start..et_end], event_type.as_bytes());
1544 if let (Some(range), Some(meta)) = (metadata_range.clone(), metadata.as_deref()) {
1545 let s = usize::try_from(range.start).unwrap();
1546 let e = usize::try_from(range.end).unwrap();
1547 prop_assert_eq!(&frame.value[s..e], meta);
1548 }
1549 let p_start = usize::try_from(payload_range.start).unwrap();
1550 let p_end = usize::try_from(payload_range.end).unwrap();
1551 prop_assert_eq!(&frame.value[p_start..p_end], payload.as_slice());
1552
1553 let pad_start = metadata_range
1555 .as_ref()
1556 .map_or(et_end, |r| usize::try_from(r.end).unwrap());
1557 for byte in &frame.value[pad_start..p_start] {
1558 prop_assert_eq!(*byte, 0u8);
1559 }
1560 }
1561 }
1562
1563 #[test]
1568 fn encode_frame_accepts_value_newtypes() {
1569 let et_v = EventType::from_static_str("UserCreated");
1570 let payload = Payload::from_bytes(Bytes::from_static(b"hello")).expect("valid");
1571 let metadata = Metadata::from_bytes(Bytes::from_static(b"m")).expect("valid");
1572 let sv = SchemaVersion::INITIAL;
1573 let frame = encode_frame(sv, &et_v, &payload, Some(&metadata)).expect("valid frame");
1574 let decoded = decode_frame(&frame.value).expect("decodes");
1575 assert_eq!(decoded.schema_version, sv);
1576 }
1577
1578 #[test]
1579 fn decode_frame_rejects_corrupt_schema_version_zero() {
1580 let et_v = EventType::from_static_str("X");
1584 let payload = Payload::from_bytes(Bytes::from_static(b"p")).expect("valid");
1585 let sv_one = SchemaVersion::INITIAL;
1586 let frame = encode_frame(sv_one, &et_v, &payload, None).expect("valid frame for tamper");
1587 let mut bytes_vec = frame.value.to_vec();
1588 bytes_vec[SCHEMA_VERSION_OFFSET..EVENT_TYPE_LEN_OFFSET].fill(0);
1589 let tampered = Bytes::from(bytes_vec);
1590 let err = decode_frame(&tampered).expect_err("schema_version=0 on wire rejected");
1591 assert!(matches!(err, DecodeError::CorruptSchemaVersion));
1592 }
1593
1594 #[test]
1599 fn decode_rejects_every_unknown_version_byte() {
1600 let frame = encode_frame(sv1(), &et("Evt"), &pl(b"payload"), Some(&md(b"m")))
1604 .expect("valid frame for tamper base");
1605 for bad in (0u8..=u8::MAX).filter(|b| *b != 1 && *b != 2) {
1606 let mut bytes_vec = frame.value.to_vec();
1607 bytes_vec[VERSION_OFFSET] = bad;
1608 let tampered = Bytes::from(bytes_vec);
1609 match decode_frame(&tampered) {
1610 Err(DecodeError::UnsupportedFrameVersion { version }) => {
1611 assert_eq!(version, bad);
1612 }
1613 other => panic!("version byte {bad} should be rejected, got {other:?}"),
1614 }
1615 }
1616 }
1617
1618 #[test]
1619 fn decode_reads_a_v1_frame_dropping_its_global_seq() {
1620 let event_type = b"Created";
1626 let payload = b"data-bytes";
1627 let mut buf = Vec::new();
1628 buf.push(1u8); buf.extend_from_slice(&999u64.to_le_bytes()); buf.extend_from_slice(&7u32.to_le_bytes()); buf.extend_from_slice(&u16::try_from(event_type.len()).unwrap().to_le_bytes()); buf.extend_from_slice(&META_LEN_ABSENT.to_le_bytes()); buf.extend_from_slice(event_type);
1634 let post_et = buf.len();
1636 buf.resize(post_et + align_padding(post_et, PAYLOAD_ALIGN), 0u8);
1637 buf.extend_from_slice(payload);
1638
1639 let decoded = decode_frame(&buf).expect("a well-formed V1 frame must still decode");
1640 assert_eq!(decoded.schema_version.get(), 7);
1641 let et = &buf
1642 [decoded.offsets.event_type.start as usize..decoded.offsets.event_type.end as usize];
1643 assert_eq!(et, event_type);
1644 let pl = &buf[decoded.offsets.payload.start as usize..decoded.offsets.payload.end as usize];
1645 assert_eq!(pl, payload);
1646 }
1647
1648 #[test]
1649 fn decode_empty_buffer_is_too_short_not_version_error() {
1650 match decode_frame(&[]) {
1651 Err(DecodeError::ValueTooShort { min, actual }) => {
1652 assert_eq!(min, HEADER_FIXED_SIZE);
1653 assert_eq!(actual, 0);
1654 }
1655 other => panic!("empty buffer should be ValueTooShort, got {other:?}"),
1656 }
1657 }
1658
1659 #[test]
1660 fn corrupt_version_byte_surfaces_unsupported_not_panic() {
1661 let frame = encode_frame(sv1(), &et("X"), &pl(b"p"), None).expect("encode");
1663 let mut bytes_vec = frame.value.to_vec();
1664 bytes_vec[VERSION_OFFSET] = 0xFF;
1665 let tampered = Bytes::from(bytes_vec);
1666 let err = decode_frame(&tampered).expect_err("corrupt version rejected");
1667 assert!(matches!(
1668 err,
1669 DecodeError::UnsupportedFrameVersion { version: 0xFF }
1670 ));
1671 }
1672}