1use 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#[derive(Debug, Snafu)]
48#[non_exhaustive]
49pub enum CoverageCodecError {
50 #[snafu(display("Failed to serialize roaring bitmap: {source}"))]
52 BitmapSerialization {
53 source: std::io::Error,
55 backtrace: Backtrace,
57 },
58
59 #[snafu(display("Failed to deserialize roaring bitmap: {source}"))]
61 BitmapDeserialization {
62 source: std::io::Error,
64 backtrace: Backtrace,
66 },
67
68 #[snafu(display("Entity coverage {field} is too large to serialize"))]
70 LengthOverflow {
71 field: &'static str,
73 backtrace: Backtrace,
75 },
76
77 #[snafu(display("Invalid entity coverage payload identifier"))]
79 InvalidEntityCoverageMagic {
80 backtrace: Backtrace,
82 },
83
84 #[snafu(display("Truncated entity coverage payload"))]
86 TruncatedPayload {
87 backtrace: Backtrace,
89 },
90
91 #[snafu(display("Invalid entity coverage {field}"))]
93 InvalidLength {
94 field: &'static str,
96 backtrace: Backtrace,
98 },
99
100 #[snafu(display("Invalid entity identity string: {source}"))]
102 InvalidEntityUtf8 {
103 source: Utf8Error,
105 backtrace: Backtrace,
107 },
108
109 #[snafu(display("Unknown entity identity value type tag: {tag}"))]
111 UnknownEntityValueTag {
112 tag: u8,
114 backtrace: Backtrace,
116 },
117
118 #[snafu(display("Invalid entity identity: {source}"))]
120 InvalidEntityIdentity {
121 source: EntityIdentityError,
123 backtrace: Backtrace,
125 },
126
127 #[snafu(display("Duplicate entity identity in coverage payload: {identity:?}"))]
129 DuplicateEntityIdentity {
130 identity: EntityIdentity,
132 backtrace: Backtrace,
134 },
135
136 #[snafu(display("Malformed nested entity coverage: {source}"))]
138 MalformedNestedCoverage {
139 #[snafu(source(from(CoverageCodecError, Box::new)), backtrace)]
141 source: Box<CoverageCodecError>,
142 },
143
144 #[snafu(display("Trailing bytes after coverage payload"))]
146 TrailingBytes {
147 backtrace: Backtrace,
149 },
150}
151
152pub 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
179pub 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
207pub 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
276pub 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
336fn 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, 0, 0, 0, 0, 0x3a, 0x30, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, ];
415 const ROARING_MAX: &[u8] = &[
416 1, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x30, 0, 0, 1, 0, 0, 0, 0xff, 0xff, 0, 0, 16, 0, 0, 0, 0xff, 0xff, ];
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 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 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 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 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], &[0, 0, 0, 4], &[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}