Skip to main content

timeseries_table_format/coverage/
serde.rs

1//! Serialization and deserialization of coverage values.
2//!
3//! Global [`Coverage`] values keep their historical RoaringTreemap format.
4//! [`EntityCoverage`] values use a separate, identified format that length
5//! prefixes identity components and nested coverage payloads.
6//!
7//! # Global serialization format
8//!
9//! Coverage data is serialized to bytes using the RoaringTreemap binary format
10//! (portable across platforms). The byte format is opaque and should not be
11//! interpreted directly; always use [`coverage_from_bytes`] to deserialize.
12//!
13//! # Entity-aware V2 serialization format
14//!
15//! All integer fields outside nested coverage are big-endian:
16//!
17//! ```text
18//! "TSTECOV2"
19//! entity_count: u32
20//! repeated entity_count times:
21//!   component_count: u32
22//!   repeated component_count times:
23//!     component_type: u8
24//!     component_value:
25//!       Utf8: byte_len: u64, then UTF-8 bytes
26//!       Int32: big-endian i32
27//!       Int64: big-endian i64
28//!       UInt64: big-endian u64
29//!   nested_coverage_byte_len: u64
30//!   nested_historical_roaring_treemap_bytes
31//! ```
32//!
33use std::{io::Cursor, str::Utf8Error};
34
35use roaring::{RoaringBitmap, RoaringTreemap};
36use snafu::{Backtrace, ResultExt, Snafu};
37
38use crate::coverage::{Coverage, EntityCoverage, EntityIdentity, EntityIdentityError, EntityValue};
39
40const ENTITY_COVERAGE_MAGIC: &[u8; 8] = b"TSTECOV2";
41const ENTITY_VALUE_UTF8: u8 = 1;
42const ENTITY_VALUE_INT32: u8 = 2;
43const ENTITY_VALUE_INT64: u8 = 3;
44const ENTITY_VALUE_UINT64: u8 = 4;
45
46/// Errors from the global and entity-aware coverage codecs.
47#[derive(Debug, Snafu)]
48#[non_exhaustive]
49pub enum CoverageCodecError {
50    /// I/O error during serialization of a coverage bitmap.
51    #[snafu(display("Failed to serialize roaring bitmap: {source}"))]
52    BitmapSerialization {
53        /// The underlying I/O error.
54        source: std::io::Error,
55        /// Backtrace captured at the codec boundary.
56        backtrace: Backtrace,
57    },
58
59    /// I/O error during deserialization of a coverage bitmap.
60    #[snafu(display("Failed to deserialize roaring bitmap: {source}"))]
61    BitmapDeserialization {
62        /// The underlying I/O error.
63        source: std::io::Error,
64        /// Backtrace captured at the codec boundary.
65        backtrace: Backtrace,
66    },
67
68    /// An in-memory length cannot be represented by the format.
69    #[snafu(display("Entity coverage {field} is too large to serialize"))]
70    LengthOverflow {
71        /// The field whose length overflowed.
72        field: &'static str,
73        /// Backtrace captured at the codec boundary.
74        backtrace: Backtrace,
75    },
76
77    /// The payload is not entity-aware coverage.
78    #[snafu(display("Invalid entity coverage payload identifier"))]
79    InvalidEntityCoverageMagic {
80        /// Backtrace captured at the codec boundary.
81        backtrace: Backtrace,
82    },
83
84    /// A fixed-size field is incomplete.
85    #[snafu(display("Truncated entity coverage payload"))]
86    TruncatedPayload {
87        /// Backtrace captured at the codec boundary.
88        backtrace: Backtrace,
89    },
90
91    /// A declared count or length is not valid for the remaining payload.
92    #[snafu(display("Invalid entity coverage {field}"))]
93    InvalidLength {
94        /// The invalid field.
95        field: &'static str,
96        /// Backtrace captured at the codec boundary.
97        backtrace: Backtrace,
98    },
99
100    /// An identity component is not valid UTF-8.
101    #[snafu(display("Invalid entity identity string: {source}"))]
102    InvalidEntityUtf8 {
103        /// The UTF-8 validation error.
104        source: Utf8Error,
105        /// Backtrace captured at the codec boundary.
106        backtrace: Backtrace,
107    },
108
109    /// An identity component uses an unknown scalar type tag.
110    #[snafu(display("Unknown entity identity value type tag: {tag}"))]
111    UnknownEntityValueTag {
112        /// Unrecognized encoded type tag.
113        tag: u8,
114        /// Backtrace captured at the codec boundary.
115        backtrace: Backtrace,
116    },
117
118    /// An encoded identity is incomplete.
119    #[snafu(display("Invalid entity identity: {source}"))]
120    InvalidEntityIdentity {
121        /// The identity validation error.
122        source: EntityIdentityError,
123        /// Backtrace captured at the codec boundary.
124        backtrace: Backtrace,
125    },
126
127    /// The payload contains the same identity more than once.
128    #[snafu(display("Duplicate entity identity in coverage payload: {identity:?}"))]
129    DuplicateEntityIdentity {
130        /// The repeated identity.
131        identity: EntityIdentity,
132        /// Backtrace captured at the codec boundary.
133        backtrace: Backtrace,
134    },
135
136    /// A nested RoaringTreemap payload is malformed.
137    #[snafu(display("Malformed nested entity coverage: {source}"))]
138    MalformedNestedCoverage {
139        /// The nested global coverage error.
140        #[snafu(source(from(CoverageCodecError, Box::new)), backtrace)]
141        source: Box<CoverageCodecError>,
142    },
143
144    /// Bytes remain after the declared coverage payload.
145    #[snafu(display("Trailing bytes after coverage payload"))]
146    TrailingBytes {
147        /// Backtrace captured at the codec boundary.
148        backtrace: Backtrace,
149    },
150}
151
152/// Serialize a coverage bitmap to a byte vector.
153///
154/// Converts the given [`Coverage`] instance to its RoaringTreemap binary representation,
155/// which can be written to disk or transmitted over the network.
156///
157/// # Arguments
158///
159/// * `cov` - The coverage instance to serialize.
160///
161/// # Returns
162///
163/// A vector of bytes in RoaringTreemap binary format, or an error if serialization fails.
164///
165/// # Errors
166///
167/// Returns [`CoverageCodecError::BitmapSerialization`] if bitmap serialization fails.
168pub fn coverage_to_bytes(cov: &Coverage) -> Result<Vec<u8>, CoverageCodecError> {
169    let mut out = Vec::new();
170    {
171        let mut w = Cursor::new(&mut out);
172        cov.present()
173            .serialize_into(&mut w)
174            .context(BitmapSerializationSnafu)?;
175    }
176    Ok(out)
177}
178
179/// Deserialize a coverage bitmap from bytes.
180///
181/// Reconstructs a [`Coverage`] instance from bytes previously written by [`coverage_to_bytes`].
182/// The byte format is the RoaringTreemap portable binary representation.
183///
184/// # Arguments
185///
186/// * `bytes` - A byte slice in RoaringTreemap binary format.
187///
188/// # Returns
189///
190/// A reconstructed [`Coverage`] instance, or an error if deserialization fails.
191///
192/// # Errors
193///
194/// Returns [`CoverageCodecError::BitmapDeserialization`] for an invalid bitmap
195/// or [`CoverageCodecError::TrailingBytes`] for appended data.
196pub fn coverage_from_bytes(bytes: &[u8]) -> Result<Coverage, CoverageCodecError> {
197    let mut r = Cursor::new(bytes);
198    let present = RoaringTreemap::deserialize_from(&mut r).context(BitmapDeserializationSnafu)?;
199
200    if r.position() != bytes.len() as u64 {
201        return TrailingBytesSnafu.fail();
202    }
203
204    Ok(Coverage::from_treemap(present))
205}
206
207/// Serialize entity-scoped coverage in canonical identity order.
208///
209/// # Errors
210///
211/// Returns [`CoverageCodecError`] if a count cannot be represented or a
212/// nested coverage bitmap cannot be serialized.
213pub fn entity_coverage_to_bytes(coverage: &EntityCoverage) -> Result<Vec<u8>, CoverageCodecError> {
214    let entity_count = u32::try_from(coverage.identity_count()).map_err(|_| {
215        LengthOverflowSnafu {
216            field: "entity count",
217        }
218        .build()
219    })?;
220
221    let mut out = Vec::new();
222    out.extend_from_slice(ENTITY_COVERAGE_MAGIC);
223    out.extend_from_slice(&entity_count.to_be_bytes());
224
225    for (identity, nested) in coverage.iter() {
226        let component_count = u32::try_from(identity.components().len()).map_err(|_| {
227            LengthOverflowSnafu {
228                field: "identity component count",
229            }
230            .build()
231        })?;
232        out.extend_from_slice(&component_count.to_be_bytes());
233
234        for component in identity.components() {
235            match component {
236                EntityValue::Utf8(value) => {
237                    out.push(ENTITY_VALUE_UTF8);
238                    let component_len = u64::try_from(value.len()).map_err(|_| {
239                        LengthOverflowSnafu {
240                            field: "identity component length",
241                        }
242                        .build()
243                    })?;
244                    out.extend_from_slice(&component_len.to_be_bytes());
245                    out.extend_from_slice(value.as_bytes());
246                }
247                EntityValue::Int32(value) => {
248                    out.push(ENTITY_VALUE_INT32);
249                    out.extend_from_slice(&value.to_be_bytes());
250                }
251                EntityValue::Int64(value) => {
252                    out.push(ENTITY_VALUE_INT64);
253                    out.extend_from_slice(&value.to_be_bytes());
254                }
255                EntityValue::UInt64(value) => {
256                    out.push(ENTITY_VALUE_UINT64);
257                    out.extend_from_slice(&value.to_be_bytes());
258                }
259            }
260        }
261
262        let nested_bytes = canonical_nested_coverage_to_bytes(nested)?;
263        let nested_len = u64::try_from(nested_bytes.len()).map_err(|_| {
264            LengthOverflowSnafu {
265                field: "nested coverage length",
266            }
267            .build()
268        })?;
269        out.extend_from_slice(&nested_len.to_be_bytes());
270        out.extend_from_slice(&nested_bytes);
271    }
272
273    Ok(out)
274}
275
276/// Deserialize the distinct entity-aware coverage format.
277///
278/// # Errors
279///
280/// Returns [`CoverageCodecError`] for malformed, ambiguous, truncated,
281/// or non-entity-aware input.
282pub fn entity_coverage_from_bytes(bytes: &[u8]) -> Result<EntityCoverage, CoverageCodecError> {
283    let mut remaining = bytes;
284    if take(&mut remaining, ENTITY_COVERAGE_MAGIC.len())? != ENTITY_COVERAGE_MAGIC {
285        return InvalidEntityCoverageMagicSnafu.fail();
286    }
287
288    let entity_count = read_u32(&mut remaining)? as usize;
289    let mut coverage = EntityCoverage::empty();
290    for _ in 0..entity_count {
291        let component_count = read_u32(&mut remaining)? as usize;
292        if component_count > remaining.len().saturating_sub(8) / 5 {
293            return InvalidLengthSnafu {
294                field: "identity component count",
295            }
296            .fail();
297        }
298
299        let mut components = Vec::new();
300        for _ in 0..component_count {
301            let tag = take(&mut remaining, 1)?[0];
302            let component = match tag {
303                ENTITY_VALUE_UTF8 => {
304                    let component_len = read_u64(&mut remaining)?;
305                    let component_bytes =
306                        take_declared(&mut remaining, component_len, "identity component length")?;
307                    let component =
308                        std::str::from_utf8(component_bytes).context(InvalidEntityUtf8Snafu)?;
309                    EntityValue::Utf8(component.to_owned())
310                }
311                ENTITY_VALUE_INT32 => EntityValue::Int32(read_i32(&mut remaining)?),
312                ENTITY_VALUE_INT64 => EntityValue::Int64(read_i64(&mut remaining)?),
313                ENTITY_VALUE_UINT64 => EntityValue::UInt64(read_u64(&mut remaining)?),
314                tag => return UnknownEntityValueTagSnafu { tag }.fail(),
315            };
316            components.push(component);
317        }
318
319        let identity = EntityIdentity::try_new(components).context(InvalidEntityIdentitySnafu)?;
320        if coverage.get(&identity).is_some() {
321            return DuplicateEntityIdentitySnafu { identity }.fail();
322        }
323
324        let nested_len = read_u64(&mut remaining)?;
325        let nested_bytes = take_declared(&mut remaining, nested_len, "nested coverage length")?;
326        let nested = coverage_from_bytes(nested_bytes).context(MalformedNestedCoverageSnafu)?;
327        coverage.union_coverage(identity, nested);
328    }
329
330    if !remaining.is_empty() {
331        return TrailingBytesSnafu.fail();
332    }
333    Ok(coverage)
334}
335
336/// Serialize after removing empty partitions and construction-history-dependent
337/// Roaring container choices from entity-aware nested coverage.
338fn canonical_nested_coverage_to_bytes(coverage: &Coverage) -> Result<Vec<u8>, CoverageCodecError> {
339    let present = RoaringTreemap::from_bitmaps(
340        coverage
341            .present()
342            .bitmaps()
343            .filter(|(_, bitmap)| !bitmap.is_empty())
344            .map(|(key, bitmap)| {
345                let mut canonical = RoaringBitmap::new();
346                let mut ranges = bitmap.iter();
347                while let Some(range) = ranges.next_range() {
348                    canonical.insert_range(range);
349                }
350                canonical.optimize();
351                (key, canonical)
352            }),
353    );
354    coverage_to_bytes(&Coverage::from_treemap(present))
355}
356
357fn take<'a>(remaining: &mut &'a [u8], len: usize) -> Result<&'a [u8], CoverageCodecError> {
358    if remaining.len() < len {
359        return TruncatedPayloadSnafu.fail();
360    }
361    let (value, rest) = remaining.split_at(len);
362    *remaining = rest;
363    Ok(value)
364}
365
366fn take_declared<'a>(
367    remaining: &mut &'a [u8],
368    len: u64,
369    field: &'static str,
370) -> Result<&'a [u8], CoverageCodecError> {
371    let len = usize::try_from(len).map_err(|_| InvalidLengthSnafu { field }.build())?;
372    if len > remaining.len() {
373        return InvalidLengthSnafu { field }.fail();
374    }
375    take(remaining, len)
376}
377
378fn read_u32(remaining: &mut &[u8]) -> Result<u32, CoverageCodecError> {
379    let mut encoded = [0; 4];
380    encoded.copy_from_slice(take(remaining, 4)?);
381    Ok(u32::from_be_bytes(encoded))
382}
383
384fn read_i32(remaining: &mut &[u8]) -> Result<i32, CoverageCodecError> {
385    let mut encoded = [0; 4];
386    encoded.copy_from_slice(take(remaining, 4)?);
387    Ok(i32::from_be_bytes(encoded))
388}
389
390fn read_i64(remaining: &mut &[u8]) -> Result<i64, CoverageCodecError> {
391    let mut encoded = [0; 8];
392    encoded.copy_from_slice(take(remaining, 8)?);
393    Ok(i64::from_be_bytes(encoded))
394}
395
396fn read_u64(remaining: &mut &[u8]) -> Result<u64, CoverageCodecError> {
397    let mut encoded = [0; 8];
398    encoded.copy_from_slice(take(remaining, 8)?);
399    Ok(u64::from_be_bytes(encoded))
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    const ROARING_ZERO: &[u8] = &[
407        1, 0, 0, 0, 0, 0, 0, 0, // Treemap entry count.
408        0, 0, 0, 0, // Treemap key.
409        0x3a, 0x30, 0, 0, // Bitmap cookie.
410        1, 0, 0, 0, // Bitmap container count.
411        0, 0, 0, 0, // Container key and cardinality minus one.
412        16, 0, 0, 0, // Container offset.
413        0, 0, // Array value.
414    ];
415    const ROARING_MAX: &[u8] = &[
416        1, 0, 0, 0, 0, 0, 0, 0, // Treemap entry count.
417        0xff, 0xff, 0xff, 0xff, // Treemap key.
418        0x3a, 0x30, 0, 0, // Bitmap cookie.
419        1, 0, 0, 0, // Bitmap container count.
420        0xff, 0xff, 0, 0, // Container key and cardinality minus one.
421        16, 0, 0, 0, // Container offset.
422        0xff, 0xff, // Array value.
423    ];
424
425    fn identity(components: &[&str]) -> EntityIdentity {
426        EntityIdentity::try_new(
427            components
428                .iter()
429                .map(|component| EntityValue::from(*component))
430                .collect(),
431        )
432        .unwrap()
433    }
434
435    #[test]
436    fn round_trip_empty_and_non_empty() {
437        // Empty coverage
438        let cov_empty = Coverage::empty();
439        let bytes = coverage_to_bytes(&cov_empty).expect("serialize empty");
440        let restored = coverage_from_bytes(&bytes).expect("deserialize empty");
441        assert_eq!(cov_empty.cardinality(), restored.cardinality());
442
443        // Non-empty coverage
444        let cov = Coverage::from_iter(vec![1u64, 2, 3, u64::MAX]);
445        let bytes = coverage_to_bytes(&cov).expect("serialize non-empty");
446        let restored = coverage_from_bytes(&bytes).expect("deserialize non-empty");
447        assert_eq!(cov.present(), restored.present());
448    }
449
450    #[test]
451    fn deserialize_rejects_invalid_bytes() {
452        let bad = b"not a roaring bitmap";
453        let err = coverage_from_bytes(bad).unwrap_err();
454        match err {
455            CoverageCodecError::BitmapDeserialization { .. } => {}
456            _ => panic!("expected deserialize error"),
457        }
458    }
459
460    #[test]
461    fn deserialize_rejects_trailing_valid_payload() {
462        let mut bytes = coverage_to_bytes(&Coverage::empty()).unwrap();
463        bytes.extend_from_slice(&coverage_to_bytes(&Coverage::from_iter([1u64])).unwrap());
464
465        let err = coverage_from_bytes(&bytes).unwrap_err();
466        assert!(matches!(err, CoverageCodecError::TrailingBytes { .. }));
467    }
468
469    #[test]
470    fn serialize_reports_io_error() {
471        // Force an I/O error by using a writer that always errors.
472        struct FailingWriter;
473        impl std::io::Write for FailingWriter {
474            fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
475                Err(std::io::Error::other("fail"))
476            }
477            fn flush(&mut self) -> std::io::Result<()> {
478                Ok(())
479            }
480        }
481
482        let cov = Coverage::from_iter(vec![1u64]);
483
484        // Reimplement minimal logic to inject failing writer
485        let err = {
486            let mut w = FailingWriter;
487            cov.present()
488                .serialize_into(&mut w)
489                .context(BitmapSerializationSnafu)
490                .unwrap_err()
491        };
492
493        match err {
494            CoverageCodecError::BitmapSerialization { .. } => {}
495            _ => panic!("expected serialize error"),
496        }
497    }
498
499    #[test]
500    fn entity_coverage_round_trips_empty_and_one_identity() {
501        let empty_bytes = entity_coverage_to_bytes(&EntityCoverage::empty()).unwrap();
502        assert_eq!(
503            empty_bytes,
504            [ENTITY_COVERAGE_MAGIC.as_slice(), &[0; 4]].concat()
505        );
506        assert_eq!(
507            entity_coverage_from_bytes(&empty_bytes).unwrap(),
508            EntityCoverage::empty()
509        );
510
511        let entity = identity(&["venue", "symbol"]);
512        let mut coverage = EntityCoverage::empty();
513        coverage.union_coverage(entity.clone(), Coverage::empty());
514        let empty_identity_bytes = entity_coverage_to_bytes(&coverage).unwrap();
515        assert_ne!(empty_identity_bytes, empty_bytes);
516        assert_eq!(
517            entity_coverage_from_bytes(&empty_identity_bytes).unwrap(),
518            coverage
519        );
520
521        coverage.union_coverage(entity, [0, u64::MAX].into_iter().collect());
522        let bytes = entity_coverage_to_bytes(&coverage).unwrap();
523        assert_eq!(entity_coverage_from_bytes(&bytes).unwrap(), coverage);
524    }
525
526    #[test]
527    fn entity_coverage_keeps_composite_identities_and_intervals_independent() {
528        let first = identity(&["a", "b:c"]);
529        let second = identity(&["a:b", "c"]);
530        let mut coverage = EntityCoverage::empty();
531        coverage.union_coverage(first.clone(), [7].into_iter().collect());
532        coverage.union_coverage(second.clone(), [7].into_iter().collect());
533
534        let restored =
535            entity_coverage_from_bytes(&entity_coverage_to_bytes(&coverage).unwrap()).unwrap();
536        assert_eq!(restored.get(&first).unwrap().cardinality(), 1);
537        assert_eq!(restored.get(&second).unwrap().cardinality(), 1);
538        assert_eq!(restored.cardinality(), 2);
539    }
540
541    #[test]
542    fn entity_coverage_serialization_uses_canonical_identity_order() {
543        let first = identity(&["A"]);
544        let second = identity(&["B"]);
545        let mut forward = EntityCoverage::empty();
546        forward.union_coverage(first.clone(), [1].into_iter().collect());
547        forward.union_coverage(second.clone(), [2].into_iter().collect());
548
549        let mut reverse = EntityCoverage::empty();
550        reverse.union_coverage(second, [2].into_iter().collect());
551        reverse.union_coverage(first, [1].into_iter().collect());
552
553        assert_eq!(
554            entity_coverage_to_bytes(&forward).unwrap(),
555            entity_coverage_to_bytes(&reverse).unwrap()
556        );
557    }
558
559    #[test]
560    fn entity_coverage_serialization_canonicalizes_roaring_storage() {
561        let partition = 1u64 << 32;
562        let inserted: Coverage = (1..=3).chain(partition + 1..=partition + 5_000).collect();
563        let mut ranged = RoaringTreemap::new();
564        ranged.insert_range(1..=3);
565        ranged.insert_range(partition + 1..=partition + 5_000);
566        let ranged = Coverage::from_treemap(ranged);
567        assert_eq!(inserted, ranged);
568        assert_ne!(
569            coverage_to_bytes(&inserted).unwrap(),
570            coverage_to_bytes(&ranged).unwrap()
571        );
572
573        let entity = identity(&["A"]);
574        let mut left = EntityCoverage::empty();
575        left.union_coverage(entity.clone(), inserted);
576        let mut right = EntityCoverage::empty();
577        right.union_coverage(entity, ranged);
578
579        assert_eq!(
580            entity_coverage_to_bytes(&left).unwrap(),
581            entity_coverage_to_bytes(&right).unwrap()
582        );
583
584        let empty_partition = Coverage::from_treemap(RoaringTreemap::from_bitmaps([(
585            7,
586            roaring::RoaringBitmap::new(),
587        )]));
588        let mut logically_empty = EntityCoverage::empty();
589        logically_empty.union_coverage(identity(&["empty"]), empty_partition);
590        let mut canonical_empty = EntityCoverage::empty();
591        canonical_empty.union_coverage(identity(&["empty"]), Coverage::empty());
592        assert_eq!(
593            entity_coverage_to_bytes(&logically_empty).unwrap(),
594            entity_coverage_to_bytes(&canonical_empty).unwrap()
595        );
596    }
597
598    #[test]
599    fn entity_coverage_v2_golden_payload_is_stable() {
600        let mut coverage = EntityCoverage::empty();
601        coverage.union_coverage(
602            EntityIdentity::try_new(vec![
603                EntityValue::from("\u{6771}\u{4eac}"),
604                EntityValue::Int32(-1),
605                EntityValue::Int64(i64::MIN),
606                EntityValue::UInt64(u64::MAX),
607            ])
608            .unwrap(),
609            [0].into_iter().collect(),
610        );
611
612        let expected = [
613            b"TSTECOV2".as_slice(),
614            &[0, 0, 0, 1], // Entity count.
615            &[0, 0, 0, 4], // Identity component count.
616            &[ENTITY_VALUE_UTF8],
617            &[0, 0, 0, 0, 0, 0, 0, 6],
618            &[0xe6, 0x9d, 0xb1, 0xe4, 0xba, 0xac],
619            &[ENTITY_VALUE_INT32],
620            &(-1i32).to_be_bytes(),
621            &[ENTITY_VALUE_INT64],
622            &i64::MIN.to_be_bytes(),
623            &[ENTITY_VALUE_UINT64],
624            &u64::MAX.to_be_bytes(),
625            &[0, 0, 0, 0, 0, 0, 0, 30],
626            ROARING_ZERO,
627        ]
628        .concat();
629
630        assert_eq!(entity_coverage_to_bytes(&coverage).unwrap(), expected);
631        assert_eq!(entity_coverage_from_bytes(&expected).unwrap(), coverage);
632    }
633
634    #[test]
635    fn entity_coverage_decoder_rejects_every_truncated_prefix() {
636        let mut coverage = EntityCoverage::empty();
637        coverage.union_coverage(identity(&["A"]), [1].into_iter().collect());
638        let bytes = entity_coverage_to_bytes(&coverage).unwrap();
639
640        for end in 0..bytes.len() {
641            assert!(entity_coverage_from_bytes(&bytes[..end]).is_err());
642        }
643    }
644
645    #[test]
646    fn entity_coverage_decoder_rejects_invalid_magic_lengths_and_strings() {
647        let mut coverage = EntityCoverage::empty();
648        coverage.union_coverage(identity(&["A"]), [1].into_iter().collect());
649        let bytes = entity_coverage_to_bytes(&coverage).unwrap();
650
651        let mut invalid_magic = bytes.clone();
652        invalid_magic[0] ^= 0xff;
653        assert!(matches!(
654            entity_coverage_from_bytes(&invalid_magic),
655            Err(CoverageCodecError::InvalidEntityCoverageMagic { .. })
656        ));
657
658        let mut version_one = bytes.clone();
659        version_one[..8].copy_from_slice(b"TSTECOV1");
660        assert!(matches!(
661            entity_coverage_from_bytes(&version_one),
662            Err(CoverageCodecError::InvalidEntityCoverageMagic { .. })
663        ));
664
665        let mut invalid_count = bytes.clone();
666        invalid_count[8..12].copy_from_slice(&u32::MAX.to_be_bytes());
667        assert!(entity_coverage_from_bytes(&invalid_count).is_err());
668
669        let mut empty_identity = bytes.clone();
670        empty_identity[12..16].copy_from_slice(&0u32.to_be_bytes());
671        assert!(matches!(
672            entity_coverage_from_bytes(&empty_identity),
673            Err(CoverageCodecError::InvalidEntityIdentity { .. })
674        ));
675
676        let mut invalid_length = bytes.clone();
677        invalid_length[17..25].copy_from_slice(&u64::MAX.to_be_bytes());
678        assert!(matches!(
679            entity_coverage_from_bytes(&invalid_length),
680            Err(CoverageCodecError::InvalidLength { .. })
681        ));
682
683        let mut invalid_string = bytes.clone();
684        invalid_string[25] = 0xff;
685        assert!(matches!(
686            entity_coverage_from_bytes(&invalid_string),
687            Err(CoverageCodecError::InvalidEntityUtf8 { .. })
688        ));
689
690        let mut unknown_type = bytes;
691        unknown_type[16] = u8::MAX;
692        assert!(matches!(
693            entity_coverage_from_bytes(&unknown_type),
694            Err(CoverageCodecError::UnknownEntityValueTag { tag: u8::MAX, .. })
695        ));
696    }
697
698    #[test]
699    fn entity_coverage_decoder_rejects_duplicate_identities() {
700        let mut coverage = EntityCoverage::empty();
701        coverage.union_coverage(identity(&["A"]), Coverage::empty());
702        let mut bytes = entity_coverage_to_bytes(&coverage).unwrap();
703        let duplicate = bytes[12..].to_vec();
704        bytes[8..12].copy_from_slice(&2u32.to_be_bytes());
705        bytes.extend_from_slice(&duplicate);
706
707        assert!(matches!(
708            entity_coverage_from_bytes(&bytes),
709            Err(CoverageCodecError::DuplicateEntityIdentity { .. })
710        ));
711    }
712
713    #[test]
714    fn entity_coverage_decoder_rejects_malformed_nested_and_trailing_bytes() {
715        let mut coverage = EntityCoverage::empty();
716        coverage.union_coverage(identity(&["A"]), [1].into_iter().collect());
717        let bytes = entity_coverage_to_bytes(&coverage).unwrap();
718
719        let mut malformed_nested = bytes.clone();
720        malformed_nested[26..34].copy_from_slice(&1u64.to_be_bytes());
721        assert!(matches!(
722            entity_coverage_from_bytes(&malformed_nested),
723            Err(CoverageCodecError::MalformedNestedCoverage { .. })
724        ));
725
726        let mut trailing = bytes;
727        trailing.push(0);
728        assert!(matches!(
729            entity_coverage_from_bytes(&trailing),
730            Err(CoverageCodecError::TrailingBytes { .. })
731        ));
732    }
733
734    #[test]
735    fn global_coverage_codec_golden_bytes_are_unchanged_and_distinct() {
736        let global_empty = coverage_to_bytes(&Coverage::empty()).unwrap();
737        assert_eq!(global_empty, vec![0; 8]);
738        assert!(matches!(
739            entity_coverage_from_bytes(&global_empty),
740            Err(CoverageCodecError::InvalidEntityCoverageMagic { .. })
741                | Err(CoverageCodecError::TruncatedPayload { .. })
742        ));
743
744        let global_extremes: Coverage = [0, u64::MAX].into_iter().collect();
745        let global_extremes_bytes = [
746            &[2, 0, 0, 0, 0, 0, 0, 0],
747            &ROARING_ZERO[8..],
748            &ROARING_MAX[8..],
749        ]
750        .concat();
751        assert_eq!(
752            coverage_to_bytes(&global_extremes).unwrap(),
753            global_extremes_bytes
754        );
755        assert_eq!(
756            coverage_from_bytes(&global_extremes_bytes)
757                .unwrap()
758                .present(),
759            global_extremes.present()
760        );
761
762        let entity_empty = entity_coverage_to_bytes(&EntityCoverage::empty()).unwrap();
763        assert!(coverage_from_bytes(&entity_empty).is_err());
764    }
765}