1#![deny(unsafe_op_in_unsafe_fn)]
2#![warn(missing_docs)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![cfg_attr(not(feature = "std"), no_std)]
5
6extern crate self as rustbinary;
74
75extern crate alloc;
79
80#[cfg(feature = "std")]
81pub mod adapters;
83
84#[cfg(feature = "adaptive")]
85pub mod adaptive;
87#[cfg(feature = "archive")]
88pub mod archive;
90#[cfg(feature = "bit-packing")]
91pub mod bitpack;
93#[cfg(feature = "cbor")]
94pub mod cbor;
96#[cfg(feature = "compression")]
97pub mod compression;
99pub mod config;
101pub mod core;
103mod decoder;
104#[cfg(feature = "encryption")]
105pub mod encryption;
107pub mod error;
109#[cfg(feature = "schema-evolution")]
110pub mod evolution;
112#[cfg(feature = "fingerprint")]
113mod frame;
114#[cfg(feature = "parallel")]
115pub mod parallel;
117pub mod pipeline;
119pub mod protocol;
121#[cfg(feature = "reflection")]
122pub mod reflection;
124#[cfg(feature = "fingerprint")]
125pub mod schema;
127mod ser;
128#[cfg(feature = "simd")]
129pub mod simd;
130#[cfg(feature = "static-size")]
131pub mod static_size;
133mod tags;
135pub mod writer;
137
138#[cfg(feature = "alloc")]
139use alloc::vec::Vec;
140#[cfg(feature = "std")]
141use std::io::{Read, Write};
142
143#[cfg(feature = "adaptive")]
144pub use adaptive::{AdaptiveConfig, CollectionStrategy, StringStrategy};
145#[cfg(feature = "bit-packing")]
146pub use bitpack::{BitPack, BitPackedConfig, BitReader, BitValue, BitWriter};
147#[cfg(feature = "cbor")]
148pub use cbor::CborConfig;
149#[cfg(all(feature = "cbor", feature = "fingerprint"))]
150pub use cbor::FingerprintedCborConfig;
151#[cfg(feature = "compression")]
152pub use compression::CompressedConfig;
153pub use config::{
154 Config, Endian, IntEncoding, Options, TrailingBytes, DEFAULT_COLLECTION_LIMIT,
155 DEFAULT_SIZE_LIMIT,
156};
157#[cfg(feature = "encryption")]
158pub use encryption::{EncryptedConfig, EncryptionKey};
159pub use error::{Error, ErrorCategory, Result};
160#[cfg(feature = "schema-evolution")]
161pub use evolution::{
162 EvolutionConfig, FieldDecoder, FieldEncoder, SchemaDecode, SchemaEncode, UnknownField,
163};
164#[cfg(feature = "parallel")]
165pub use parallel::ParallelConfig;
166#[cfg(feature = "reflection")]
167pub use reflection::{FieldInfo, Reflect, TypeShape, VariantInfo};
168#[cfg(feature = "fingerprint")]
169pub use schema::{Fingerprint, FingerprintedConfig};
170#[cfg(feature = "simd")]
171pub use simd::{hardware_capabilities, simd_backend, HardwareCapabilities, SimdBackend};
172#[cfg(feature = "static-size")]
173pub use static_size::StaticSize;
174pub use writer::{CountWriter, EncodeWriter, SliceWriter};
175
176#[cfg(all(feature = "derive", feature = "bit-packing"))]
177pub use rustbinary_derive::BitPacked;
178
179#[cfg(feature = "bit-packing")]
180#[doc(hidden)]
181pub const fn __bitpack_max(left: usize, right: usize) -> usize {
182 if left > right {
183 left
184 } else {
185 right
186 }
187}
188#[cfg(all(feature = "derive", feature = "fingerprint"))]
189pub use rustbinary_derive::Fingerprint;
190#[cfg(all(feature = "derive", feature = "reflection"))]
191pub use rustbinary_derive::Reflect;
192#[cfg(all(feature = "derive", feature = "static-size"))]
193pub use rustbinary_derive::StaticSize;
194
195#[cfg(feature = "derive")]
202pub use nextjson::{NsonDeserialize, NsonSchema, NsonSerialize};
203
204pub const fn options() -> Config {
206 Config::standard()
207}
208
209pub const fn legacy_options() -> Config {
211 Config::legacy()
212}
213
214#[cfg(feature = "alloc")]
216pub fn serialize<T: nextjson::NsonSerialize + ?Sized>(value: &T) -> Result<Vec<u8>> {
217 Config::standard().serialize(value)
218}
219
220#[cfg(feature = "std")]
222pub fn serialize_into<W: Write, T: nextjson::NsonSerialize + ?Sized>(
223 writer: W,
224 value: &T,
225) -> Result<()> {
226 Config::standard().serialize_into(writer, value)
227}
228
229pub fn serialize_into_slice<T: nextjson::NsonSerialize + ?Sized>(
236 output: &mut [u8],
237 value: &T,
238) -> Result<usize> {
239 Config::standard().serialize_into_slice(output, value)
240}
241
242pub fn serialized_size<T: nextjson::NsonSerialize + ?Sized>(value: &T) -> Result<u64> {
244 Config::standard().serialized_size(value)
245}
246
247pub fn deserialize<'de, T: nextjson::NsonDeserialize<'de>>(input: &'de [u8]) -> Result<T> {
251 Config::standard().deserialize(input)
252}
253
254#[cfg(feature = "std")]
256pub fn deserialize_from<R: Read, T: for<'de> nextjson::NsonDeserialize<'de>>(
257 reader: R,
258) -> Result<T> {
259 Config::standard().deserialize_from(reader)
260}
261
262#[cfg(all(test, feature = "std"))]
263mod tests {
264 use std::{
265 cell::Cell,
266 collections::BTreeMap,
267 io::{self, Cursor, Write},
268 };
269
270 #[cfg(feature = "cbor")]
271 use std::collections::HashMap;
272
273 use super::*;
274
275 #[derive(Debug, PartialEq, nextjson::NsonSerialize, nextjson::NsonDeserialize)]
276 struct Record<'a> {
277 id: u64,
278 delta: i32,
279 #[njson(borrow)]
280 name: &'a str,
281 payload: Vec<u8>,
282 enabled: Option<bool>,
283 }
284
285 #[derive(Debug, PartialEq, nextjson::NsonSerialize, nextjson::NsonDeserialize)]
286 struct BorrowedEnvelope<'a> {
287 #[njson(borrow)]
288 name: &'a str,
289 #[njson(borrow)]
290 payload: &'a str,
291 nested: BorrowedMetadata<'a>,
292 }
293
294 #[derive(Debug, PartialEq, nextjson::NsonSerialize, nextjson::NsonDeserialize)]
295 struct BorrowedMetadata<'a> {
296 #[njson(borrow)]
297 source: &'a str,
298 }
299
300 #[derive(Debug, PartialEq, nextjson::NsonSerialize, nextjson::NsonDeserialize)]
301 enum Event {
302 Idle,
303 Data(u16),
304 Point { x: i64, y: i64 },
305 }
306
307 #[cfg(all(
308 feature = "fingerprint",
309 feature = "reflection",
310 feature = "static-size"
311 ))]
312 #[derive(
313 Debug,
314 nextjson::NsonSerialize,
315 nextjson::NsonDeserialize,
316 crate::Fingerprint,
317 crate::Reflect,
318 crate::StaticSize,
319 )]
320 struct ProtocolRecord {
321 enabled: bool,
322 count: u16,
323 coordinates: [i32; 2],
324 }
325
326 #[cfg(all(
327 feature = "fingerprint",
328 feature = "reflection",
329 feature = "static-size"
330 ))]
331 #[derive(nextjson::NsonSerialize, nextjson::NsonDeserialize, crate::Fingerprint)]
332 struct ChangedProtocolRecord {
333 count: u16,
334 enabled: bool,
335 coordinates: [i32; 2],
336 }
337
338 #[cfg(feature = "reflection")]
339 #[derive(crate::Reflect)]
340 enum ReflectedEvent {
341 Empty,
342 Tuple(u8, bool),
343 Named { code: u16 },
344 }
345
346 #[cfg(all(feature = "bit-packing", feature = "static-size"))]
347 #[derive(Debug, PartialEq, crate::BitPacked, crate::StaticSize)]
348 struct PackedHeader {
349 #[bits = 3]
350 mode: u8,
351 enabled: bool,
352 #[bits = 7]
353 delta: i16,
354 }
355
356 #[cfg(feature = "bit-packing")]
357 #[derive(Debug, PartialEq, crate::BitPacked)]
358 enum PackedEvent {
359 Empty,
360 Flag(bool),
361 Code(#[bits = 4] u8),
362 }
363
364 #[cfg(feature = "schema-evolution")]
365 #[derive(Debug, PartialEq)]
366 struct SchemaV1 {
367 name: String,
368 count: u32,
369 }
370
371 #[cfg(feature = "schema-evolution")]
372 impl SchemaEncode for SchemaV1 {
373 const SCHEMA_ID: u64 = 0x4859_5048_454e_0001;
374 const SCHEMA_VERSION: u32 = 1;
375
376 fn encode_fields(&self, encoder: &mut FieldEncoder) -> Result<()> {
377 encoder.field(2, &self.count)?;
379 encoder.field(1, &self.name)
380 }
381 }
382
383 #[cfg(feature = "schema-evolution")]
384 impl<'de> SchemaDecode<'de> for SchemaV1 {
385 const SCHEMA_ID: u64 = <Self as SchemaEncode>::SCHEMA_ID;
386
387 fn decode_fields(decoder: &mut FieldDecoder<'de>, _version: u32) -> Result<Self> {
388 Ok(Self {
389 name: decoder.required(1)?,
390 count: decoder.required(2)?,
391 })
392 }
393 }
394
395 #[cfg(feature = "schema-evolution")]
396 #[derive(Debug, PartialEq)]
397 struct SchemaV2<'a> {
398 title: &'a str,
399 count: u32,
400 active: bool,
401 source_version: u32,
402 }
403
404 #[cfg(feature = "schema-evolution")]
405 impl SchemaEncode for SchemaV2<'_> {
406 const SCHEMA_ID: u64 = <SchemaV1 as SchemaEncode>::SCHEMA_ID;
407 const SCHEMA_VERSION: u32 = 2;
408
409 fn encode_fields(&self, encoder: &mut FieldEncoder) -> Result<()> {
410 encoder.field(1, self.title)?;
411 encoder.field(2, &self.count)?;
412 encoder.field(3, &self.active)
413 }
414 }
415
416 #[cfg(feature = "schema-evolution")]
417 impl<'de> SchemaDecode<'de> for SchemaV2<'de> {
418 const SCHEMA_ID: u64 = <SchemaV1 as SchemaEncode>::SCHEMA_ID;
419
420 fn decode_fields(decoder: &mut FieldDecoder<'de>, version: u32) -> Result<Self> {
421 Ok(Self {
422 title: decoder.required(1)?,
423 count: decoder.required(2)?,
424 active: decoder.or_default(3)?,
425 source_version: version,
426 })
427 }
428 }
429
430 #[cfg(feature = "schema-evolution")]
431 struct OtherSchema;
432
433 #[cfg(feature = "schema-evolution")]
434 impl<'de> SchemaDecode<'de> for OtherSchema {
435 const SCHEMA_ID: u64 = 0xdead_beef;
436
437 fn decode_fields(_decoder: &mut FieldDecoder<'de>, _version: u32) -> Result<Self> {
438 Ok(Self)
439 }
440 }
441
442 #[cfg(all(feature = "bit-packing", feature = "static-size"))]
443 #[test]
444 fn bit_packed_struct_and_enum_round_trip_within_static_bounds() {
445 let config = options().with_bit_packing();
446 for header in [
447 PackedHeader {
448 mode: 0,
449 enabled: false,
450 delta: -1,
451 },
452 PackedHeader {
453 mode: 7,
454 enabled: true,
455 delta: 63,
456 },
457 ] {
458 let bytes = config.serialize(&header).unwrap();
459 assert_eq!(config.deserialize::<PackedHeader>(&bytes).unwrap(), header);
460 assert!(bytes.len() <= PackedHeader::PACKED_MAX_SIZE);
461 }
462 const { assert!(PackedHeader::MAX_SIZE > 0) };
463 const { assert!(PackedHeader::PACKED_MAX_BITS > 0) };
464
465 for event in [
466 PackedEvent::Empty,
467 PackedEvent::Flag(true),
468 PackedEvent::Code(15),
469 ] {
470 let bytes = config.serialize(&event).unwrap();
471 assert_eq!(config.deserialize::<PackedEvent>(&bytes).unwrap(), event);
472 }
473 }
474
475 #[cfg(feature = "schema-evolution")]
476 #[test]
477 fn schema_evolution_is_forward_compatible_and_rejects_foreign_ids() {
478 let config = options().with_schema_evolution();
479
480 let v1 = SchemaV1 {
482 name: "alpha".into(),
483 count: 7,
484 };
485 let v1_bytes = config.serialize(&v1).unwrap();
486
487 let decoded_v2: SchemaV2<'_> = config.deserialize(&v1_bytes).unwrap();
490 assert_eq!(
491 decoded_v2,
492 SchemaV2 {
493 title: "alpha",
494 count: 7,
495 active: false,
496 source_version: SchemaV1::SCHEMA_VERSION,
497 }
498 );
499
500 let v2 = SchemaV2 {
502 title: "beta",
503 count: 9,
504 active: true,
505 source_version: 0,
506 };
507 let v2_bytes = config.serialize(&v2).unwrap();
508 assert_eq!(
509 config.deserialize::<SchemaV2<'_>>(&v2_bytes).unwrap(),
510 SchemaV2 {
511 title: "beta",
512 count: 9,
513 active: true,
514 source_version: SchemaV2::SCHEMA_VERSION,
515 }
516 );
517
518 assert!(matches!(
520 config.deserialize::<OtherSchema>(&v1_bytes),
521 Err(Error::SchemaMismatch {
522 expected: 0xdead_beef,
523 actual: <SchemaV1 as SchemaEncode>::SCHEMA_ID
524 })
525 ));
526 }
527
528 #[test]
529 fn legacy_fixed_vector_is_stable() {
530 let legacy = legacy_options();
531 let bytes = legacy
532 .serialize(&(0x0102u16, -2i32, "A", Event::Data(9)))
533 .unwrap();
534 assert_eq!(
537 bytes,
538 [
539 0x0a, 0x03, 0x02, 0x01, 0, 0, 0, 0, 0, 0, 0x05, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
542 0xff, 0x09, 1, 0, 0, 0, 0, 0, 0, 0, b'A', 0x0b, 0x09, 4, 0, 0, 0, 0, 0, 0, 0, b'D', b'a', b't', b'a', 0x03, 9, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, ]
550 );
551 assert_eq!(
552 legacy
553 .deserialize::<(u16, i32, String, Event)>(&bytes)
554 .unwrap(),
555 (0x0102, -2, "A".into(), Event::Data(9))
556 );
557 }
558
559 #[test]
560 fn compact_varints_cover_boundaries_and_signed_values() {
561 let config = options();
562 for value in [
563 0u128,
564 250,
565 251,
566 u16::MAX as u128,
567 u16::MAX as u128 + 1,
568 u32::MAX as u128 + 1,
569 u64::MAX as u128 + 1,
570 u128::MAX,
571 ] {
572 let bytes = config.serialize(&value).unwrap();
573 assert_eq!(config.deserialize::<u128>(&bytes).unwrap(), value);
574 }
575 for value in [
576 i128::MIN,
577 i64::MIN as i128,
578 -251,
579 -1,
580 0,
581 1,
582 251,
583 i64::MAX as i128,
584 i128::MAX,
585 ] {
586 let bytes = config.serialize(&value).unwrap();
587 assert_eq!(config.deserialize::<i128>(&bytes).unwrap(), value);
588 }
589 assert_eq!(config.serialize(&250u64).unwrap(), [3, 250]);
590 assert_eq!(config.serialize(&251u64).unwrap(), [3, 251, 251, 0]);
591 }
592
593 #[test]
594 fn compact_v1_golden_vectors_are_stable() {
595 let compact = options();
596 let unsigned: &[(u64, &[u8])] = &[
597 (0, &[3, 0]),
598 (250, &[3, 250]),
599 (251, &[3, 251, 251, 0]),
600 (65_535, &[3, 251, 255, 255]),
601 (65_536, &[3, 252, 0, 0, 1, 0]),
602 (4_294_967_296, &[3, 253, 0, 0, 0, 0, 1, 0, 0, 0]),
603 ];
604 for &(value, golden) in unsigned {
605 assert_eq!(compact.serialize(&value).unwrap(), golden);
606 assert_eq!(compact.deserialize::<u64>(golden).unwrap(), value);
607 }
608
609 let record = Record {
610 id: 42,
611 delta: -7,
612 name: "zero-copy",
613 payload: vec![0, 1, 255],
614 enabled: Some(true),
615 };
616 let golden = [
617 0x0b, 0x09, 2, b'i', b'd', 0x03, 42, 0x09, 5, b'd', b'e', b'l', b't', b'a', 0x05, 13, 0x09, 4, b'n', b'a', b'm', b'e', 0x09, 9, b'z', b'e', b'r', b'o', b'-', b'c', b'o',
621 b'p', b'y', 0x09, 7, b'p', b'a', b'y', b'l', b'o', b'a', b'd', 0x0a, 0x03, 0, 0x03, 1, 0x03, 251, 255, 0, 0xff, 0x09, 7, b'e', b'n', b'a', b'b', b'l', b'e', b'd', 0x02, 0xff, ];
628 assert_eq!(compact.serialize(&record).unwrap(), golden);
629 assert_eq!(compact.deserialize::<Record<'_>>(&golden).unwrap(), record);
630
631 let big_fixed = compact.with_big_endian().with_fixint_encoding();
632 assert_eq!(
633 big_fixed.serialize(&(0x0102u16, -2i32, 1.5f32)).unwrap(),
634 [
635 0x0a, 0x03, 0, 0, 0, 0, 0, 0, 1, 2, 0x05, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
638 0xfe, 0x08, 0x3f, 0xc0, 0x00, 0x00, 0xff, ]
642 );
643 }
644
645 #[test]
646 fn round_trips_full_data_model_and_borrows_strings() {
647 let record = Record {
648 id: 42,
649 delta: -7,
650 name: "zero-copy",
651 payload: vec![0, 1, 255],
652 enabled: Some(true),
653 };
654 let bytes = options().serialize(&record).unwrap();
655 let decoded: Record<'_> = options().deserialize(&bytes).unwrap();
656 assert_eq!(decoded, record);
657 let start = bytes.as_ptr() as usize;
658 assert!((start..start + bytes.len()).contains(&(decoded.name.as_ptr() as usize)));
659
660 for event in [
661 Event::Idle,
662 Event::Data(65535),
663 Event::Point { x: -9, y: 17 },
664 ] {
665 let encoded = options().serialize(&event).unwrap();
666 assert_eq!(options().deserialize::<Event>(&encoded).unwrap(), event);
667 }
668 }
669
670 #[test]
671 fn nested_borrowed_fields_point_into_the_input_frame() {
672 let value = BorrowedEnvelope {
673 name: "zero-copy",
674 payload: "borrowed-payload",
675 nested: BorrowedMetadata { source: "edge-07" },
676 };
677 let config = options().with_limit(1024);
678 let frame = config.serialize(&value).unwrap();
679 let decoded: BorrowedEnvelope<'_> = config.deserialize(&frame).unwrap();
680 assert_eq!(decoded, value);
681
682 let start = frame.as_ptr() as usize;
683 let end = start + frame.len();
684 for borrowed in [
685 decoded.name.as_bytes(),
686 decoded.payload.as_bytes(),
687 decoded.nested.source.as_bytes(),
688 ] {
689 let pointer = borrowed.as_ptr() as usize;
690 assert!(pointer >= start && pointer + borrowed.len() <= end);
691 }
692 }
693
694 #[test]
695 fn supports_endianness_floats_chars_maps_and_non_finite_values() {
696 assert_eq!(
697 options()
698 .with_big_endian()
699 .with_fixint_encoding()
700 .serialize(&0x0102u16)
701 .unwrap(),
702 [3, 0, 0, 0, 0, 0, 0, 1, 2]
703 );
704 for value in ['a', 'é', '汉', '🚀'] {
705 let bytes = options().serialize(&value).unwrap();
706 assert_eq!(options().deserialize::<char>(&bytes).unwrap(), value);
707 }
708 let map = BTreeMap::from([
709 ("one".to_owned(), "1".to_owned()),
710 ("two".to_owned(), "2".to_owned()),
711 ]);
712 let bytes = options().serialize(&map).unwrap();
713 assert_eq!(
714 options()
715 .deserialize::<BTreeMap<String, String>>(&bytes)
716 .unwrap(),
717 map
718 );
719 let nan = f64::NAN;
720 assert!(options()
721 .deserialize::<f64>(&options().serialize(&nan).unwrap())
722 .unwrap()
723 .is_nan());
724 }
725
726 #[test]
727 fn streaming_size_limits_and_trailing_policy_are_enforced() {
728 let value = vec![1u32, 2, 3, 65_536];
729 let config = options().with_limit(64);
730 let mut stream = Vec::new();
731 config.serialize_into(&mut stream, &value).unwrap();
732 assert_eq!(config.serialized_size(&value).unwrap(), stream.len() as u64);
733 assert_eq!(
734 config
735 .deserialize_from::<_, Vec<u32>>(Cursor::new(&stream))
736 .unwrap(),
737 value
738 );
739 assert!(matches!(
740 options().with_limit(2).serialize(&u64::MAX),
741 Err(Error::SizeLimit { limit: 2 })
742 ));
743
744 let mut trailing = options().serialize(&7u8).unwrap();
745 trailing.push(8);
746 assert!(matches!(
747 options().deserialize::<u8>(&trailing),
748 Err(Error::TrailingBytes { remaining: 1 })
749 ));
750 assert_eq!(
751 options()
752 .allow_trailing_bytes()
753 .deserialize::<u8>(&trailing)
754 .unwrap(),
755 7
756 );
757 }
758
759 #[test]
760 fn malformed_inputs_are_rejected_without_panics() {
761 assert!(options().deserialize::<bool>(&[2]).unwrap());
763 assert!(matches!(
764 options().deserialize::<Option<u8>>(&[3]),
765 Err(Error::UnexpectedEnd)
766 ));
767 assert!(matches!(
768 options().deserialize::<u64>(&[255]),
769 Err(Error::Custom(_))
770 ));
771 assert!(matches!(
772 options().deserialize::<u64>(&[251, 1, 0]),
773 Err(Error::Custom(_))
774 ));
775 assert!(matches!(
776 options().deserialize::<char>(&[0x09, 2, b'a', b'b']),
777 Err(Error::InvalidChar)
778 ));
779 let mut hostile = vec![0x0a];
781 hostile.extend(std::iter::repeat_n(0x00, 65));
782 hostile.push(0xff);
783 assert!(matches!(
784 legacy_options()
785 .with_collection_limit(64)
786 .deserialize::<Vec<()>>(&hostile),
787 Err(Error::CollectionLimit { limit: 64 })
788 ));
789
790 for len in 0..48 {
791 for fill in [0, 1, 0x7f, 0xfb, 0xff] {
792 let input = vec![fill; len];
793 assert!(
794 std::panic::catch_unwind(|| options().deserialize::<Record<'_>>(&input))
795 .is_ok()
796 );
797 }
798 }
799 }
800
801 struct Stateful<'a>(&'a Cell<u8>);
802
803 impl nextjson::NsonSchema for Stateful<'_> {
804 const SCHEMA: nextjson::TypeSchema = nextjson::TypeSchema::U8;
805 }
806 impl nextjson::NsonSerialize for Stateful<'_> {
807 fn nextencode<E: nextjson::FormatEncoder>(
808 &self,
809 encoder: &mut E,
810 ) -> ::core::result::Result<(), E::Error> {
811 let next = self.0.get() + 1;
812 self.0.set(next);
813 encoder.write_u64(next as u64)
814 }
815 }
816
817 struct UnbalancedContainer;
818
819 impl nextjson::NsonSchema for UnbalancedContainer {
820 const SCHEMA: nextjson::TypeSchema = nextjson::TypeSchema::Seq(&nextjson::TypeSchema::Unit);
821 }
822 impl nextjson::NsonSerialize for UnbalancedContainer {
823 fn nextencode<E: nextjson::FormatEncoder>(
824 &self,
825 encoder: &mut E,
826 ) -> ::core::result::Result<(), E::Error> {
827 encoder.end_array()
829 }
830 }
831
832 struct FailingWriter {
833 remaining: usize,
834 }
835
836 #[cfg(any(feature = "compression", feature = "encryption"))]
837 struct HeaderOnlyReader {
838 header: Cursor<Vec<u8>>,
839 }
840
841 #[cfg(any(feature = "compression", feature = "encryption"))]
842 impl Read for HeaderOnlyReader {
843 fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
844 if self.header.position() == self.header.get_ref().len() as u64 {
845 panic!("frame body must not be read after a rejected header");
846 }
847 self.header.read(output)
848 }
849 }
850
851 impl Write for FailingWriter {
852 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
853 if self.remaining == 0 {
854 return Err(io::Error::new(io::ErrorKind::BrokenPipe, "test writer"));
855 }
856 let written = self.remaining.min(bytes.len());
857 self.remaining -= written;
858 Ok(written)
859 }
860
861 fn flush(&mut self) -> io::Result<()> {
862 Ok(())
863 }
864 }
865
866 #[test]
867 fn serializer_runs_once_and_io_failures_are_preserved() {
868 let calls = Cell::new(0);
869 assert_eq!(options().serialize(&Stateful(&calls)).unwrap(), [3, 1]);
870 assert_eq!(calls.get(), 1);
871 assert!(matches!(
872 options().serialize(&UnbalancedContainer),
873 Err(Error::Custom(_))
874 ));
875 let failure = options().serialize_into(FailingWriter { remaining: 2 }, &u64::MAX);
876 assert!(
877 matches!(failure, Err(Error::Io(error)) if error.kind() == io::ErrorKind::BrokenPipe)
878 );
879 }
880
881 #[test]
882 fn slice_serialization_is_single_pass_and_allocation_free() {
883 let value = (513u16, "zero allocation", vec![1u8, 2, 3]);
884 let expected = options().serialize(&value).unwrap();
885 let mut exact = [0u8; 64];
886 let written = options().serialize_into_slice(&mut exact, &value).unwrap();
887 assert_eq!(&exact[..written], expected);
888
889 let calls = Cell::new(0);
890 let mut two = [0u8; 2];
891 assert_eq!(
892 options()
893 .serialize_into_slice(&mut two, &Stateful(&calls))
894 .unwrap(),
895 2
896 );
897 assert_eq!(calls.get(), 1);
898
899 let mut short = [0u8; 3];
900 assert!(matches!(
901 options().serialize_into_slice(&mut short, &value),
902 Err(Error::BufferTooSmall {
903 required,
904 available: 3
905 }) if required == expected.len()
906 ));
907 assert_eq!(&short, &expected[..3]);
908 }
909
910 #[cfg(all(
911 feature = "fingerprint",
912 feature = "reflection",
913 feature = "static-size"
914 ))]
915 #[test]
916 fn derives_produce_checked_schema_bounds_and_reflection() {
917 let value = ProtocolRecord {
918 enabled: true,
919 count: 513,
920 coordinates: [-1, i32::MAX],
921 };
922 assert!(options().serialize(&value).unwrap().len() <= ProtocolRecord::MAX_SIZE);
923 assert!(legacy_options().serialize(&value).unwrap().len() <= ProtocolRecord::MAX_SIZE);
924 const { assert!(ProtocolRecord::MAX_SIZE > 0) };
925 const { assert!(ProtocolRecord::PACKED_MAX_BITS > 0) };
926
927 let TypeShape::Struct(fields) = ProtocolRecord::SHAPE else {
928 panic!("record must reflect as a struct");
929 };
930 assert_eq!(fields.len(), 3);
931 assert_eq!(fields[0].name, "enabled");
932 assert_eq!(fields[1].type_name, "u16");
933 assert_eq!(fields[2].index, 2);
934
935 let TypeShape::Enum(variants) = ReflectedEvent::SHAPE else {
936 panic!("event must reflect as an enum");
937 };
938 let _constructed = (
939 ReflectedEvent::Empty,
940 ReflectedEvent::Tuple(1, true),
941 ReflectedEvent::Named { code: 2 },
942 );
943 let ReflectedEvent::Tuple(tuple_number, tuple_flag) = _constructed.1 else {
944 unreachable!()
945 };
946 let ReflectedEvent::Named { code: named_code } = _constructed.2 else {
947 unreachable!()
948 };
949 assert_eq!((tuple_number, tuple_flag, named_code), (1, true, 2));
950 assert_eq!(variants[1].name, "Tuple");
951 assert_eq!(variants[1].fields[0].name, "0");
952 assert_eq!(variants[2].fields[0].type_name, "u16");
953
954 assert_ne!(
955 ProtocolRecord::TYPE_FINGERPRINT,
956 ChangedProtocolRecord::TYPE_FINGERPRINT
957 );
958 assert_ne!(
959 ProtocolRecord::fingerprint(options()),
960 ProtocolRecord::fingerprint(options().with_big_endian())
961 );
962 assert_ne!(
963 ProtocolRecord::fingerprint(options()),
964 ProtocolRecord::fingerprint(options().with_fixint_encoding())
965 );
966 }
967
968 #[cfg(all(
969 feature = "fingerprint",
970 feature = "reflection",
971 feature = "static-size"
972 ))]
973 #[test]
974 fn fingerprint_frames_reject_schema_and_configuration_drift() {
975 let value = ProtocolRecord {
976 enabled: true,
977 count: 7,
978 coordinates: [2, 3],
979 };
980 let framed = options().with_fingerprint().serialize(&value).unwrap();
981 let decoded: ProtocolRecord = options().with_fingerprint().deserialize(&framed).unwrap();
982 assert_eq!(decoded.count, value.count);
983
984 assert!(matches!(
985 options()
986 .with_fingerprint()
987 .deserialize::<ChangedProtocolRecord>(&framed),
988 Err(Error::SchemaMismatch { .. })
989 ));
990 assert!(matches!(
991 options()
992 .with_big_endian()
993 .with_fingerprint()
994 .deserialize::<ProtocolRecord>(&framed),
995 Err(Error::SchemaMismatch { .. })
996 ));
997
998 let mut output = [0u8; 128];
999 let written = options()
1000 .with_fingerprint()
1001 .serialize_into_slice(&mut output, &value)
1002 .unwrap();
1003 assert_eq!(&output[..written], framed);
1004 assert_eq!(
1005 options()
1006 .with_fingerprint()
1007 .serialized_size(&value)
1008 .unwrap(),
1009 written as u64
1010 );
1011
1012 let mut corrupt = framed.clone();
1013 corrupt[0] = 0;
1014 assert!(matches!(
1015 options()
1016 .with_fingerprint()
1017 .deserialize::<ProtocolRecord>(&corrupt),
1018 Err(Error::InvalidFrame("bad fingerprint magic"))
1019 ));
1020 }
1021
1022 #[cfg(feature = "cbor")]
1023 #[test]
1024 fn cbor_matches_rfc_vectors_and_deterministic_map_order() {
1025 assert_eq!(
1028 options().with_cbor_format().serialize(&0u8).unwrap(),
1029 [0x00]
1030 );
1031 assert_eq!(
1032 options().with_cbor_format().serialize(&24u8).unwrap(),
1033 [0x18, 0x18]
1034 );
1035 assert_eq!(
1036 options().with_cbor_format().serialize("a").unwrap(),
1037 [0x61, b'a']
1038 );
1039 assert_eq!(
1040 options()
1041 .with_cbor_format()
1042 .serialize(&vec![1u8, 2, 3])
1043 .unwrap(),
1044 [0x9f, 0x01, 0x02, 0x03, 0xff]
1045 );
1046
1047 let first = HashMap::from([("aa", 1u8), ("b", 2)]);
1048 let second = HashMap::from([("b", 2u8), ("aa", 1)]);
1049 let deterministic = options().with_cbor_format().with_deterministic_encoding();
1050 let encoded = deterministic.serialize(&first).unwrap();
1051 assert_eq!(encoded, deterministic.serialize(&second).unwrap());
1052 assert_eq!(
1053 encoded,
1054 [0xbf, 0x61, b'b', 0x02, 0x62, b'a', b'a', 0x01, 0xff]
1055 );
1056 assert_eq!(
1057 deterministic
1058 .deserialize::<HashMap<String, u8>>(&encoded)
1059 .unwrap(),
1060 HashMap::from([("aa".into(), 1), ("b".into(), 2)])
1061 );
1062
1063 let mut trailing = encoded.clone();
1064 trailing.push(0);
1065 assert!(matches!(
1066 deterministic.deserialize::<HashMap<String, u8>>(&trailing),
1067 Err(Error::Cbor(_))
1068 ));
1069 assert!(matches!(
1070 deterministic.deserialize_from::<_, HashMap<String, u8>>(Cursor::new(&trailing)),
1071 Err(Error::Cbor(_))
1072 ));
1073 assert_eq!(
1074 options()
1075 .with_limit(1)
1076 .with_cbor_format()
1077 .deserialize_from::<_, u8>(Cursor::new([0x00]))
1078 .unwrap(),
1079 0
1080 );
1081 assert!(matches!(
1082 options()
1083 .with_limit(2)
1084 .with_cbor_format()
1085 .serialize(&vec![1u8, 2, 3]),
1086 Err(Error::SizeLimit { limit: 2 })
1087 ));
1088 }
1089
1090 #[cfg(all(
1091 feature = "cbor",
1092 feature = "fingerprint",
1093 feature = "reflection",
1094 feature = "static-size"
1095 ))]
1096 #[test]
1097 fn cbor_fingerprint_covers_format_and_determinism() {
1098 let value = ProtocolRecord {
1099 enabled: false,
1100 count: 9,
1101 coordinates: [4, 5],
1102 };
1103 let binary = ProtocolRecord::fingerprint(options());
1104 let regular = options().with_cbor_format();
1105 let deterministic = regular.with_deterministic_encoding();
1106 assert_ne!(binary, regular.fingerprint::<ProtocolRecord>());
1107 assert_ne!(
1108 regular.fingerprint::<ProtocolRecord>(),
1109 deterministic.fingerprint::<ProtocolRecord>()
1110 );
1111
1112 let frame = deterministic.with_fingerprint().serialize(&value).unwrap();
1113 let decoded: ProtocolRecord = deterministic
1114 .with_fingerprint()
1115 .deserialize(&frame)
1116 .unwrap();
1117 assert_eq!(decoded.coordinates, value.coordinates);
1118 assert!(matches!(
1119 regular
1120 .with_fingerprint()
1121 .deserialize::<ProtocolRecord>(&frame),
1122 Err(Error::SchemaMismatch { .. })
1123 ));
1124 }
1125
1126 #[cfg(feature = "compression")]
1127 #[test]
1128 fn compression_is_adaptive_bounded_and_round_trips() {
1129 let repeated = vec![0u8; 2000];
1132 let compressed = options()
1133 .with_limit(8192)
1134 .with_zstd_compression(3)
1135 .with_compression_threshold(128);
1136 let frame = compressed.serialize(&repeated).unwrap();
1137 assert_eq!(&frame[..4], b"RBZ1");
1138 assert_eq!(u16::from_le_bytes([frame[6], frame[7]]), 1);
1139 assert!(frame.len() < repeated.len() / 4);
1140 assert_eq!(compressed.deserialize::<Vec<u8>>(&frame).unwrap(), repeated);
1141
1142 let small = options().with_zstd_compression(3).serialize(&7u8).unwrap();
1143 assert_eq!(u16::from_le_bytes([small[6], small[7]]), 0);
1144 assert_eq!(
1145 options()
1146 .with_zstd_compression(3)
1147 .deserialize::<u8>(&small)
1148 .unwrap(),
1149 7
1150 );
1151
1152 let mut hostile = frame.clone();
1153 hostile[8..16].copy_from_slice(&8193u64.to_le_bytes());
1154 assert!(matches!(
1155 compressed.deserialize::<Vec<u8>>(&hostile),
1156 Err(Error::SizeLimit { limit: 8192 })
1157 ));
1158 assert!(matches!(
1159 compressed.deserialize::<Vec<u8>>(&frame[..frame.len() - 1]),
1160 Err(Error::UnexpectedEnd)
1161 ));
1162 }
1163
1164 #[cfg(feature = "compression")]
1165 #[test]
1166 fn compressed_stream_rejects_oversized_header_before_reading_body() {
1167 let mut header = Vec::from(*b"RBZ1");
1168 header.extend_from_slice(&1u16.to_le_bytes());
1169 header.extend_from_slice(&1u16.to_le_bytes());
1170 header.extend_from_slice(&1025u64.to_le_bytes());
1171 header.extend_from_slice(&1u64.to_le_bytes());
1172 let reader = HeaderOnlyReader {
1173 header: Cursor::new(header),
1174 };
1175
1176 assert!(matches!(
1177 options()
1178 .with_limit(1024)
1179 .with_zstd_compression(3)
1180 .deserialize_from::<_, Vec<u8>>(reader),
1181 Err(Error::SizeLimit { limit: 1024 })
1182 ));
1183 }
1184
1185 #[cfg(all(feature = "compression", feature = "cbor"))]
1186 #[test]
1187 fn deterministic_cbor_can_be_compressed_as_one_pipeline() {
1188 let value = BTreeMap::from([("payload".to_owned(), "x".repeat(2048))]);
1189 let config = options()
1190 .with_cbor_format()
1191 .with_deterministic_encoding()
1192 .with_zstd_compression(5)
1193 .with_compression_threshold(64);
1194 let frame = config.serialize(&value).unwrap();
1195 assert_eq!(
1196 config
1197 .deserialize::<BTreeMap<String, String>>(&frame)
1198 .unwrap(),
1199 value
1200 );
1201 }
1202
1203 #[cfg(feature = "encryption")]
1204 #[test]
1205 fn authenticated_encryption_uses_random_nonces_and_rejects_tampering() {
1206 let value = (42u64, "classified".to_owned(), vec![7u8; 512]);
1207 let config = options()
1208 .with_limit(4096)
1209 .with_encryption(EncryptionKey::new([0x42; 32]));
1210 assert_eq!(
1211 format!("{:?}", EncryptionKey::new([0x42; 32])),
1212 "EncryptionKey([REDACTED])"
1213 );
1214
1215 let first = config.serialize(&value).unwrap();
1216 let second = config.serialize(&value).unwrap();
1217 assert_eq!(&first[..4], b"RBX1");
1218 assert_ne!(&first[8..32], &second[8..32]);
1219 assert_ne!(first, second);
1220 assert_eq!(
1221 config
1222 .deserialize::<(u64, String, Vec<u8>)>(&first)
1223 .unwrap(),
1224 value
1225 );
1226
1227 let mut tampered = first.clone();
1228 *tampered.last_mut().unwrap() ^= 1;
1229 assert!(matches!(
1230 config.deserialize::<(u64, String, Vec<u8>)>(&tampered),
1231 Err(Error::Encryption)
1232 ));
1233 let wrong_key = options()
1234 .with_limit(4096)
1235 .with_encryption(EncryptionKey::new([0x24; 32]));
1236 assert!(matches!(
1237 wrong_key.deserialize::<(u64, String, Vec<u8>)>(&first),
1238 Err(Error::Encryption)
1239 ));
1240
1241 let mut hostile = first.clone();
1242 hostile[32..40].copy_from_slice(&4097u64.to_le_bytes());
1243 hostile[40..48].copy_from_slice(&4113u64.to_le_bytes());
1244 assert!(matches!(
1245 config.deserialize::<(u64, String, Vec<u8>)>(&hostile),
1246 Err(Error::SizeLimit { limit: 4096 })
1247 ));
1248
1249 let mut stream = Cursor::new([first.as_slice(), b"next-frame"].concat());
1250 assert_eq!(
1251 config
1252 .deserialize_from::<_, (u64, String, Vec<u8>)>(&mut stream)
1253 .unwrap(),
1254 value
1255 );
1256 assert_eq!(stream.position(), first.len() as u64);
1257 }
1258
1259 #[cfg(feature = "encryption")]
1260 #[test]
1261 fn encrypted_stream_rejects_oversized_header_before_reading_body() {
1262 let mut header = Vec::from(*b"RBX1");
1265 header.extend_from_slice(&1u16.to_le_bytes());
1266 header.extend_from_slice(&1u16.to_le_bytes());
1267 header.extend_from_slice(&[0u8; 24]); header.extend_from_slice(&1025u64.to_le_bytes()); header.extend_from_slice(&1041u64.to_le_bytes()); let reader = HeaderOnlyReader {
1271 header: Cursor::new(header),
1272 };
1273
1274 assert!(matches!(
1275 options()
1276 .with_limit(1024)
1277 .with_encryption(EncryptionKey::new([0x42; 32]))
1278 .deserialize_from::<_, (u64, String, Vec<u8>)>(reader),
1279 Err(Error::SizeLimit { limit: 1024 })
1280 ));
1281 }
1282}