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