1use crate::binary::{decode_wmo_i16, decode_wmo_i8};
4use crate::error::{Error, Result};
5
6#[derive(Debug, Clone, PartialEq)]
8#[non_exhaustive]
9pub enum DataRepresentation {
10 SimplePacking(ScaledPackingParams),
12 Grib1SecondOrderPacking(Grib1SecondOrderPackingParams),
17 ComplexPacking(ComplexPackingParams),
19 IeeePacking(IeeePackingParams),
21 Jpeg2000Packing(Jpeg2000PackingParams),
23 PngPacking(PngPackingParams),
25 CcsdsPacking(CcsdsPackingParams),
27 RunLengthPacking(RunLengthPackingParams),
29 Unsupported(u16),
31}
32
33#[derive(Debug, Clone, PartialEq)]
38pub struct ScaledPackingParams {
39 pub encoded_values: usize,
40 pub reference_value: f32,
41 pub binary_scale: i16,
42 pub decimal_scale: i16,
43 pub bits_per_value: u8,
44 pub original_field_type: u8,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[non_exhaustive]
50pub enum Grib1SecondOrderLayout {
51 RowByRow,
54 ConstantWidth,
56 VariableWidth,
58}
59
60#[derive(Debug, Clone, PartialEq)]
62pub struct Grib1SecondOrderPackingParams {
63 pub(crate) packing: ScaledPackingParams,
64 pub(crate) layout: Grib1SecondOrderLayout,
65 pub(crate) group_count: usize,
66 pub(crate) group_widths: Vec<u8>,
67 pub(crate) group_lengths: Vec<usize>,
68 pub(crate) first_order_values_offset: usize,
69 pub(crate) second_order_values_offset: usize,
70 pub(crate) unused_bits: u8,
71}
72
73impl Grib1SecondOrderPackingParams {
74 pub const fn packing(&self) -> &ScaledPackingParams {
75 &self.packing
76 }
77
78 pub const fn layout(&self) -> Grib1SecondOrderLayout {
79 self.layout
80 }
81
82 pub const fn group_count(&self) -> usize {
83 self.group_count
84 }
85
86 pub fn group_widths(&self) -> &[u8] {
89 &self.group_widths
90 }
91
92 pub fn group_lengths(&self) -> &[usize] {
95 &self.group_lengths
96 }
97
98 pub const fn first_order_values_offset(&self) -> usize {
100 self.first_order_values_offset
101 }
102
103 pub const fn second_order_values_offset(&self) -> usize {
105 self.second_order_values_offset
106 }
107
108 pub const fn unused_bits(&self) -> u8 {
109 self.unused_bits
110 }
111
112 pub fn group_width(&self, group_index: usize) -> Option<u8> {
114 match self.layout {
115 Grib1SecondOrderLayout::ConstantWidth => self.group_widths.first().copied(),
116 Grib1SecondOrderLayout::RowByRow | Grib1SecondOrderLayout::VariableWidth => {
117 self.group_widths.get(group_index).copied()
118 }
119 }
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
125#[non_exhaustive]
126pub enum IeeePrecision {
127 Single,
129 Double,
131 Quadruple,
133}
134
135impl IeeePrecision {
136 pub const fn code(self) -> u8 {
138 match self {
139 Self::Single => 1,
140 Self::Double => 2,
141 Self::Quadruple => 3,
142 }
143 }
144
145 pub const fn bytes_per_value(self) -> usize {
147 match self {
148 Self::Single => 4,
149 Self::Double => 8,
150 Self::Quadruple => 16,
151 }
152 }
153
154 pub const fn bits_per_value(self) -> u8 {
156 match self {
157 Self::Single => 32,
158 Self::Double => 64,
159 Self::Quadruple => 128,
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub struct IeeePackingParams {
167 pub encoded_values: usize,
168 pub precision: IeeePrecision,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq)]
176pub struct RunLengthPackingParams {
177 pub encoded_values: usize,
178 pub bits_per_value: u8,
179 pub max_level_value: u16,
180 pub decimal_scale: i16,
181 pub level_values: Vec<u16>,
182}
183
184impl RunLengthPackingParams {
185 pub fn representative_value(&self, level: u16) -> Option<f64> {
187 if level == 0 || level > self.max_level_value {
188 return None;
189 }
190 let scaled = *self.level_values.get(usize::from(level - 1))?;
191 Some(f64::from(scaled) * 10.0_f64.powi(-i32::from(self.decimal_scale)))
192 }
193}
194
195#[derive(Debug, Clone, PartialEq)]
197pub struct Jpeg2000PackingParams {
198 pub packing: ScaledPackingParams,
199 pub compression_type: u8,
200 pub target_compression_ratio: u8,
201}
202
203#[derive(Debug, Clone, PartialEq)]
205pub struct PngPackingParams {
206 pub packing: ScaledPackingParams,
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
214pub struct CcsdsFlags(u8);
215
216impl CcsdsFlags {
217 pub const NONE: Self = Self(0);
219 pub const SIGNED: Self = Self(1 << 0);
224 pub const THREE_BYTE: Self = Self(1 << 1);
226 pub const MSB: Self = Self(1 << 2);
228 pub const PREPROCESS: Self = Self(1 << 3);
230 pub const RESTRICTED: Self = Self(1 << 4);
232 pub const PAD_RSI: Self = Self(1 << 5);
234 pub const NOT_ENFORCE: Self = Self(1 << 6);
236
237 const KNOWN_BITS: u8 = Self::SIGNED.0
238 | Self::THREE_BYTE.0
239 | Self::MSB.0
240 | Self::PREPROCESS.0
241 | Self::RESTRICTED.0
242 | Self::PAD_RSI.0
243 | Self::NOT_ENFORCE.0;
244
245 pub const DEFAULT: Self = Self(Self::THREE_BYTE.0 | Self::MSB.0 | Self::PREPROCESS.0);
247
248 pub const fn from_bits(bits: u8) -> Option<Self> {
250 if bits & !Self::KNOWN_BITS == 0 {
251 Some(Self(bits))
252 } else {
253 None
254 }
255 }
256
257 pub const fn bits(self) -> u8 {
259 self.0
260 }
261
262 pub const fn contains(self, other: Self) -> bool {
264 self.0 & other.0 == other.0
265 }
266}
267
268impl Default for CcsdsFlags {
269 fn default() -> Self {
270 Self::DEFAULT
271 }
272}
273
274impl std::ops::BitOr for CcsdsFlags {
275 type Output = Self;
276
277 fn bitor(self, rhs: Self) -> Self::Output {
278 Self(self.0 | rhs.0)
279 }
280}
281
282impl std::ops::BitOrAssign for CcsdsFlags {
283 fn bitor_assign(&mut self, rhs: Self) {
284 self.0 |= rhs.0;
285 }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
294pub struct CcsdsBlockSize(u8);
295
296impl CcsdsBlockSize {
297 pub const EIGHT: Self = Self(8);
298 pub const SIXTEEN: Self = Self(16);
299 pub const THIRTY_TWO: Self = Self(32);
300 pub const SIXTY_FOUR: Self = Self(64);
301
302 pub const fn new(samples: u8) -> Option<Self> {
304 if samples != 0 && samples.is_multiple_of(2) {
305 Some(Self(samples))
306 } else {
307 None
308 }
309 }
310
311 pub const fn samples(self) -> u8 {
312 self.0
313 }
314
315 pub const fn is_standard(self) -> bool {
316 matches!(self.0, 8 | 16 | 32 | 64)
317 }
318}
319
320impl Default for CcsdsBlockSize {
321 fn default() -> Self {
322 Self::THIRTY_TWO
323 }
324}
325
326#[derive(Debug, Clone, PartialEq)]
328pub struct CcsdsPackingParams {
329 packing: ScaledPackingParams,
330 flags: CcsdsFlags,
331 block_size: CcsdsBlockSize,
332 reference_sample_interval: u16,
333}
334
335impl CcsdsPackingParams {
336 pub fn new(
339 packing: ScaledPackingParams,
340 flags: CcsdsFlags,
341 block_size: CcsdsBlockSize,
342 reference_sample_interval: u16,
343 ) -> Result<Self> {
344 validate_ccsds_parameters(
345 packing.bits_per_value,
346 flags,
347 block_size,
348 reference_sample_interval,
349 )
350 .map_err(Error::ValueOutOfRange)?;
351
352 Ok(Self {
353 packing,
354 flags,
355 block_size,
356 reference_sample_interval,
357 })
358 }
359
360 pub const fn packing(&self) -> &ScaledPackingParams {
361 &self.packing
362 }
363
364 pub const fn flags(&self) -> CcsdsFlags {
365 self.flags
366 }
367
368 pub const fn block_size(&self) -> CcsdsBlockSize {
369 self.block_size
370 }
371
372 pub const fn reference_sample_interval(&self) -> u16 {
373 self.reference_sample_interval
374 }
375}
376
377#[derive(Debug, Clone, PartialEq)]
384pub struct ComplexPackingParams {
385 pub encoded_values: usize,
386 pub reference_value: f32,
387 pub binary_scale: i16,
388 pub decimal_scale: i16,
389 pub group_reference_bits: u8,
390 pub original_field_type: u8,
391 pub group_splitting_method: u8,
392 pub missing_value_management: u8,
393 pub primary_missing_substitute: u32,
394 pub secondary_missing_substitute: u32,
395 pub num_groups: usize,
396 pub group_width_reference: u8,
397 pub group_width_bits: u8,
398 pub group_length_reference: u32,
399 pub group_length_increment: u8,
400 pub true_length_last_group: u32,
401 pub scaled_group_length_bits: u8,
402 pub spatial_differencing: Option<SpatialDifferencingParams>,
403}
404
405impl ComplexPackingParams {
406 pub const fn primary_missing_substitute(&self) -> Option<u32> {
414 match self.missing_value_management {
415 1 | 2 => Some(self.primary_missing_substitute),
416 _ => None,
417 }
418 }
419
420 pub const fn secondary_missing_substitute(&self) -> Option<u32> {
428 match self.missing_value_management {
429 2 => Some(self.secondary_missing_substitute),
430 _ => None,
431 }
432 }
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq)]
437pub struct SpatialDifferencingParams {
438 pub order: u8,
439 pub descriptor_octets: u8,
440}
441
442impl DataRepresentation {
443 pub const fn template_number(&self) -> u16 {
446 match self {
447 Self::SimplePacking(_) => 0,
448 Self::Grib1SecondOrderPacking(_) => 1005,
451 Self::ComplexPacking(params) => {
452 if params.spatial_differencing.is_some() {
453 3
454 } else {
455 2
456 }
457 }
458 Self::IeeePacking(_) => 4,
459 Self::Jpeg2000Packing(_) => 40,
460 Self::PngPacking(_) => 41,
461 Self::CcsdsPacking(_) => 42,
462 Self::RunLengthPacking(_) => 200,
463 Self::Unsupported(template) => *template,
464 }
465 }
466
467 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
468 if section_bytes.len() < 11 {
469 return Err(Error::InvalidSection {
470 section: 5,
471 reason: format!("expected at least 11 bytes, got {}", section_bytes.len()),
472 });
473 }
474 if section_bytes[4] != 5 {
475 return Err(Error::InvalidSection {
476 section: section_bytes[4],
477 reason: "not a data representation section".into(),
478 });
479 }
480
481 let template = u16::from_be_bytes(section_bytes[9..11].try_into().unwrap());
482 match template {
483 0 => parse_simple_packing(section_bytes),
484 2 => parse_complex_packing(section_bytes, false),
485 3 => parse_complex_packing(section_bytes, true),
486 4 => parse_ieee_packing(section_bytes),
487 40 => parse_jpeg2000_packing(section_bytes),
488 41 => parse_png_packing(section_bytes),
489 42 => parse_ccsds_packing(section_bytes),
490 200 => parse_run_length_packing(section_bytes),
491 _ => Ok(Self::Unsupported(template)),
492 }
493 }
494
495 pub fn encoded_values(&self) -> Option<usize> {
496 match self {
497 Self::SimplePacking(params) => Some(params.encoded_values),
498 Self::Grib1SecondOrderPacking(params) => Some(params.packing.encoded_values),
499 Self::ComplexPacking(params) => Some(params.encoded_values),
500 Self::IeeePacking(params) => Some(params.encoded_values),
501 Self::Jpeg2000Packing(params) => Some(params.packing.encoded_values),
502 Self::PngPacking(params) => Some(params.packing.encoded_values),
503 Self::CcsdsPacking(params) => Some(params.packing().encoded_values),
504 Self::RunLengthPacking(params) => Some(params.encoded_values),
505 Self::Unsupported(_) => None,
506 }
507 }
508}
509
510fn parse_simple_packing(data: &[u8]) -> Result<DataRepresentation> {
511 Ok(DataRepresentation::SimplePacking(
512 parse_scaled_packing_base(data, 0, 21)?,
513 ))
514}
515
516fn parse_ieee_packing(data: &[u8]) -> Result<DataRepresentation> {
517 if data.len() < 12 {
518 return Err(Error::InvalidSection {
519 section: 5,
520 reason: format!("template 5.4 requires 12 bytes, got {}", data.len()),
521 });
522 }
523
524 let precision = match data[11] {
525 1 => IeeePrecision::Single,
526 2 => IeeePrecision::Double,
527 3 => IeeePrecision::Quadruple,
528 code => {
529 return Err(Error::InvalidSection {
530 section: 5,
531 reason: format!("template 5.4 has invalid IEEE precision code {code}"),
532 });
533 }
534 };
535
536 Ok(DataRepresentation::IeeePacking(IeeePackingParams {
537 encoded_values: u32::from_be_bytes(data[5..9].try_into().unwrap()) as usize,
538 precision,
539 }))
540}
541
542fn parse_run_length_packing(data: &[u8]) -> Result<DataRepresentation> {
543 if data.len() < 17 {
544 return Err(Error::InvalidSection {
545 section: 5,
546 reason: format!(
547 "template 5.200 requires at least 17 bytes, got {}",
548 data.len()
549 ),
550 });
551 }
552
553 let bits_per_value = data[11];
554 let max_level_value = u16::from_be_bytes(data[12..14].try_into().unwrap());
555 let number_of_level_values = usize::from(u16::from_be_bytes(data[14..16].try_into().unwrap()));
556 let required = number_of_level_values
557 .checked_mul(2)
558 .and_then(|level_bytes| 17_usize.checked_add(level_bytes))
559 .ok_or(Error::ArithmeticOverflow {
560 operation: "computing template 5.200 length",
561 })?;
562 if data.len() < required {
563 return Err(Error::InvalidSection {
564 section: 5,
565 reason: format!(
566 "template 5.200 declares {number_of_level_values} level values and requires {required} bytes, got {}",
567 data.len()
568 ),
569 });
570 }
571
572 validate_run_length_parameters(bits_per_value, max_level_value, number_of_level_values)
573 .map_err(|reason| Error::InvalidSection {
574 section: 5,
575 reason: format!("invalid template 5.200 parameters: {reason}"),
576 })?;
577
578 let mut level_values = Vec::new();
579 level_values
580 .try_reserve_exact(number_of_level_values)
581 .map_err(|error| {
582 Error::allocation("template 5.200 level values", number_of_level_values, error)
583 })?;
584 for bytes in data[17..required].chunks_exact(2) {
585 level_values.push(u16::from_be_bytes(
586 bytes.try_into().expect("two-byte level value"),
587 ));
588 }
589
590 Ok(DataRepresentation::RunLengthPacking(
591 RunLengthPackingParams {
592 encoded_values: u32::from_be_bytes(data[5..9].try_into().unwrap()) as usize,
593 bits_per_value,
594 max_level_value,
595 decimal_scale: decode_wmo_i8(data[16]),
596 level_values,
597 },
598 ))
599}
600
601fn validate_run_length_parameters(
602 bits_per_value: u8,
603 max_level_value: u16,
604 number_of_level_values: usize,
605) -> std::result::Result<(), String> {
606 if bits_per_value > 64 {
607 return Err(format!(
608 "bits per value must not exceed 64, got {bits_per_value}"
609 ));
610 }
611 if max_level_value == 0 {
612 if number_of_level_values != 0 {
613 return Err(format!(
614 "zero maximum level requires an empty level table, got {number_of_level_values} entries"
615 ));
616 }
617 return Ok(());
618 }
619 if bits_per_value == 0 {
620 return Err(format!(
621 "bits per value must be between 1 and 64, got {bits_per_value}"
622 ));
623 }
624 if usize::from(max_level_value) > number_of_level_values {
625 return Err(format!(
626 "maximum level {max_level_value} exceeds the {number_of_level_values}-entry level table"
627 ));
628 }
629 let max_code = if bits_per_value == 64 {
630 u64::MAX
631 } else {
632 (1_u64 << bits_per_value) - 1
633 };
634 if u64::from(max_level_value) >= max_code {
635 return Err(format!(
636 "maximum level {max_level_value} leaves no run-length codes at {bits_per_value} bits"
637 ));
638 }
639 Ok(())
640}
641
642fn parse_scaled_packing_base(
643 data: &[u8],
644 template: u16,
645 required: usize,
646) -> Result<ScaledPackingParams> {
647 if data.len() < required {
648 return Err(Error::InvalidSection {
649 section: 5,
650 reason: format!(
651 "template 5.{template} requires {required} bytes, got {}",
652 data.len()
653 ),
654 });
655 }
656
657 Ok(ScaledPackingParams {
658 encoded_values: u32::from_be_bytes(data[5..9].try_into().unwrap()) as usize,
659 reference_value: f32::from_be_bytes(data[11..15].try_into().unwrap()),
660 binary_scale: decode_wmo_i16(&data[15..17]).unwrap(),
661 decimal_scale: decode_wmo_i16(&data[17..19]).unwrap(),
662 bits_per_value: data[19],
663 original_field_type: data[20],
664 })
665}
666
667fn parse_jpeg2000_packing(data: &[u8]) -> Result<DataRepresentation> {
668 Ok(DataRepresentation::Jpeg2000Packing(Jpeg2000PackingParams {
669 packing: parse_scaled_packing_base(data, 40, 23)?,
670 compression_type: data[21],
671 target_compression_ratio: data[22],
672 }))
673}
674
675fn parse_png_packing(data: &[u8]) -> Result<DataRepresentation> {
676 Ok(DataRepresentation::PngPacking(PngPackingParams {
677 packing: parse_scaled_packing_base(data, 41, 21)?,
678 }))
679}
680
681fn parse_ccsds_packing(data: &[u8]) -> Result<DataRepresentation> {
682 let packing = parse_scaled_packing_base(data, 42, 25)?;
683 let flags = CcsdsFlags::from_bits(data[21]).ok_or_else(|| Error::InvalidSection {
684 section: 5,
685 reason: format!(
686 "template 5.42 has reserved CCSDS flag bits set: 0x{:02x}",
687 data[21]
688 ),
689 })?;
690 let block_size = CcsdsBlockSize::new(data[22]).ok_or_else(|| Error::InvalidSection {
691 section: 5,
692 reason: format!(
693 "template 5.42 block size must be a non-zero even value, got {}",
694 data[22]
695 ),
696 })?;
697 let reference_sample_interval = u16::from_be_bytes(data[23..25].try_into().unwrap());
698
699 validate_ccsds_parameters(
700 packing.bits_per_value,
701 flags,
702 block_size,
703 reference_sample_interval,
704 )
705 .map_err(|reason| Error::InvalidSection {
706 section: 5,
707 reason: format!("invalid template 5.42 parameters: {reason}"),
708 })?;
709
710 Ok(DataRepresentation::CcsdsPacking(CcsdsPackingParams {
711 packing,
712 flags,
713 block_size,
714 reference_sample_interval,
715 }))
716}
717
718fn validate_ccsds_parameters(
719 bits_per_value: u8,
720 flags: CcsdsFlags,
721 block_size: CcsdsBlockSize,
722 reference_sample_interval: u16,
723) -> std::result::Result<(), String> {
724 if bits_per_value > 32 {
725 return Err(format!(
726 "CCSDS bits per value must be at most 32, got {bits_per_value}"
727 ));
728 }
729 if !block_size.is_standard() && !flags.contains(CcsdsFlags::NOT_ENFORCE) {
730 return Err(format!(
731 "CCSDS block size {} requires the NOT_ENFORCE flag",
732 block_size.samples()
733 ));
734 }
735 if !(1..=4096).contains(&reference_sample_interval) {
736 return Err(format!(
737 "CCSDS reference sample interval must be between 1 and 4096, got {reference_sample_interval}"
738 ));
739 }
740 if flags.contains(CcsdsFlags::RESTRICTED) && bits_per_value > 4 {
741 return Err(format!(
742 "CCSDS RESTRICTED flag requires at most 4 bits per value, got {bits_per_value}"
743 ));
744 }
745 Ok(())
746}
747
748fn parse_complex_packing(
749 data: &[u8],
750 with_spatial_differencing: bool,
751) -> Result<DataRepresentation> {
752 let required = if with_spatial_differencing { 49 } else { 47 };
753 if data.len() < required {
754 return Err(Error::InvalidSection {
755 section: 5,
756 reason: format!(
757 "template 5.{} requires {required} bytes, got {}",
758 if with_spatial_differencing { 3 } else { 2 },
759 data.len()
760 ),
761 });
762 }
763
764 let group_splitting_method = data[21];
765 if !matches!(group_splitting_method, 0 | 1) {
766 return Err(Error::UnsupportedGroupSplittingMethod(
767 group_splitting_method,
768 ));
769 }
770
771 let missing_value_management = data[22];
772 if missing_value_management > 2 {
773 return Err(Error::UnsupportedMissingValueManagement(
774 missing_value_management,
775 ));
776 }
777
778 let spatial_differencing = if with_spatial_differencing {
779 let order = data[47];
780 if !matches!(order, 1 | 2) {
781 return Err(Error::UnsupportedSpatialDifferencingOrder(order));
782 }
783 Some(SpatialDifferencingParams {
784 order,
785 descriptor_octets: data[48],
786 })
787 } else {
788 None
789 };
790 let group_length_reference = u32::from_be_bytes(data[37..41].try_into().unwrap());
791 let group_length_increment = data[41];
792 let true_length_last_group = u32::from_be_bytes(data[42..46].try_into().unwrap());
793 let scaled_group_length_bits = data[46];
794 if group_splitting_method == 0
795 && (group_length_reference != 0
796 || group_length_increment != 0
797 || true_length_last_group != 0
798 || scaled_group_length_bits != 0)
799 {
800 return Err(Error::InvalidSection {
801 section: 5,
802 reason: "complex packing group splitting method 0 requires all group-length fields to be zero"
803 .into(),
804 });
805 }
806
807 Ok(DataRepresentation::ComplexPacking(ComplexPackingParams {
808 encoded_values: u32::from_be_bytes(data[5..9].try_into().unwrap()) as usize,
809 reference_value: f32::from_be_bytes(data[11..15].try_into().unwrap()),
810 binary_scale: decode_wmo_i16(&data[15..17]).unwrap(),
811 decimal_scale: decode_wmo_i16(&data[17..19]).unwrap(),
812 group_reference_bits: data[19],
813 original_field_type: data[20],
814 group_splitting_method,
815 missing_value_management,
816 primary_missing_substitute: u32::from_be_bytes(data[23..27].try_into().unwrap()),
817 secondary_missing_substitute: u32::from_be_bytes(data[27..31].try_into().unwrap()),
818 num_groups: u32::from_be_bytes(data[31..35].try_into().unwrap()) as usize,
819 group_width_reference: data[35],
820 group_width_bits: data[36],
821 group_length_reference,
822 group_length_increment,
823 true_length_last_group,
824 scaled_group_length_bits,
825 spatial_differencing,
826 }))
827}
828
829#[cfg(test)]
830mod tests {
831 use super::{
832 CcsdsBlockSize, CcsdsFlags, CcsdsPackingParams, ComplexPackingParams, DataRepresentation,
833 IeeePackingParams, IeeePrecision, Jpeg2000PackingParams, PngPackingParams,
834 RunLengthPackingParams, ScaledPackingParams, SpatialDifferencingParams,
835 };
836 use crate::binary::{encode_wmo_i16, encode_wmo_i8};
837
838 #[test]
839 fn parses_simple_packing_template() {
840 let mut section = vec![0u8; 21];
841 section[..4].copy_from_slice(&(21u32).to_be_bytes());
842 section[4] = 5;
843 section[5..9].copy_from_slice(&3u32.to_be_bytes());
844 section[9..11].copy_from_slice(&0u16.to_be_bytes());
845 section[11..15].copy_from_slice(&10.0f32.to_be_bytes());
846 section[15..17].copy_from_slice(&2i16.to_be_bytes());
847 section[17..19].copy_from_slice(&1i16.to_be_bytes());
848 section[19] = 8;
849 section[20] = 0;
850
851 let representation = DataRepresentation::parse(§ion).unwrap();
852 assert_eq!(representation.template_number(), 0);
853 assert_eq!(
854 representation,
855 DataRepresentation::SimplePacking(ScaledPackingParams {
856 encoded_values: 3,
857 reference_value: 10.0,
858 binary_scale: 2,
859 decimal_scale: 1,
860 bits_per_value: 8,
861 original_field_type: 0,
862 })
863 );
864 }
865
866 #[test]
867 fn parses_all_ieee_packing_precisions() {
868 for (code, precision) in [
869 (1, IeeePrecision::Single),
870 (2, IeeePrecision::Double),
871 (3, IeeePrecision::Quadruple),
872 ] {
873 let representation = DataRepresentation::parse(&ieee_section(code)).unwrap();
874 assert_eq!(representation.template_number(), 4);
875 assert_eq!(
876 representation,
877 DataRepresentation::IeeePacking(IeeePackingParams {
878 encoded_values: 6,
879 precision,
880 })
881 );
882 assert_eq!(representation.encoded_values(), Some(6));
883 }
884 }
885
886 #[test]
887 fn rejects_invalid_or_truncated_ieee_packing_parameters() {
888 for code in [0, 4, 255] {
889 let error = DataRepresentation::parse(&ieee_section(code)).unwrap_err();
890 assert!(error.to_string().contains("invalid IEEE precision code"));
891 }
892
893 let error = DataRepresentation::parse(&ieee_section(1)[..11]).unwrap_err();
894 assert!(error.to_string().contains("requires 12 bytes"));
895 }
896
897 #[test]
898 fn parses_run_length_packing_template() {
899 let section = run_length_section(3, 2, -1, &[10, 20, 30]);
900 let representation = DataRepresentation::parse(§ion).unwrap();
901
902 assert_eq!(representation.template_number(), 200);
903 assert_eq!(
904 representation,
905 DataRepresentation::RunLengthPacking(RunLengthPackingParams {
906 encoded_values: 9,
907 bits_per_value: 3,
908 max_level_value: 2,
909 decimal_scale: -1,
910 level_values: vec![10, 20, 30],
911 })
912 );
913 let DataRepresentation::RunLengthPacking(params) = representation else {
914 unreachable!();
915 };
916 assert_eq!(params.representative_value(0), None);
917 assert_eq!(params.representative_value(1), Some(100.0));
918 assert_eq!(params.representative_value(2), Some(200.0));
919 assert_eq!(params.representative_value(3), None);
920 }
921
922 #[test]
923 fn parses_all_missing_run_length_parameters() {
924 let representation = DataRepresentation::parse(&run_length_section(0, 0, 0, &[])).unwrap();
925 assert_eq!(
926 representation,
927 DataRepresentation::RunLengthPacking(RunLengthPackingParams {
928 encoded_values: 9,
929 bits_per_value: 0,
930 max_level_value: 0,
931 decimal_scale: 0,
932 level_values: Vec::new(),
933 })
934 );
935 }
936
937 #[test]
938 fn rejects_invalid_run_length_parameters() {
939 for (section, reason) in [
940 (
941 run_length_section(3, 3, 0, &[10, 20]),
942 "exceeds the 2-entry level table",
943 ),
944 (
945 run_length_section(2, 3, 0, &[10, 20, 30]),
946 "leaves no run-length codes",
947 ),
948 (
949 run_length_section(0, 1, 0, &[10]),
950 "bits per value must be between 1 and 64",
951 ),
952 (
953 run_length_section(65, 1, 0, &[10]),
954 "bits per value must not exceed 64",
955 ),
956 (
957 run_length_section(65, 0, 0, &[]),
958 "bits per value must not exceed 64",
959 ),
960 (
961 run_length_section(3, 0, 0, &[10]),
962 "zero maximum level requires an empty level table",
963 ),
964 ] {
965 let error = DataRepresentation::parse(§ion).unwrap_err();
966 assert!(error.to_string().contains(reason), "{error}");
967 }
968
969 let section = run_length_section(3, 2, 0, &[10, 20]);
970 let error = DataRepresentation::parse(§ion[..section.len() - 1]).unwrap_err();
971 assert!(error.to_string().contains("requires 21 bytes"));
972 }
973
974 #[test]
975 fn parses_jpeg2000_packing_template() {
976 let mut section = vec![0u8; 23];
977 section[..4].copy_from_slice(&(23u32).to_be_bytes());
978 section[4] = 5;
979 section[5..9].copy_from_slice(&4u32.to_be_bytes());
980 section[9..11].copy_from_slice(&40u16.to_be_bytes());
981 section[11..15].copy_from_slice(&3.5f32.to_be_bytes());
982 section[15..17].copy_from_slice(&encode_wmo_i16(-1).unwrap());
983 section[17..19].copy_from_slice(&2i16.to_be_bytes());
984 section[19] = 12;
985 section[20] = 0;
986 section[21] = 1;
987 section[22] = 255;
988
989 let representation = DataRepresentation::parse(§ion).unwrap();
990 assert_eq!(representation.template_number(), 40);
991 assert_eq!(
992 representation,
993 DataRepresentation::Jpeg2000Packing(Jpeg2000PackingParams {
994 packing: ScaledPackingParams {
995 encoded_values: 4,
996 reference_value: 3.5,
997 binary_scale: -1,
998 decimal_scale: 2,
999 bits_per_value: 12,
1000 original_field_type: 0,
1001 },
1002 compression_type: 1,
1003 target_compression_ratio: 255,
1004 })
1005 );
1006 }
1007
1008 #[test]
1009 fn parses_png_packing_template() {
1010 let mut section = vec![0u8; 21];
1011 section[..4].copy_from_slice(&(21u32).to_be_bytes());
1012 section[4] = 5;
1013 section[5..9].copy_from_slice(&6u32.to_be_bytes());
1014 section[9..11].copy_from_slice(&41u16.to_be_bytes());
1015 section[11..15].copy_from_slice(&1.25f32.to_be_bytes());
1016 section[15..17].copy_from_slice(&1i16.to_be_bytes());
1017 section[17..19].copy_from_slice(&encode_wmo_i16(-2).unwrap());
1018 section[19] = 16;
1019 section[20] = 1;
1020
1021 let representation = DataRepresentation::parse(§ion).unwrap();
1022 assert_eq!(representation.template_number(), 41);
1023 assert_eq!(
1024 representation,
1025 DataRepresentation::PngPacking(PngPackingParams {
1026 packing: ScaledPackingParams {
1027 encoded_values: 6,
1028 reference_value: 1.25,
1029 binary_scale: 1,
1030 decimal_scale: -2,
1031 bits_per_value: 16,
1032 original_field_type: 1,
1033 },
1034 })
1035 );
1036 }
1037
1038 #[test]
1039 fn exposes_only_enabled_complex_missing_value_substitutes() {
1040 let mut params = ComplexPackingParams {
1041 encoded_values: 1,
1042 reference_value: 0.0,
1043 binary_scale: 0,
1044 decimal_scale: 0,
1045 group_reference_bits: 1,
1046 original_field_type: 0,
1047 group_splitting_method: 1,
1048 missing_value_management: 0,
1049 primary_missing_substitute: 0x1122_3344,
1050 secondary_missing_substitute: 0x5566_7788,
1051 num_groups: 1,
1052 group_width_reference: 0,
1053 group_width_bits: 0,
1054 group_length_reference: 1,
1055 group_length_increment: 1,
1056 true_length_last_group: 1,
1057 scaled_group_length_bits: 0,
1058 spatial_differencing: None,
1059 };
1060
1061 assert_eq!(params.primary_missing_substitute(), None);
1062 assert_eq!(params.secondary_missing_substitute(), None);
1063
1064 params.missing_value_management = 1;
1065 assert_eq!(params.primary_missing_substitute(), Some(0x1122_3344));
1066 assert_eq!(params.secondary_missing_substitute(), None);
1067
1068 params.missing_value_management = 2;
1069 assert_eq!(params.primary_missing_substitute(), Some(0x1122_3344));
1070 assert_eq!(params.secondary_missing_substitute(), Some(0x5566_7788));
1071 }
1072
1073 #[test]
1074 fn reports_complex_and_unknown_template_numbers() {
1075 let mut params = ComplexPackingParams {
1076 encoded_values: 0,
1077 reference_value: 0.0,
1078 binary_scale: 0,
1079 decimal_scale: 0,
1080 group_reference_bits: 0,
1081 original_field_type: 0,
1082 group_splitting_method: 1,
1083 missing_value_management: 0,
1084 primary_missing_substitute: 0,
1085 secondary_missing_substitute: 0,
1086 num_groups: 0,
1087 group_width_reference: 0,
1088 group_width_bits: 0,
1089 group_length_reference: 0,
1090 group_length_increment: 0,
1091 true_length_last_group: 0,
1092 scaled_group_length_bits: 0,
1093 spatial_differencing: None,
1094 };
1095
1096 assert_eq!(
1097 DataRepresentation::ComplexPacking(params.clone()).template_number(),
1098 2
1099 );
1100 params.spatial_differencing = Some(SpatialDifferencingParams {
1101 order: 1,
1102 descriptor_octets: 1,
1103 });
1104 assert_eq!(
1105 DataRepresentation::ComplexPacking(params).template_number(),
1106 3
1107 );
1108 assert_eq!(DataRepresentation::Unsupported(500).template_number(), 500);
1109 }
1110
1111 #[test]
1112 fn parses_ccsds_packing_template() {
1113 let section = ccsds_section(20, CcsdsFlags::DEFAULT.bits(), 32, 128);
1114
1115 let representation = DataRepresentation::parse(§ion).unwrap();
1116 assert_eq!(representation.template_number(), 42);
1117 assert_eq!(
1118 representation,
1119 DataRepresentation::CcsdsPacking(
1120 CcsdsPackingParams::new(
1121 ScaledPackingParams {
1122 encoded_values: 6,
1123 reference_value: 1.25,
1124 binary_scale: -1,
1125 decimal_scale: 2,
1126 bits_per_value: 20,
1127 original_field_type: 0,
1128 },
1129 CcsdsFlags::DEFAULT,
1130 CcsdsBlockSize::THIRTY_TWO,
1131 128,
1132 )
1133 .unwrap()
1134 )
1135 );
1136 }
1137
1138 #[test]
1139 fn parses_unenforced_even_ccsds_block_size() {
1140 let flags = CcsdsFlags::DEFAULT | CcsdsFlags::NOT_ENFORCE;
1141 let section = ccsds_section(8, flags.bits(), 12, 1);
1142 let parsed = DataRepresentation::parse(§ion).unwrap();
1143 let DataRepresentation::CcsdsPacking(params) = parsed else {
1144 panic!("expected CCSDS representation");
1145 };
1146
1147 assert_eq!(params.flags(), flags);
1148 assert_eq!(params.block_size().samples(), 12);
1149 assert_eq!(params.reference_sample_interval(), 1);
1150 }
1151
1152 #[test]
1153 fn rejects_reserved_ccsds_flag_bit() {
1154 let error = DataRepresentation::parse(&ccsds_section(8, 0x80, 32, 128)).unwrap_err();
1155 assert!(matches!(
1156 error,
1157 crate::Error::InvalidSection { section: 5, .. }
1158 ));
1159 assert!(error.to_string().contains("reserved CCSDS flag bits"));
1160 }
1161
1162 #[test]
1163 fn rejects_nonstandard_ccsds_block_without_flag() {
1164 let error = DataRepresentation::parse(&ccsds_section(8, 0, 12, 128)).unwrap_err();
1165 assert!(error.to_string().contains("requires the NOT_ENFORCE flag"));
1166 }
1167
1168 #[test]
1169 fn rejects_restricted_ccsds_width_above_four_bits() {
1170 let error =
1171 DataRepresentation::parse(&ccsds_section(5, CcsdsFlags::RESTRICTED.bits(), 32, 128))
1172 .unwrap_err();
1173 assert!(error.to_string().contains("requires at most 4 bits"));
1174 }
1175
1176 #[test]
1177 fn rejects_ccsds_reference_interval_outside_codec_range() {
1178 for interval in [0, 4097] {
1179 let error = DataRepresentation::parse(&ccsds_section(8, 0, 32, interval)).unwrap_err();
1180 assert!(error
1181 .to_string()
1182 .contains("reference sample interval must be between 1 and 4096"));
1183 }
1184 }
1185
1186 fn ccsds_section(bits_per_value: u8, flags: u8, block_size: u8, rsi: u16) -> Vec<u8> {
1187 let mut section = vec![0u8; 25];
1188 section[..4].copy_from_slice(&25u32.to_be_bytes());
1189 section[4] = 5;
1190 section[5..9].copy_from_slice(&6u32.to_be_bytes());
1191 section[9..11].copy_from_slice(&42u16.to_be_bytes());
1192 section[11..15].copy_from_slice(&1.25f32.to_be_bytes());
1193 section[15..17].copy_from_slice(&encode_wmo_i16(-1).unwrap());
1194 section[17..19].copy_from_slice(&2i16.to_be_bytes());
1195 section[19] = bits_per_value;
1196 section[20] = 0;
1197 section[21] = flags;
1198 section[22] = block_size;
1199 section[23..25].copy_from_slice(&rsi.to_be_bytes());
1200 section
1201 }
1202
1203 fn ieee_section(precision: u8) -> Vec<u8> {
1204 let mut section = vec![0u8; 12];
1205 section[..4].copy_from_slice(&12u32.to_be_bytes());
1206 section[4] = 5;
1207 section[5..9].copy_from_slice(&6u32.to_be_bytes());
1208 section[9..11].copy_from_slice(&4u16.to_be_bytes());
1209 section[11] = precision;
1210 section
1211 }
1212
1213 fn run_length_section(
1214 bits_per_value: u8,
1215 max_level_value: u16,
1216 decimal_scale: i16,
1217 level_values: &[u16],
1218 ) -> Vec<u8> {
1219 let length = 17 + 2 * level_values.len();
1220 let mut section = vec![0u8; length];
1221 section[..4].copy_from_slice(&(length as u32).to_be_bytes());
1222 section[4] = 5;
1223 section[5..9].copy_from_slice(&9u32.to_be_bytes());
1224 section[9..11].copy_from_slice(&200u16.to_be_bytes());
1225 section[11] = bits_per_value;
1226 section[12..14].copy_from_slice(&max_level_value.to_be_bytes());
1227 section[14..16].copy_from_slice(&(level_values.len() as u16).to_be_bytes());
1228 section[16] = encode_wmo_i8(decimal_scale).unwrap();
1229 for (bytes, level) in section[17..].chunks_exact_mut(2).zip(level_values) {
1230 bytes.copy_from_slice(&level.to_be_bytes());
1231 }
1232 section
1233 }
1234}