Skip to main content

grib_core/
data.rs

1//! Data Representation Section (Section 5) shared model.
2
3use crate::binary::{decode_wmo_i16, decode_wmo_i8};
4use crate::error::{Error, Result};
5
6/// GRIB data representation and validated packing parameters.
7#[derive(Debug, Clone, PartialEq)]
8#[non_exhaustive]
9pub enum DataRepresentation {
10    /// Template 5.0: Simple packing.
11    SimplePacking(ScaledPackingParams),
12    /// GRIB1 grid-point second-order packing.
13    ///
14    /// GRIB1 stores this layout in Binary Data Section flags rather than a
15    /// GRIB2 data representation template.
16    Grib1SecondOrderPacking(Grib1SecondOrderPackingParams),
17    /// Template 5.2/5.3: Complex packing with optional spatial differencing.
18    ComplexPacking(ComplexPackingParams),
19    /// Template 5.4: IEEE floating-point values.
20    IeeePacking(IeeePackingParams),
21    /// Template 5.40: JPEG 2000 code stream packing.
22    Jpeg2000Packing(Jpeg2000PackingParams),
23    /// Template 5.41: PNG image packing.
24    PngPacking(PngPackingParams),
25    /// Template 5.42: CCSDS 121.0 adaptive entropy coding.
26    CcsdsPacking(CcsdsPackingParams),
27    /// Template 5.200: Run-length packing with predefined level values.
28    RunLengthPacking(RunLengthPackingParams),
29    /// Unsupported template.
30    Unsupported(u16),
31}
32
33/// Scaling parameters shared by GRIB1 and GRIB2 grid-point packing.
34///
35/// Decoded values use `(reference + packed * 2^binary_scale) *
36/// 10^-decimal_scale`.
37#[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/// Standard GRIB1 second-order group layout.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[non_exhaustive]
50pub enum Grib1SecondOrderLayout {
51    /// One variable-width group for each scan line; group lengths come from
52    /// the grid and optional primary bitmap.
53    RowByRow,
54    /// Groups are located by a secondary bitmap and share one value width.
55    ConstantWidth,
56    /// Groups are located by a secondary bitmap and have individual widths.
57    VariableWidth,
58}
59
60/// Validated parameters for standard GRIB1 grid-point second-order packing.
61#[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    /// Returns one width for constant-width packing or one width per group for
87    /// row-by-row and variable-width packing.
88    pub fn group_widths(&self) -> &[u8] {
89        &self.group_widths
90    }
91
92    /// Returns secondary-bitmap group lengths. Row-by-row lengths are supplied
93    /// from the grid and primary bitmap when the field is indexed.
94    pub fn group_lengths(&self) -> &[usize] {
95        &self.group_lengths
96    }
97
98    /// Byte offset of first-order values relative to GRIB1 BDS octet 12.
99    pub const fn first_order_values_offset(&self) -> usize {
100        self.first_order_values_offset
101    }
102
103    /// Byte offset of second-order values relative to GRIB1 BDS octet 12.
104    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    /// Returns the second-order width for one group.
113    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/// IEEE floating-point precision from Code Table 5.7.
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
125#[non_exhaustive]
126pub enum IeeePrecision {
127    /// IEEE 754 binary32.
128    Single,
129    /// IEEE 754 binary64.
130    Double,
131    /// IEEE 754 binary128.
132    Quadruple,
133}
134
135impl IeeePrecision {
136    /// Returns the Code Table 5.7 wire value.
137    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    /// Returns the number of octets occupied by each Section 7 value.
146    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    /// Returns the number of bits occupied by each Section 7 value.
155    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/// Parameters for IEEE floating-point packing (Template 5.4).
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub struct IeeePackingParams {
167    pub encoded_values: usize,
168    pub precision: IeeePrecision,
169}
170
171/// Parameters for run-length packing with level values (Template 5.200).
172///
173/// Packed level zero represents a missing value. Codes from one through
174/// `max_level_value` index the corresponding scaled entry in `level_values`.
175#[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    /// Returns the decoded representative value for a nonzero level code.
186    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/// Parameters for JPEG 2000 code stream packing (Template 5.40).
196#[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/// Parameters for PNG image packing (Template 5.41).
204#[derive(Debug, Clone, PartialEq)]
205pub struct PngPackingParams {
206    pub packing: ScaledPackingParams,
207}
208
209/// CCSDS/AEC option mask stored by data representation template 5.42.
210///
211/// The bit assignments match the `libaec` API referenced by the WMO template.
212/// Bit 7 is reserved and therefore rejected by [`CcsdsFlags::from_bits`].
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
214pub struct CcsdsFlags(u8);
215
216impl CcsdsFlags {
217    /// No optional codec behavior.
218    pub const NONE: Self = Self(0);
219    /// Treat sample bit patterns as signed during AEC preprocessing.
220    ///
221    /// GRIB reconstruction still interprets the decoded samples as unsigned
222    /// scaled differences from the field reference value.
223    pub const SIGNED: Self = Self(1 << 0);
224    /// Samples with 17 through 24 bits use three-byte containers.
225    pub const THREE_BYTE: Self = Self(1 << 1);
226    /// Sample containers store their most-significant byte first.
227    pub const MSB: Self = Self(1 << 2);
228    /// Apply the CCSDS unit-delay preprocessor.
229    pub const PREPROCESS: Self = Self(1 << 3);
230    /// Use the restricted code-option set for samples no wider than four bits.
231    pub const RESTRICTED: Self = Self(1 << 4);
232    /// Decode legacy streams whose reference sample intervals are byte padded.
233    pub const PAD_RSI: Self = Self(1 << 5);
234    /// Permit non-standard even block sizes.
235    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    /// ecCodes' canonical template 5.42 options.
246    pub const DEFAULT: Self = Self(Self::THREE_BYTE.0 | Self::MSB.0 | Self::PREPROCESS.0);
247
248    /// Constructs flags when no reserved bits are set.
249    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    /// Returns the template 5.42 wire representation.
258    pub const fn bits(self) -> u8 {
259        self.0
260    }
261
262    /// Returns whether all bits in `other` are present.
263    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/// Number of samples in a CCSDS/AEC coding block.
289///
290/// Standard streams use 8, 16, 32, or 64 samples. Other non-zero even sizes
291/// are representable because `libaec` permits them when
292/// [`CcsdsFlags::NOT_ENFORCE`] is present.
293#[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    /// Constructs a block size accepted by the AEC codec.
303    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/// Validated parameters for CCSDS 121.0 packing (template 5.42).
327#[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    /// Constructs a parameter set after validating the interdependent AEC
337    /// width, flag, block-size, and reference-interval constraints.
338    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/// Parameters for complex packing (Templates 5.2 and 5.3).
378///
379/// Decoders represent embedded primary and secondary missing codes as `NaN`.
380/// The template's substitute metadata is preserved separately and can be
381/// queried with [`ComplexPackingParams::primary_missing_substitute`] and
382/// [`ComplexPackingParams::secondary_missing_substitute`].
383#[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    /// Returns the raw four-octet primary substitute when primary missing
407    /// values are enabled by Code Table 5.5.
408    ///
409    /// The decoder intentionally emits `NaN` instead of applying this value.
410    /// Callers that need producer-specific substitution can inspect
411    /// [`ComplexPackingParams::original_field_type`] and interpret this raw
412    /// Template 5.2/5.3 value accordingly.
413    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    /// Returns the raw four-octet secondary substitute when both primary and
421    /// secondary missing values are enabled by Code Table 5.5.
422    ///
423    /// The decoder intentionally emits `NaN` instead of applying this value.
424    /// Callers that need producer-specific substitution can inspect
425    /// [`ComplexPackingParams::original_field_type`] and interpret this raw
426    /// Template 5.2/5.3 value accordingly.
427    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/// Parameters specific to template 5.3 spatial differencing.
436#[derive(Debug, Clone, Copy, PartialEq, Eq)]
437pub struct SpatialDifferencingParams {
438    pub order: u8,
439    pub descriptor_octets: u8,
440}
441
442impl DataRepresentation {
443    /// Returns the GRIB2 template number or a diagnostic identifier for a
444    /// GRIB1 representation.
445    pub const fn template_number(&self) -> u16 {
446        match self {
447            Self::SimplePacking(_) => 0,
448            // GRIB1 has no template number. Keep this identifier disjoint from
449            // WMO GRIB2 template numbers for diagnostics and writer rejection.
450            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(&section).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(&section).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(&section).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(&section[..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(&section).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(&section).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(&section).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(&section).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}