Skip to main content

grib_core/
product.rs

1//! GRIB2 metadata carried by Sections 1 and 4.
2
3use crate::binary::{decode_wmo_i32, decode_wmo_i8};
4use crate::error::{Error, Result};
5use crate::metadata::ReferenceTime;
6use crate::parameter;
7
8/// Section 1: Identification Section.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Identification {
11    pub center_id: u16,
12    pub subcenter_id: u16,
13    pub master_table_version: u8,
14    pub local_table_version: u8,
15    pub significance_of_reference_time: u8,
16    pub reference_year: u16,
17    pub reference_month: u8,
18    pub reference_day: u8,
19    pub reference_hour: u8,
20    pub reference_minute: u8,
21    pub reference_second: u8,
22    pub production_status: u8,
23    pub processed_data_type: u8,
24}
25
26impl Identification {
27    pub fn parse(section_bytes: &[u8]) -> Result<Self> {
28        if section_bytes.len() < 21 {
29            return Err(Error::InvalidSection {
30                section: 1,
31                reason: format!("expected at least 21 bytes, got {}", section_bytes.len()),
32            });
33        }
34        if section_bytes[4] != 1 {
35            return Err(Error::InvalidSection {
36                section: section_bytes[4],
37                reason: "not an identification section".into(),
38            });
39        }
40
41        let reference_time = ReferenceTime {
42            year: u16::from_be_bytes(section_bytes[12..14].try_into().unwrap()),
43            month: section_bytes[14],
44            day: section_bytes[15],
45            hour: section_bytes[16],
46            minute: section_bytes[17],
47            second: section_bytes[18],
48        };
49        reference_time.validate_in_section(1)?;
50
51        Ok(Self {
52            center_id: u16::from_be_bytes(section_bytes[5..7].try_into().unwrap()),
53            subcenter_id: u16::from_be_bytes(section_bytes[7..9].try_into().unwrap()),
54            master_table_version: section_bytes[9],
55            local_table_version: section_bytes[10],
56            significance_of_reference_time: section_bytes[11],
57            reference_year: reference_time.year,
58            reference_month: reference_time.month,
59            reference_day: reference_time.day,
60            reference_hour: reference_time.hour,
61            reference_minute: reference_time.minute,
62            reference_second: reference_time.second,
63            production_status: section_bytes[19],
64            processed_data_type: section_bytes[20],
65        })
66    }
67}
68
69/// A fixed surface from Product Definition templates.
70#[derive(Debug, Clone, PartialEq)]
71pub struct FixedSurface {
72    pub surface_type: u8,
73    /// The numeric level, or `None` when either WMO numeric component is
74    /// encoded with its missing-value sentinel.
75    pub value: Option<ScaledValue>,
76}
77
78/// The scale/value pair used to encode a fixed-surface numeric level.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct ScaledValue {
81    pub scale_factor: i16,
82    pub scaled_value: i32,
83}
84
85impl FixedSurface {
86    pub const fn with_value(surface_type: u8, scale_factor: i16, scaled_value: i32) -> Self {
87        Self {
88            surface_type,
89            value: Some(ScaledValue {
90                scale_factor,
91                scaled_value,
92            }),
93        }
94    }
95
96    pub const fn without_value(surface_type: u8) -> Self {
97        Self {
98            surface_type,
99            value: None,
100        }
101    }
102
103    pub fn scaled_value_f64(&self) -> Option<f64> {
104        let value = self.value?;
105        let factor = 10.0_f64.powi(-i32::from(value.scale_factor));
106        Some(f64::from(value.scaled_value) * factor)
107    }
108}
109
110/// Section 4: Product Definition Section.
111#[derive(Debug, Clone, PartialEq)]
112pub struct ProductDefinition {
113    pub parameter_category: u8,
114    pub parameter_number: u8,
115    pub template: ProductDefinitionTemplate,
116}
117
118/// Typed GRIB2 Product Definition templates.
119#[derive(Debug, Clone, PartialEq)]
120#[non_exhaustive]
121pub enum ProductDefinitionTemplate {
122    AnalysisOrForecast(AnalysisOrForecastTemplate),
123    IndividualEnsembleForecast(IndividualEnsembleForecastTemplate),
124    DerivedForecast(DerivedForecastTemplate),
125    ProbabilityForecast(ProbabilityForecastTemplate),
126    PercentileForecast(PercentileForecastTemplate),
127    StatisticalProcess(StatisticalProcessTemplate),
128    ProbabilityStatisticalProcess(ProbabilityStatisticalProcessTemplate),
129    PercentileStatisticalProcess(PercentileStatisticalProcessTemplate),
130    EnsembleStatisticalProcess(EnsembleStatisticalProcessTemplate),
131    DerivedStatisticalProcess(DerivedStatisticalProcessTemplate),
132    SpatialProcess(SpatialProcessTemplate),
133    /// A well-framed Section 4 whose template is not interpreted by this
134    /// version of the library. `raw` contains the template-specific bytes
135    /// following the common parameter category and number.
136    Unsupported {
137        number: u16,
138        raw: Vec<u8>,
139    },
140}
141
142/// Product Definition Template 4.0: analysis or forecast at a horizontal level.
143#[derive(Debug, Clone, PartialEq)]
144pub struct AnalysisOrForecastTemplate {
145    pub type_of_generating_process: u8,
146    pub background_generating_process_identifier: u8,
147    pub generating_process_identifier: u8,
148    pub hours_after_data_cutoff: Option<u16>,
149    pub minutes_after_data_cutoff: Option<u8>,
150    pub forecast_time_unit: u8,
151    pub forecast_time: i32,
152    pub first_surface: Option<FixedSurface>,
153    pub second_surface: Option<FixedSurface>,
154}
155
156/// Product Definition Template 4.1: individual ensemble forecast at a point in time.
157#[derive(Debug, Clone, PartialEq)]
158pub struct IndividualEnsembleForecastTemplate {
159    pub base: AnalysisOrForecastTemplate,
160    pub type_of_ensemble_forecast: u8,
161    pub perturbation_number: u8,
162    pub number_of_forecasts_in_ensemble: u8,
163}
164
165/// Product Definition Template 4.2: forecast derived from all ensemble members.
166#[derive(Debug, Clone, PartialEq)]
167pub struct DerivedForecastTemplate {
168    pub base: AnalysisOrForecastTemplate,
169    pub derived_forecast_type: u8,
170    pub number_of_forecasts_in_ensemble: u8,
171}
172
173/// A signed decimal threshold used by probability product templates.
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub struct ProbabilityLimit {
176    pub scale_factor: i16,
177    pub scaled_value: i32,
178}
179
180impl ProbabilityLimit {
181    pub fn value_f64(self) -> f64 {
182        f64::from(self.scaled_value) * 10.0_f64.powi(-i32::from(self.scale_factor))
183    }
184}
185
186/// The event whose forecast probability is encoded by templates 4.5 and 4.9.
187///
188/// Known WMO event types carry exactly the limit values used by their
189/// definition. [`ProbabilityType::Other`] preserves reserved, locally defined,
190/// and noncanonical input without imposing semantics on its limits. The writer
191/// accepts `Other` only for codes without a standard typed variant.
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193#[non_exhaustive]
194pub enum ProbabilityType {
195    BelowLowerLimit(ProbabilityLimit),
196    AboveUpperLimit(ProbabilityLimit),
197    BetweenLimits {
198        lower: ProbabilityLimit,
199        upper: ProbabilityLimit,
200    },
201    AboveLowerLimit(ProbabilityLimit),
202    BelowUpperLimit(ProbabilityLimit),
203    EqualLowerLimit(ProbabilityLimit),
204    AboveNormal,
205    NearNormal,
206    BelowNormal,
207    CategoricalBoolean,
208    Quantile,
209    Missing,
210    Other {
211        code: u8,
212        lower: Option<ProbabilityLimit>,
213        upper: Option<ProbabilityLimit>,
214    },
215}
216
217impl ProbabilityType {
218    pub const fn code(self) -> u8 {
219        match self {
220            Self::BelowLowerLimit(_) => 0,
221            Self::AboveUpperLimit(_) => 1,
222            Self::BetweenLimits { .. } => 2,
223            Self::AboveLowerLimit(_) => 3,
224            Self::BelowUpperLimit(_) => 4,
225            Self::EqualLowerLimit(_) => 5,
226            Self::AboveNormal => 6,
227            Self::NearNormal => 7,
228            Self::BelowNormal => 8,
229            Self::CategoricalBoolean => 9,
230            Self::Quantile => 10,
231            Self::Missing => 255,
232            Self::Other { code, .. } => code,
233        }
234    }
235
236    pub const fn lower_limit(self) -> Option<ProbabilityLimit> {
237        match self {
238            Self::BelowLowerLimit(limit)
239            | Self::AboveLowerLimit(limit)
240            | Self::EqualLowerLimit(limit) => Some(limit),
241            Self::BetweenLimits { lower, .. } => Some(lower),
242            Self::Other { lower, .. } => lower,
243            _ => None,
244        }
245    }
246
247    pub const fn upper_limit(self) -> Option<ProbabilityLimit> {
248        match self {
249            Self::AboveUpperLimit(limit) | Self::BelowUpperLimit(limit) => Some(limit),
250            Self::BetweenLimits { upper, .. } => Some(upper),
251            Self::Other { upper, .. } => upper,
252            _ => None,
253        }
254    }
255
256    fn from_code_and_limits(
257        code: u8,
258        lower: Option<ProbabilityLimit>,
259        upper: Option<ProbabilityLimit>,
260    ) -> Self {
261        match (code, lower, upper) {
262            (0, Some(limit), None) => Self::BelowLowerLimit(limit),
263            (1, None, Some(limit)) => Self::AboveUpperLimit(limit),
264            (2, Some(lower), Some(upper)) => Self::BetweenLimits { lower, upper },
265            (3, Some(limit), None) => Self::AboveLowerLimit(limit),
266            (4, None, Some(limit)) => Self::BelowUpperLimit(limit),
267            (5, Some(limit), None) => Self::EqualLowerLimit(limit),
268            (6, None, None) => Self::AboveNormal,
269            (7, None, None) => Self::NearNormal,
270            (8, None, None) => Self::BelowNormal,
271            (9, None, None) => Self::CategoricalBoolean,
272            (10, None, None) => Self::Quantile,
273            (255, None, None) => Self::Missing,
274            (code, lower, upper) => Self::Other { code, lower, upper },
275        }
276    }
277}
278
279/// Product Definition Template 4.5: probability forecast at a point in time.
280#[derive(Debug, Clone, PartialEq)]
281pub struct ProbabilityForecastTemplate {
282    pub base: AnalysisOrForecastTemplate,
283    pub forecast_probability_number: u8,
284    pub total_number_of_forecast_probabilities: u8,
285    pub probability: ProbabilityType,
286}
287
288/// Product Definition Template 4.6: percentile forecast at a point in time.
289#[derive(Debug, Clone, PartialEq)]
290pub struct PercentileForecastTemplate {
291    pub base: AnalysisOrForecastTemplate,
292    pub percentile_value: u8,
293}
294
295/// Time-interval metadata shared by statistically processed product templates.
296#[derive(Debug, Clone, PartialEq)]
297pub struct StatisticalInterval {
298    pub end_of_overall_time_interval: ReferenceTime,
299    pub number_of_missing_in_statistical_process: u32,
300    pub time_ranges: Vec<StatisticalTimeRange>,
301}
302
303/// Product Definition Template 4.8: statistically processed field over a time interval.
304#[derive(Debug, Clone, PartialEq)]
305pub struct StatisticalProcessTemplate {
306    pub base: AnalysisOrForecastTemplate,
307    pub interval: StatisticalInterval,
308}
309
310/// Product Definition Template 4.9: probability forecast over a time interval.
311#[derive(Debug, Clone, PartialEq)]
312pub struct ProbabilityStatisticalProcessTemplate {
313    pub probability: ProbabilityForecastTemplate,
314    pub interval: StatisticalInterval,
315}
316
317/// Product Definition Template 4.10: percentile forecast over a time interval.
318#[derive(Debug, Clone, PartialEq)]
319pub struct PercentileStatisticalProcessTemplate {
320    pub percentile: PercentileForecastTemplate,
321    pub interval: StatisticalInterval,
322}
323
324/// Product Definition Template 4.11: individual ensemble forecast over a time interval.
325#[derive(Debug, Clone, PartialEq)]
326pub struct EnsembleStatisticalProcessTemplate {
327    pub ensemble: IndividualEnsembleForecastTemplate,
328    pub interval: StatisticalInterval,
329}
330
331/// Product Definition Template 4.12: derived ensemble forecast over a time interval.
332#[derive(Debug, Clone, PartialEq)]
333pub struct DerivedStatisticalProcessTemplate {
334    pub derived: DerivedForecastTemplate,
335    pub interval: StatisticalInterval,
336}
337
338/// Product Definition Template 4.15: statistical processing over a spatial area.
339#[derive(Debug, Clone, PartialEq)]
340pub struct SpatialProcessTemplate {
341    pub base: AnalysisOrForecastTemplate,
342    pub statistical_process: u8,
343    pub spatial_processing: u8,
344    pub number_of_points_used: u8,
345}
346
347/// Statistical processing descriptor from GRIB2 Product Definition templates
348/// with one or more time range specifications.
349#[derive(Debug, Clone, PartialEq, Eq)]
350pub struct StatisticalTimeRange {
351    pub type_of_statistical_processing: u8,
352    pub type_of_time_increment: u8,
353    pub time_range_unit: u8,
354    pub time_range_length: u32,
355    pub time_increment_unit: u8,
356    pub time_increment: u32,
357}
358
359impl ProductDefinition {
360    pub fn parse(section_bytes: &[u8]) -> Result<Self> {
361        if section_bytes.len() < 11 {
362            return Err(Error::InvalidSection {
363                section: 4,
364                reason: format!("expected at least 11 bytes, got {}", section_bytes.len()),
365            });
366        }
367        if section_bytes[4] != 4 {
368            return Err(Error::InvalidSection {
369                section: section_bytes[4],
370                reason: "not a product definition section".into(),
371            });
372        }
373
374        let template = u16::from_be_bytes(section_bytes[7..9].try_into().unwrap());
375        let parameter_category = section_bytes[9];
376        let parameter_number = section_bytes[10];
377
378        Ok(Self {
379            parameter_category,
380            parameter_number,
381            template: ProductDefinitionTemplate::parse(template, section_bytes)?,
382        })
383    }
384
385    pub fn parameter_name(&self, discipline: u8) -> &'static str {
386        parameter::parameter_name(discipline, self.parameter_category, self.parameter_number)
387    }
388
389    pub fn parameter_description(&self, discipline: u8) -> &'static str {
390        parameter::parameter_description(discipline, self.parameter_category, self.parameter_number)
391    }
392
393    pub fn template_number(&self) -> u16 {
394        self.template.number()
395    }
396
397    pub fn type_of_generating_process(&self) -> Option<u8> {
398        self.template
399            .base()
400            .map(|base| base.type_of_generating_process)
401    }
402
403    pub fn generating_process_identifier(&self) -> Option<u8> {
404        self.template
405            .base()
406            .map(|base| base.generating_process_identifier)
407    }
408
409    pub fn forecast_time_unit(&self) -> Option<u8> {
410        self.template.base().map(|base| base.forecast_time_unit)
411    }
412
413    pub fn forecast_time(&self) -> Option<i32> {
414        self.template.base().map(|base| base.forecast_time)
415    }
416
417    pub fn first_surface(&self) -> Option<&FixedSurface> {
418        self.template
419            .base()
420            .and_then(|base| base.first_surface.as_ref())
421    }
422
423    pub fn second_surface(&self) -> Option<&FixedSurface> {
424        self.template
425            .base()
426            .and_then(|base| base.second_surface.as_ref())
427    }
428
429    pub fn end_of_overall_time_interval(&self) -> Option<ReferenceTime> {
430        self.template.end_of_overall_time_interval()
431    }
432}
433
434impl ProductDefinitionTemplate {
435    pub fn parse(template: u16, section_bytes: &[u8]) -> Result<Self> {
436        match template {
437            0 => Ok(Self::AnalysisOrForecast(AnalysisOrForecastTemplate::parse(
438                section_bytes,
439            )?)),
440            1 => Ok(Self::IndividualEnsembleForecast(
441                IndividualEnsembleForecastTemplate::parse(section_bytes)?,
442            )),
443            2 => Ok(Self::DerivedForecast(DerivedForecastTemplate::parse(
444                section_bytes,
445            )?)),
446            5 => Ok(Self::ProbabilityForecast(
447                ProbabilityForecastTemplate::parse(section_bytes)?,
448            )),
449            6 => Ok(Self::PercentileForecast(PercentileForecastTemplate::parse(
450                section_bytes,
451            )?)),
452            8 => Ok(Self::StatisticalProcess(StatisticalProcessTemplate::parse(
453                section_bytes,
454            )?)),
455            9 => Ok(Self::ProbabilityStatisticalProcess(
456                ProbabilityStatisticalProcessTemplate::parse(section_bytes)?,
457            )),
458            10 => Ok(Self::PercentileStatisticalProcess(
459                PercentileStatisticalProcessTemplate::parse(section_bytes)?,
460            )),
461            11 => Ok(Self::EnsembleStatisticalProcess(
462                EnsembleStatisticalProcessTemplate::parse(section_bytes)?,
463            )),
464            12 => Ok(Self::DerivedStatisticalProcess(
465                DerivedStatisticalProcessTemplate::parse(section_bytes)?,
466            )),
467            15 => Ok(Self::SpatialProcess(SpatialProcessTemplate::parse(
468                section_bytes,
469            )?)),
470            number => {
471                let raw_len = section_bytes.len() - 11;
472                let mut raw = Vec::new();
473                raw.try_reserve(raw_len).map_err(|error| {
474                    Error::allocation("unsupported product-template bytes", raw_len, error)
475                })?;
476                raw.extend_from_slice(&section_bytes[11..]);
477                Ok(Self::Unsupported { number, raw })
478            }
479        }
480    }
481
482    pub const fn number(&self) -> u16 {
483        match self {
484            Self::AnalysisOrForecast(_) => 0,
485            Self::IndividualEnsembleForecast(_) => 1,
486            Self::DerivedForecast(_) => 2,
487            Self::ProbabilityForecast(_) => 5,
488            Self::PercentileForecast(_) => 6,
489            Self::StatisticalProcess(_) => 8,
490            Self::ProbabilityStatisticalProcess(_) => 9,
491            Self::PercentileStatisticalProcess(_) => 10,
492            Self::EnsembleStatisticalProcess(_) => 11,
493            Self::DerivedStatisticalProcess(_) => 12,
494            Self::SpatialProcess(_) => 15,
495            Self::Unsupported { number, .. } => *number,
496        }
497    }
498
499    fn base(&self) -> Option<&AnalysisOrForecastTemplate> {
500        Some(match self {
501            Self::AnalysisOrForecast(template) => template,
502            Self::IndividualEnsembleForecast(template) => &template.base,
503            Self::DerivedForecast(template) => &template.base,
504            Self::ProbabilityForecast(template) => &template.base,
505            Self::PercentileForecast(template) => &template.base,
506            Self::StatisticalProcess(template) => &template.base,
507            Self::ProbabilityStatisticalProcess(template) => &template.probability.base,
508            Self::PercentileStatisticalProcess(template) => &template.percentile.base,
509            Self::EnsembleStatisticalProcess(template) => &template.ensemble.base,
510            Self::DerivedStatisticalProcess(template) => &template.derived.base,
511            Self::SpatialProcess(template) => &template.base,
512            Self::Unsupported { .. } => return None,
513        })
514    }
515
516    fn end_of_overall_time_interval(&self) -> Option<ReferenceTime> {
517        match self {
518            Self::StatisticalProcess(template) => {
519                Some(template.interval.end_of_overall_time_interval)
520            }
521            Self::ProbabilityStatisticalProcess(template) => {
522                Some(template.interval.end_of_overall_time_interval)
523            }
524            Self::PercentileStatisticalProcess(template) => {
525                Some(template.interval.end_of_overall_time_interval)
526            }
527            Self::EnsembleStatisticalProcess(template) => {
528                Some(template.interval.end_of_overall_time_interval)
529            }
530            Self::DerivedStatisticalProcess(template) => {
531                Some(template.interval.end_of_overall_time_interval)
532            }
533            Self::AnalysisOrForecast(_)
534            | Self::IndividualEnsembleForecast(_)
535            | Self::DerivedForecast(_)
536            | Self::ProbabilityForecast(_)
537            | Self::PercentileForecast(_)
538            | Self::SpatialProcess(_) => None,
539            Self::Unsupported { .. } => None,
540        }
541    }
542}
543
544impl AnalysisOrForecastTemplate {
545    const MINIMUM_LENGTH: usize = 34;
546
547    fn parse(section_bytes: &[u8]) -> Result<Self> {
548        require_len(section_bytes, Self::MINIMUM_LENGTH, "template 4.0")?;
549
550        let minutes_after_data_cutoff = (section_bytes[16] != 0xff).then_some(section_bytes[16]);
551        if minutes_after_data_cutoff.is_some_and(|minutes| minutes > 59) {
552            return Err(Error::InvalidSection {
553                section: 4,
554                reason: "minutes after data cutoff must be at most 59".into(),
555            });
556        }
557
558        Ok(Self {
559            type_of_generating_process: section_bytes[11],
560            background_generating_process_identifier: section_bytes[12],
561            generating_process_identifier: section_bytes[13],
562            hours_after_data_cutoff: (section_bytes[14..16] != [0xff; 2])
563                .then(|| u16::from_be_bytes(section_bytes[14..16].try_into().unwrap())),
564            minutes_after_data_cutoff,
565            forecast_time_unit: section_bytes[17],
566            forecast_time: decode_wmo_i32(&section_bytes[18..22]).unwrap(),
567            first_surface: parse_surface(&section_bytes[22..28]),
568            second_surface: parse_surface(&section_bytes[28..34]),
569        })
570    }
571}
572
573impl IndividualEnsembleForecastTemplate {
574    const MINIMUM_LENGTH: usize = 37;
575
576    fn parse(section_bytes: &[u8]) -> Result<Self> {
577        require_len(section_bytes, Self::MINIMUM_LENGTH, "template 4.1")?;
578
579        Ok(Self {
580            base: AnalysisOrForecastTemplate::parse(section_bytes)?,
581            type_of_ensemble_forecast: section_bytes[34],
582            perturbation_number: section_bytes[35],
583            number_of_forecasts_in_ensemble: section_bytes[36],
584        })
585    }
586}
587
588impl DerivedForecastTemplate {
589    const MINIMUM_LENGTH: usize = 36;
590
591    fn parse(section_bytes: &[u8]) -> Result<Self> {
592        require_len(section_bytes, Self::MINIMUM_LENGTH, "template 4.2")?;
593
594        Ok(Self {
595            base: AnalysisOrForecastTemplate::parse(section_bytes)?,
596            derived_forecast_type: section_bytes[34],
597            number_of_forecasts_in_ensemble: section_bytes[35],
598        })
599    }
600}
601
602impl ProbabilityForecastTemplate {
603    const MINIMUM_LENGTH: usize = 47;
604
605    fn parse(section_bytes: &[u8]) -> Result<Self> {
606        require_len(section_bytes, Self::MINIMUM_LENGTH, "template 4.5")?;
607
608        let lower = parse_probability_limit(&section_bytes[37..42]);
609        let upper = parse_probability_limit(&section_bytes[42..47]);
610        Ok(Self {
611            base: AnalysisOrForecastTemplate::parse(section_bytes)?,
612            forecast_probability_number: section_bytes[34],
613            total_number_of_forecast_probabilities: section_bytes[35],
614            probability: ProbabilityType::from_code_and_limits(section_bytes[36], lower, upper),
615        })
616    }
617}
618
619impl PercentileForecastTemplate {
620    const MINIMUM_LENGTH: usize = 35;
621
622    fn parse(section_bytes: &[u8]) -> Result<Self> {
623        require_len(section_bytes, Self::MINIMUM_LENGTH, "template 4.6")?;
624
625        let percentile_value = section_bytes[34];
626        if percentile_value > 100 {
627            return Err(Error::InvalidSection {
628                section: 4,
629                reason: format!("template 4.6 percentile {percentile_value} exceeds 100"),
630            });
631        }
632        Ok(Self {
633            base: AnalysisOrForecastTemplate::parse(section_bytes)?,
634            percentile_value,
635        })
636    }
637}
638
639impl StatisticalProcessTemplate {
640    fn parse(section_bytes: &[u8]) -> Result<Self> {
641        Ok(Self {
642            base: AnalysisOrForecastTemplate::parse(section_bytes)?,
643            interval: StatisticalInterval::parse(section_bytes, 34, "template 4.8")?,
644        })
645    }
646}
647
648impl ProbabilityStatisticalProcessTemplate {
649    fn parse(section_bytes: &[u8]) -> Result<Self> {
650        Ok(Self {
651            probability: ProbabilityForecastTemplate::parse(section_bytes)?,
652            interval: StatisticalInterval::parse(section_bytes, 47, "template 4.9")?,
653        })
654    }
655}
656
657impl PercentileStatisticalProcessTemplate {
658    fn parse(section_bytes: &[u8]) -> Result<Self> {
659        Ok(Self {
660            percentile: PercentileForecastTemplate::parse(section_bytes)?,
661            interval: StatisticalInterval::parse(section_bytes, 35, "template 4.10")?,
662        })
663    }
664}
665
666impl EnsembleStatisticalProcessTemplate {
667    fn parse(section_bytes: &[u8]) -> Result<Self> {
668        Ok(Self {
669            ensemble: IndividualEnsembleForecastTemplate::parse(section_bytes)?,
670            interval: StatisticalInterval::parse(section_bytes, 37, "template 4.11")?,
671        })
672    }
673}
674
675impl DerivedStatisticalProcessTemplate {
676    fn parse(section_bytes: &[u8]) -> Result<Self> {
677        Ok(Self {
678            derived: DerivedForecastTemplate::parse(section_bytes)?,
679            interval: StatisticalInterval::parse(section_bytes, 36, "template 4.12")?,
680        })
681    }
682}
683
684impl SpatialProcessTemplate {
685    const MINIMUM_LENGTH: usize = 37;
686
687    fn parse(section_bytes: &[u8]) -> Result<Self> {
688        require_len(section_bytes, Self::MINIMUM_LENGTH, "template 4.15")?;
689
690        Ok(Self {
691            base: AnalysisOrForecastTemplate::parse(section_bytes)?,
692            statistical_process: section_bytes[34],
693            spatial_processing: section_bytes[35],
694            number_of_points_used: section_bytes[36],
695        })
696    }
697}
698
699impl StatisticalInterval {
700    fn parse(section_bytes: &[u8], end_time_offset: usize, context: &str) -> Result<Self> {
701        let time_range_offset =
702            end_time_offset
703                .checked_add(12)
704                .ok_or_else(|| Error::InvalidSection {
705                    section: 4,
706                    reason: "statistical interval offset overflow".into(),
707                })?;
708        require_len(section_bytes, time_range_offset, context)?;
709        let time_range_count = section_bytes[end_time_offset + 7] as usize;
710        let min_len = required_time_range_template_len(time_range_offset, time_range_count)?;
711        require_len(section_bytes, min_len, context)?;
712
713        Ok(Self {
714            end_of_overall_time_interval: parse_reference_time(
715                &section_bytes[end_time_offset..end_time_offset + 7],
716                4,
717            )?,
718            number_of_missing_in_statistical_process: u32::from_be_bytes(
719                section_bytes[end_time_offset + 8..end_time_offset + 12]
720                    .try_into()
721                    .unwrap(),
722            ),
723            time_ranges: parse_statistical_time_ranges(
724                &section_bytes[time_range_offset..min_len],
725                time_range_count,
726            )?,
727        })
728    }
729}
730
731fn require_len(section_bytes: &[u8], min_len: usize, context: &str) -> Result<()> {
732    if section_bytes.len() < min_len {
733        return Err(Error::InvalidSection {
734            section: 4,
735            reason: format!(
736                "{context} requires at least {min_len} bytes, got {}",
737                section_bytes.len()
738            ),
739        });
740    }
741    Ok(())
742}
743
744fn required_time_range_template_len(
745    time_range_offset: usize,
746    time_range_count: usize,
747) -> Result<usize> {
748    time_range_count
749        .checked_mul(12)
750        .and_then(|len| time_range_offset.checked_add(len))
751        .ok_or_else(|| Error::InvalidSection {
752            section: 4,
753            reason: "statistical time range length overflow".into(),
754        })
755}
756
757fn parse_reference_time(bytes: &[u8], section: u8) -> Result<ReferenceTime> {
758    let reference_time = ReferenceTime {
759        year: u16::from_be_bytes(bytes[0..2].try_into().unwrap()),
760        month: bytes[2],
761        day: bytes[3],
762        hour: bytes[4],
763        minute: bytes[5],
764        second: bytes[6],
765    };
766    reference_time.validate_in_section(section)?;
767    Ok(reference_time)
768}
769
770fn parse_statistical_time_ranges(
771    bytes: &[u8],
772    time_range_count: usize,
773) -> Result<Vec<StatisticalTimeRange>> {
774    let mut ranges = Vec::new();
775    ranges
776        .try_reserve_exact(time_range_count)
777        .map_err(|error| {
778            Error::allocation(
779                "statistical time-range descriptors",
780                time_range_count,
781                error,
782            )
783        })?;
784    for range in bytes.chunks_exact(12).take(time_range_count) {
785        ranges.push(StatisticalTimeRange {
786            type_of_statistical_processing: range[0],
787            type_of_time_increment: range[1],
788            time_range_unit: range[2],
789            time_range_length: u32::from_be_bytes(range[3..7].try_into().unwrap()),
790            time_increment_unit: range[7],
791            time_increment: u32::from_be_bytes(range[8..12].try_into().unwrap()),
792        });
793    }
794    Ok(ranges)
795}
796
797fn parse_probability_limit(bytes: &[u8]) -> Option<ProbabilityLimit> {
798    if bytes[0] == 0xff || bytes[1..5] == [0xff; 4] {
799        return None;
800    }
801
802    Some(ProbabilityLimit {
803        scale_factor: decode_wmo_i8(bytes[0]),
804        scaled_value: decode_wmo_i32(&bytes[1..5])?,
805    })
806}
807
808fn parse_surface(section_bytes: &[u8]) -> Option<FixedSurface> {
809    let surface_type = section_bytes[0];
810    if surface_type == 255 {
811        return None;
812    }
813
814    let value = if section_bytes[1] == 0xff || section_bytes[2..6] == [0xff; 4] {
815        None
816    } else {
817        Some(ScaledValue {
818            scale_factor: decode_wmo_i8(section_bytes[1]),
819            scaled_value: decode_wmo_i32(&section_bytes[2..6])?,
820        })
821    };
822
823    Some(FixedSurface {
824        surface_type,
825        value,
826    })
827}
828
829#[cfg(test)]
830mod tests {
831    use super::{
832        AnalysisOrForecastTemplate, Identification, ProbabilityType, ProductDefinition,
833        ProductDefinitionTemplate,
834    };
835    use crate::error::Error;
836    use crate::metadata::ReferenceTime;
837
838    #[test]
839    fn parses_identification_section() {
840        let section = valid_identification_section();
841
842        let id = Identification::parse(&section).unwrap();
843        assert_eq!(id.center_id, 7);
844        assert_eq!(id.reference_year, 2026);
845        assert_eq!(id.reference_hour, 12);
846    }
847
848    #[test]
849    fn rejects_invalid_identification_reference_time() {
850        let mut section = valid_identification_section();
851        section[14] = 2;
852        section[15] = 29;
853        let err = Identification::parse(&section).unwrap_err();
854        assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
855        assert!(err.to_string().contains("invalid reference timestamp"));
856
857        let mut section = valid_identification_section();
858        section[18] = 60;
859        let err = Identification::parse(&section).unwrap_err();
860        assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
861    }
862
863    #[test]
864    fn parses_product_definition_template_zero_fields() {
865        let section = product_section_template_zero();
866
867        let product = ProductDefinition::parse(&section).unwrap();
868        assert_eq!(product.parameter_category, 2);
869        assert_eq!(product.parameter_number, 3);
870        assert_eq!(product.template_number(), 0);
871        assert_eq!(product.forecast_time(), Some(6));
872        assert_eq!(
873            product.first_surface().unwrap().scaled_value_f64(),
874            Some(850.0)
875        );
876        assert_eq!(
877            product.template,
878            ProductDefinitionTemplate::AnalysisOrForecast(AnalysisOrForecastTemplate {
879                type_of_generating_process: 2,
880                background_generating_process_identifier: 0,
881                generating_process_identifier: 0,
882                hours_after_data_cutoff: Some(0),
883                minutes_after_data_cutoff: Some(0),
884                forecast_time_unit: 1,
885                forecast_time: 6,
886                first_surface: product.first_surface().cloned(),
887                second_surface: None,
888            })
889        );
890    }
891
892    #[test]
893    fn parses_individual_ensemble_forecast_template() {
894        let mut section = product_section_template_zero();
895        section.resize(37, 0);
896        section[..4].copy_from_slice(&(37u32).to_be_bytes());
897        section[7..9].copy_from_slice(&1u16.to_be_bytes());
898        section[34] = 1;
899        section[35] = 2;
900        section[36] = 20;
901
902        let product = ProductDefinition::parse(&section).unwrap();
903        assert_eq!(product.template_number(), 1);
904        assert_eq!(product.forecast_time(), Some(6));
905        match product.template {
906            ProductDefinitionTemplate::IndividualEnsembleForecast(template) => {
907                assert_eq!(template.type_of_ensemble_forecast, 1);
908                assert_eq!(template.perturbation_number, 2);
909                assert_eq!(template.number_of_forecasts_in_ensemble, 20);
910                assert_eq!(template.base.forecast_time, 6);
911            }
912            other => panic!("expected template 4.1, got {other:?}"),
913        }
914    }
915
916    #[test]
917    fn parses_derived_forecast_template() {
918        let mut section = product_section_template_zero();
919        section.resize(36, 0);
920        set_product_template(&mut section, 2);
921        section[34] = 4;
922        section[35] = 50;
923
924        let product = ProductDefinition::parse(&section).unwrap();
925        let ProductDefinitionTemplate::DerivedForecast(template) = product.template else {
926            panic!("expected template 4.2");
927        };
928        assert_eq!(template.derived_forecast_type, 4);
929        assert_eq!(template.number_of_forecasts_in_ensemble, 50);
930        assert_eq!(template.base.forecast_time, 6);
931    }
932
933    #[test]
934    fn parses_probability_forecast_with_typed_thresholds() {
935        let mut section = product_section_template_zero();
936        section.resize(47, 0xff);
937        set_product_template(&mut section, 5);
938        section[34] = 2;
939        section[35] = 10;
940        section[36] = 2;
941        section[37] = 1;
942        section[38..42].copy_from_slice(&crate::binary::encode_wmo_i32(-125).unwrap());
943        section[42] = 1;
944        section[43..47].copy_from_slice(&crate::binary::encode_wmo_i32(250).unwrap());
945
946        let product = ProductDefinition::parse(&section).unwrap();
947        let ProductDefinitionTemplate::ProbabilityForecast(template) = product.template else {
948            panic!("expected template 4.5");
949        };
950        assert_eq!(template.forecast_probability_number, 2);
951        assert_eq!(template.total_number_of_forecast_probabilities, 10);
952        let ProbabilityType::BetweenLimits { lower, upper } = template.probability else {
953            panic!("expected a between-limits probability");
954        };
955        assert_eq!(lower.value_f64(), -12.5);
956        assert_eq!(upper.value_f64(), 25.0);
957    }
958
959    #[test]
960    fn preserves_noncanonical_probability_limit_combinations() {
961        let mut section = product_section_template_zero();
962        section.resize(47, 0xff);
963        set_product_template(&mut section, 5);
964        section[36] = 0;
965
966        let product = ProductDefinition::parse(&section).unwrap();
967        let ProductDefinitionTemplate::ProbabilityForecast(template) = product.template else {
968            panic!("expected template 4.5");
969        };
970        assert_eq!(
971            template.probability,
972            ProbabilityType::Other {
973                code: 0,
974                lower: None,
975                upper: None,
976            }
977        );
978
979        section[36] = 7;
980        let product = ProductDefinition::parse(&section).unwrap();
981        let ProductDefinitionTemplate::ProbabilityForecast(template) = product.template else {
982            panic!("expected template 4.5");
983        };
984        assert_eq!(template.probability, ProbabilityType::NearNormal);
985    }
986
987    #[test]
988    fn parses_percentile_forecast_and_rejects_values_above_one_hundred() {
989        let mut section = product_section_template_zero();
990        section.resize(35, 0);
991        set_product_template(&mut section, 6);
992        section[34] = 90;
993
994        let product = ProductDefinition::parse(&section).unwrap();
995        let ProductDefinitionTemplate::PercentileForecast(template) = product.template else {
996            panic!("expected template 4.6");
997        };
998        assert_eq!(template.percentile_value, 90);
999
1000        section[34] = 101;
1001        assert!(matches!(
1002            ProductDefinition::parse(&section),
1003            Err(Error::InvalidSection { section: 4, .. })
1004        ));
1005    }
1006
1007    #[test]
1008    fn parses_signed_forecast_time_and_process_metadata() {
1009        let mut section = product_section_template_zero();
1010        section[12] = 7;
1011        section[13] = 42;
1012        section[14..16].copy_from_slice(&12u16.to_be_bytes());
1013        section[16] = 30;
1014        section[18..22].copy_from_slice(&crate::binary::encode_wmo_i32(-6).unwrap());
1015
1016        let product = ProductDefinition::parse(&section).unwrap();
1017        assert_eq!(product.type_of_generating_process(), Some(2));
1018        assert_eq!(product.generating_process_identifier(), Some(42));
1019        assert_eq!(product.forecast_time(), Some(-6));
1020        let ProductDefinitionTemplate::AnalysisOrForecast(template) = product.template else {
1021            panic!("expected template 4.0");
1022        };
1023        assert_eq!(template.background_generating_process_identifier, 7);
1024        assert_eq!(template.hours_after_data_cutoff, Some(12));
1025        assert_eq!(template.minutes_after_data_cutoff, Some(30));
1026    }
1027
1028    #[test]
1029    fn parses_missing_cutoff_metadata_and_rejects_invalid_minutes() {
1030        let mut section = product_section_template_zero();
1031        section[14..16].copy_from_slice(&u16::MAX.to_be_bytes());
1032        section[16] = u8::MAX;
1033        let product = ProductDefinition::parse(&section).unwrap();
1034        let ProductDefinitionTemplate::AnalysisOrForecast(template) = product.template else {
1035            panic!("expected template 4.0");
1036        };
1037        assert_eq!(template.hours_after_data_cutoff, None);
1038        assert_eq!(template.minutes_after_data_cutoff, None);
1039
1040        section[16] = 60;
1041        assert!(matches!(
1042            ProductDefinition::parse(&section),
1043            Err(Error::InvalidSection { section: 4, .. })
1044        ));
1045    }
1046
1047    #[test]
1048    fn parses_statistical_process_template() {
1049        let section = product_section_template_eight();
1050
1051        let product = ProductDefinition::parse(&section).unwrap();
1052        assert_eq!(product.template_number(), 8);
1053        assert_eq!(product.forecast_time(), Some(6));
1054        assert_eq!(
1055            product.end_of_overall_time_interval(),
1056            Some(ReferenceTime {
1057                year: 2026,
1058                month: 3,
1059                day: 20,
1060                hour: 18,
1061                minute: 0,
1062                second: 0,
1063            })
1064        );
1065        match product.template {
1066            ProductDefinitionTemplate::StatisticalProcess(template) => {
1067                assert_eq!(template.interval.time_ranges.len(), 1);
1068                assert_eq!(
1069                    template.interval.time_ranges[0].type_of_statistical_processing,
1070                    1
1071                );
1072                assert_eq!(template.interval.time_ranges[0].time_range_length, 6);
1073            }
1074            other => panic!("expected template 4.8, got {other:?}"),
1075        }
1076    }
1077
1078    #[test]
1079    fn parses_ensemble_statistical_process_template() {
1080        let mut section = product_section_template_eight();
1081        section.resize(61, 0);
1082        section[..4].copy_from_slice(&(61u32).to_be_bytes());
1083        section[7..9].copy_from_slice(&11u16.to_be_bytes());
1084        section.copy_within(34..58, 37);
1085        section[34] = 1;
1086        section[35] = 3;
1087        section[36] = 20;
1088
1089        let product = ProductDefinition::parse(&section).unwrap();
1090        assert_eq!(product.template_number(), 11);
1091        assert_eq!(
1092            product.end_of_overall_time_interval(),
1093            Some(ReferenceTime {
1094                year: 2026,
1095                month: 3,
1096                day: 20,
1097                hour: 18,
1098                minute: 0,
1099                second: 0,
1100            })
1101        );
1102        match product.template {
1103            ProductDefinitionTemplate::EnsembleStatisticalProcess(template) => {
1104                assert_eq!(template.ensemble.perturbation_number, 3);
1105                assert_eq!(template.interval.time_ranges.len(), 1);
1106            }
1107            other => panic!("expected template 4.11, got {other:?}"),
1108        }
1109    }
1110
1111    #[test]
1112    fn parses_probability_statistical_process_template() {
1113        let mut section = product_section_template_zero();
1114        section.resize(71, 0xff);
1115        set_product_template(&mut section, 9);
1116        section[34] = 1;
1117        section[35] = 10;
1118        section[36] = 3;
1119        section[37] = 1;
1120        section[38..42].copy_from_slice(&crate::binary::encode_wmo_i32(125).unwrap());
1121        set_statistical_interval(&mut section, 47);
1122
1123        let product = ProductDefinition::parse(&section).unwrap();
1124        let ProductDefinitionTemplate::ProbabilityStatisticalProcess(template) = product.template
1125        else {
1126            panic!("expected template 4.9");
1127        };
1128        assert_eq!(
1129            template.probability.probability,
1130            ProbabilityType::AboveLowerLimit(super::ProbabilityLimit {
1131                scale_factor: 1,
1132                scaled_value: 125,
1133            })
1134        );
1135        assert_eq!(template.interval.time_ranges.len(), 1);
1136        assert_eq!(template.interval.end_of_overall_time_interval.hour, 18);
1137    }
1138
1139    #[test]
1140    fn parses_percentile_statistical_process_template() {
1141        let mut section = product_section_template_zero();
1142        section.resize(59, 0);
1143        set_product_template(&mut section, 10);
1144        section[34] = 75;
1145        set_statistical_interval(&mut section, 35);
1146
1147        let product = ProductDefinition::parse(&section).unwrap();
1148        let ProductDefinitionTemplate::PercentileStatisticalProcess(template) = product.template
1149        else {
1150            panic!("expected template 4.10");
1151        };
1152        assert_eq!(template.percentile.percentile_value, 75);
1153        assert_eq!(template.interval.time_ranges.len(), 1);
1154    }
1155
1156    #[test]
1157    fn parses_derived_statistical_process_template() {
1158        let mut section = product_section_template_zero();
1159        section.resize(60, 0);
1160        set_product_template(&mut section, 12);
1161        section[34] = 1;
1162        section[35] = 20;
1163        set_statistical_interval(&mut section, 36);
1164
1165        let product = ProductDefinition::parse(&section).unwrap();
1166        let ProductDefinitionTemplate::DerivedStatisticalProcess(template) = product.template
1167        else {
1168            panic!("expected template 4.12");
1169        };
1170        assert_eq!(template.derived.derived_forecast_type, 1);
1171        assert_eq!(template.derived.number_of_forecasts_in_ensemble, 20);
1172        assert_eq!(template.interval.time_ranges.len(), 1);
1173    }
1174
1175    #[test]
1176    fn parses_spatial_process_template() {
1177        let mut section = product_section_template_zero();
1178        section.resize(37, 0);
1179        set_product_template(&mut section, 15);
1180        section[34] = 1;
1181        section[35] = 2;
1182        section[36] = 4;
1183
1184        let product = ProductDefinition::parse(&section).unwrap();
1185        let ProductDefinitionTemplate::SpatialProcess(template) = product.template else {
1186            panic!("expected template 4.15");
1187        };
1188        assert_eq!(template.statistical_process, 1);
1189        assert_eq!(template.spatial_processing, 2);
1190        assert_eq!(template.number_of_points_used, 4);
1191        assert_eq!(template.base.forecast_time, 6);
1192    }
1193
1194    #[test]
1195    fn rejects_invalid_statistical_process_end_time() {
1196        let mut section = product_section_template_eight();
1197        section[36] = 2;
1198        section[37] = 29;
1199
1200        let err = ProductDefinition::parse(&section).unwrap_err();
1201        assert!(matches!(err, Error::InvalidSection { section: 4, .. }));
1202        assert!(err.to_string().contains("invalid reference timestamp"));
1203    }
1204
1205    #[test]
1206    fn preserves_unsupported_product_definition_templates() {
1207        let mut section = vec![0u8; 34];
1208        section[..4].copy_from_slice(&(34u32).to_be_bytes());
1209        section[4] = 4;
1210        section[7..9].copy_from_slice(&99u16.to_be_bytes());
1211        section[9] = 2;
1212        section[10] = 3;
1213
1214        section[11..].copy_from_slice(&[0x5a; 23]);
1215
1216        let product = ProductDefinition::parse(&section).unwrap();
1217        assert_eq!(product.template_number(), 99);
1218        assert_eq!(product.forecast_time(), None);
1219        assert_eq!(product.first_surface(), None);
1220        assert!(matches!(
1221            product.template,
1222            ProductDefinitionTemplate::Unsupported { number: 99, ref raw }
1223                if raw == &[0x5a; 23]
1224        ));
1225    }
1226
1227    #[test]
1228    fn preserves_surface_type_when_numeric_level_is_missing() {
1229        let mut section = product_section_template_zero();
1230        section[23] = 0xff;
1231        section[24..28].copy_from_slice(&[0xff; 4]);
1232
1233        let product = ProductDefinition::parse(&section).unwrap();
1234        let surface = product.first_surface().unwrap();
1235        assert_eq!(surface.surface_type, 103);
1236        assert_eq!(surface.value, None);
1237        assert_eq!(surface.scaled_value_f64(), None);
1238    }
1239
1240    #[test]
1241    fn rejects_truncated_template_zero_sections() {
1242        let mut section = vec![0u8; 33];
1243        section[..4].copy_from_slice(&(33u32).to_be_bytes());
1244        section[4] = 4;
1245        section[7..9].copy_from_slice(&0u16.to_be_bytes());
1246        section[9] = 2;
1247        section[10] = 3;
1248
1249        let err = ProductDefinition::parse(&section).unwrap_err();
1250        assert!(matches!(err, Error::InvalidSection { section: 4, .. }));
1251    }
1252
1253    #[test]
1254    fn rejects_truncated_instantaneous_product_templates() {
1255        for (template, length) in [(2, 35), (5, 46), (6, 34), (15, 36)] {
1256            let mut section = product_section_template_zero();
1257            section.resize(length, 0);
1258            set_product_template(&mut section, template);
1259            assert!(matches!(
1260                ProductDefinition::parse(&section),
1261                Err(Error::InvalidSection { section: 4, .. })
1262            ));
1263        }
1264    }
1265
1266    #[test]
1267    fn rejects_truncated_interval_product_templates() {
1268        for (template, length) in [(8, 45), (9, 58), (10, 46), (11, 48), (12, 47)] {
1269            let mut section = product_section_template_zero();
1270            section.resize(length, 0);
1271            set_product_template(&mut section, template);
1272            assert!(matches!(
1273                ProductDefinition::parse(&section),
1274                Err(Error::InvalidSection { section: 4, .. })
1275            ));
1276        }
1277    }
1278
1279    fn product_section_template_zero() -> Vec<u8> {
1280        let mut section = vec![0u8; 34];
1281        section[..4].copy_from_slice(&(34u32).to_be_bytes());
1282        section[4] = 4;
1283        section[7..9].copy_from_slice(&0u16.to_be_bytes());
1284        section[9] = 2;
1285        section[10] = 3;
1286        section[11] = 2;
1287        section[17] = 1;
1288        section[18..22].copy_from_slice(&6u32.to_be_bytes());
1289        section[22] = 103;
1290        section[23] = 0;
1291        section[24..28].copy_from_slice(&850u32.to_be_bytes());
1292        section[28] = 255;
1293        section
1294    }
1295
1296    fn product_section_template_eight() -> Vec<u8> {
1297        let mut section = product_section_template_zero();
1298        section.resize(58, 0);
1299        set_product_template(&mut section, 8);
1300        set_statistical_interval(&mut section, 34);
1301        section
1302    }
1303
1304    fn set_product_template(section: &mut [u8], template: u16) {
1305        let length = u32::try_from(section.len()).unwrap();
1306        section[..4].copy_from_slice(&length.to_be_bytes());
1307        section[7..9].copy_from_slice(&template.to_be_bytes());
1308    }
1309
1310    fn set_statistical_interval(section: &mut [u8], offset: usize) {
1311        section[offset..offset + 2].copy_from_slice(&2026u16.to_be_bytes());
1312        section[offset + 2] = 3;
1313        section[offset + 3] = 20;
1314        section[offset + 4] = 18;
1315        section[offset + 5] = 0;
1316        section[offset + 6] = 0;
1317        section[offset + 7] = 1;
1318        section[offset + 8..offset + 12].copy_from_slice(&0u32.to_be_bytes());
1319        let range = offset + 12;
1320        section[range] = 1;
1321        section[range + 1] = 2;
1322        section[range + 2] = 1;
1323        section[range + 3..range + 7].copy_from_slice(&6u32.to_be_bytes());
1324        section[range + 7] = 255;
1325        section[range + 8..range + 12].copy_from_slice(&0u32.to_be_bytes());
1326    }
1327
1328    fn valid_identification_section() -> Vec<u8> {
1329        let mut section = vec![0u8; 21];
1330        section[..4].copy_from_slice(&(21u32).to_be_bytes());
1331        section[4] = 1;
1332        section[5..7].copy_from_slice(&7u16.to_be_bytes());
1333        section[7..9].copy_from_slice(&14u16.to_be_bytes());
1334        section[9] = 35;
1335        section[10] = 1;
1336        section[11] = 1;
1337        section[12..14].copy_from_slice(&2026u16.to_be_bytes());
1338        section[14] = 3;
1339        section[15] = 20;
1340        section[16] = 12;
1341        section[17] = 30;
1342        section[18] = 45;
1343        section[19] = 0;
1344        section[20] = 1;
1345        section
1346    }
1347}