1use crate::error::{Error, Result};
4use crate::metadata::ReferenceTime;
5use crate::parameter;
6use crate::util::{grib_i32, grib_i8};
7
8#[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#[derive(Debug, Clone, PartialEq)]
71pub struct FixedSurface {
72 pub surface_type: u8,
73 pub scale_factor: i16,
74 pub scaled_value: i32,
75}
76
77impl FixedSurface {
78 pub fn scaled_value_f64(&self) -> f64 {
79 let factor = 10.0_f64.powi(-(self.scale_factor as i32));
80 self.scaled_value as f64 * factor
81 }
82}
83
84#[derive(Debug, Clone, PartialEq)]
86pub struct ProductDefinition {
87 pub parameter_category: u8,
88 pub parameter_number: u8,
89 pub template: ProductDefinitionTemplate,
90}
91
92#[derive(Debug, Clone, PartialEq)]
94pub enum ProductDefinitionTemplate {
95 AnalysisOrForecast(AnalysisOrForecastTemplate),
96 IndividualEnsembleForecast(IndividualEnsembleForecastTemplate),
97 StatisticalProcess(StatisticalProcessTemplate),
98 EnsembleStatisticalProcess(EnsembleStatisticalProcessTemplate),
99}
100
101#[derive(Debug, Clone, PartialEq)]
103pub struct AnalysisOrForecastTemplate {
104 pub generating_process: u8,
105 pub forecast_time_unit: u8,
106 pub forecast_time: u32,
107 pub first_surface: Option<FixedSurface>,
108 pub second_surface: Option<FixedSurface>,
109}
110
111#[derive(Debug, Clone, PartialEq)]
113pub struct IndividualEnsembleForecastTemplate {
114 pub base: AnalysisOrForecastTemplate,
115 pub type_of_ensemble_forecast: u8,
116 pub perturbation_number: u8,
117 pub number_of_forecasts_in_ensemble: u8,
118}
119
120#[derive(Debug, Clone, PartialEq)]
122pub struct StatisticalProcessTemplate {
123 pub base: AnalysisOrForecastTemplate,
124 pub end_of_overall_time_interval: ReferenceTime,
125 pub number_of_missing_in_statistical_process: u32,
126 pub time_ranges: Vec<StatisticalTimeRange>,
127}
128
129#[derive(Debug, Clone, PartialEq)]
131pub struct EnsembleStatisticalProcessTemplate {
132 pub ensemble: IndividualEnsembleForecastTemplate,
133 pub end_of_overall_time_interval: ReferenceTime,
134 pub number_of_missing_in_statistical_process: u32,
135 pub time_ranges: Vec<StatisticalTimeRange>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct StatisticalTimeRange {
142 pub type_of_statistical_processing: u8,
143 pub type_of_time_increment: u8,
144 pub time_range_unit: u8,
145 pub time_range_length: u32,
146 pub time_increment_unit: u8,
147 pub time_increment: u32,
148}
149
150impl ProductDefinition {
151 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
152 if section_bytes.len() < 11 {
153 return Err(Error::InvalidSection {
154 section: 4,
155 reason: format!("expected at least 11 bytes, got {}", section_bytes.len()),
156 });
157 }
158 if section_bytes[4] != 4 {
159 return Err(Error::InvalidSection {
160 section: section_bytes[4],
161 reason: "not a product definition section".into(),
162 });
163 }
164
165 let template = u16::from_be_bytes(section_bytes[7..9].try_into().unwrap());
166 let parameter_category = section_bytes[9];
167 let parameter_number = section_bytes[10];
168
169 Ok(Self {
170 parameter_category,
171 parameter_number,
172 template: ProductDefinitionTemplate::parse(template, section_bytes)?,
173 })
174 }
175
176 pub fn parameter_name(&self, discipline: u8) -> &'static str {
177 parameter::parameter_name(discipline, self.parameter_category, self.parameter_number)
178 }
179
180 pub fn parameter_description(&self, discipline: u8) -> &'static str {
181 parameter::parameter_description(discipline, self.parameter_category, self.parameter_number)
182 }
183
184 pub fn template_number(&self) -> u16 {
185 self.template.number()
186 }
187
188 pub fn generating_process(&self) -> Option<u8> {
189 Some(self.template.base().generating_process)
190 }
191
192 pub fn forecast_time_unit(&self) -> Option<u8> {
193 Some(self.template.base().forecast_time_unit)
194 }
195
196 pub fn forecast_time(&self) -> Option<u32> {
197 Some(self.template.base().forecast_time)
198 }
199
200 pub fn first_surface(&self) -> Option<&FixedSurface> {
201 self.template.base().first_surface.as_ref()
202 }
203
204 pub fn second_surface(&self) -> Option<&FixedSurface> {
205 self.template.base().second_surface.as_ref()
206 }
207
208 pub fn end_of_overall_time_interval(&self) -> Option<ReferenceTime> {
209 self.template.end_of_overall_time_interval()
210 }
211}
212
213impl ProductDefinitionTemplate {
214 pub fn parse(template: u16, section_bytes: &[u8]) -> Result<Self> {
215 match template {
216 0 => Ok(Self::AnalysisOrForecast(AnalysisOrForecastTemplate::parse(
217 section_bytes,
218 )?)),
219 1 => Ok(Self::IndividualEnsembleForecast(
220 IndividualEnsembleForecastTemplate::parse(section_bytes)?,
221 )),
222 8 => Ok(Self::StatisticalProcess(StatisticalProcessTemplate::parse(
223 section_bytes,
224 )?)),
225 11 => Ok(Self::EnsembleStatisticalProcess(
226 EnsembleStatisticalProcessTemplate::parse(section_bytes)?,
227 )),
228 other => Err(Error::UnsupportedProductTemplate(other)),
229 }
230 }
231
232 pub const fn number(&self) -> u16 {
233 match self {
234 Self::AnalysisOrForecast(_) => 0,
235 Self::IndividualEnsembleForecast(_) => 1,
236 Self::StatisticalProcess(_) => 8,
237 Self::EnsembleStatisticalProcess(_) => 11,
238 }
239 }
240
241 fn base(&self) -> &AnalysisOrForecastTemplate {
242 match self {
243 Self::AnalysisOrForecast(template) => template,
244 Self::IndividualEnsembleForecast(template) => &template.base,
245 Self::StatisticalProcess(template) => &template.base,
246 Self::EnsembleStatisticalProcess(template) => &template.ensemble.base,
247 }
248 }
249
250 fn end_of_overall_time_interval(&self) -> Option<ReferenceTime> {
251 match self {
252 Self::StatisticalProcess(template) => Some(template.end_of_overall_time_interval),
253 Self::EnsembleStatisticalProcess(template) => {
254 Some(template.end_of_overall_time_interval)
255 }
256 Self::AnalysisOrForecast(_) | Self::IndividualEnsembleForecast(_) => None,
257 }
258 }
259}
260
261impl AnalysisOrForecastTemplate {
262 const MINIMUM_LENGTH: usize = 34;
263
264 fn parse(section_bytes: &[u8]) -> Result<Self> {
265 require_len(section_bytes, Self::MINIMUM_LENGTH, "template 4.0")?;
266
267 Ok(Self {
268 generating_process: section_bytes[11],
269 forecast_time_unit: section_bytes[17],
270 forecast_time: u32::from_be_bytes(section_bytes[18..22].try_into().unwrap()),
271 first_surface: parse_surface(§ion_bytes[22..28]),
272 second_surface: parse_surface(§ion_bytes[28..34]),
273 })
274 }
275}
276
277impl IndividualEnsembleForecastTemplate {
278 const MINIMUM_LENGTH: usize = 37;
279
280 fn parse(section_bytes: &[u8]) -> Result<Self> {
281 require_len(section_bytes, Self::MINIMUM_LENGTH, "template 4.1")?;
282
283 Ok(Self {
284 base: AnalysisOrForecastTemplate::parse(section_bytes)?,
285 type_of_ensemble_forecast: section_bytes[34],
286 perturbation_number: section_bytes[35],
287 number_of_forecasts_in_ensemble: section_bytes[36],
288 })
289 }
290}
291
292impl StatisticalProcessTemplate {
293 const TIME_RANGE_OFFSET: usize = 46;
294
295 fn parse(section_bytes: &[u8]) -> Result<Self> {
296 require_len(section_bytes, Self::TIME_RANGE_OFFSET, "template 4.8")?;
297 let time_range_count = section_bytes[41] as usize;
298 let min_len = required_time_range_template_len(Self::TIME_RANGE_OFFSET, time_range_count)?;
299 require_len(section_bytes, min_len, "template 4.8")?;
300
301 Ok(Self {
302 base: AnalysisOrForecastTemplate::parse(section_bytes)?,
303 end_of_overall_time_interval: parse_reference_time(§ion_bytes[34..41], 4)?,
304 number_of_missing_in_statistical_process: u32::from_be_bytes(
305 section_bytes[42..46].try_into().unwrap(),
306 ),
307 time_ranges: parse_statistical_time_ranges(
308 §ion_bytes[Self::TIME_RANGE_OFFSET..min_len],
309 time_range_count,
310 ),
311 })
312 }
313}
314
315impl EnsembleStatisticalProcessTemplate {
316 const TIME_RANGE_OFFSET: usize = 49;
317
318 fn parse(section_bytes: &[u8]) -> Result<Self> {
319 require_len(section_bytes, Self::TIME_RANGE_OFFSET, "template 4.11")?;
320 let time_range_count = section_bytes[44] as usize;
321 let min_len = required_time_range_template_len(Self::TIME_RANGE_OFFSET, time_range_count)?;
322 require_len(section_bytes, min_len, "template 4.11")?;
323
324 Ok(Self {
325 ensemble: IndividualEnsembleForecastTemplate::parse(section_bytes)?,
326 end_of_overall_time_interval: parse_reference_time(§ion_bytes[37..44], 4)?,
327 number_of_missing_in_statistical_process: u32::from_be_bytes(
328 section_bytes[45..49].try_into().unwrap(),
329 ),
330 time_ranges: parse_statistical_time_ranges(
331 §ion_bytes[Self::TIME_RANGE_OFFSET..min_len],
332 time_range_count,
333 ),
334 })
335 }
336}
337
338fn require_len(section_bytes: &[u8], min_len: usize, context: &str) -> Result<()> {
339 if section_bytes.len() < min_len {
340 return Err(Error::InvalidSection {
341 section: 4,
342 reason: format!(
343 "{context} requires at least {min_len} bytes, got {}",
344 section_bytes.len()
345 ),
346 });
347 }
348 Ok(())
349}
350
351fn required_time_range_template_len(
352 time_range_offset: usize,
353 time_range_count: usize,
354) -> Result<usize> {
355 time_range_count
356 .checked_mul(12)
357 .and_then(|len| time_range_offset.checked_add(len))
358 .ok_or_else(|| Error::InvalidSection {
359 section: 4,
360 reason: "statistical time range length overflow".into(),
361 })
362}
363
364fn parse_reference_time(bytes: &[u8], section: u8) -> Result<ReferenceTime> {
365 let reference_time = ReferenceTime {
366 year: u16::from_be_bytes(bytes[0..2].try_into().unwrap()),
367 month: bytes[2],
368 day: bytes[3],
369 hour: bytes[4],
370 minute: bytes[5],
371 second: bytes[6],
372 };
373 reference_time.validate_in_section(section)?;
374 Ok(reference_time)
375}
376
377fn parse_statistical_time_ranges(
378 bytes: &[u8],
379 time_range_count: usize,
380) -> Vec<StatisticalTimeRange> {
381 bytes
382 .chunks_exact(12)
383 .take(time_range_count)
384 .map(|range| StatisticalTimeRange {
385 type_of_statistical_processing: range[0],
386 type_of_time_increment: range[1],
387 time_range_unit: range[2],
388 time_range_length: u32::from_be_bytes(range[3..7].try_into().unwrap()),
389 time_increment_unit: range[7],
390 time_increment: u32::from_be_bytes(range[8..12].try_into().unwrap()),
391 })
392 .collect()
393}
394
395fn parse_surface(section_bytes: &[u8]) -> Option<FixedSurface> {
396 let surface_type = section_bytes[0];
397 if surface_type == 255 {
398 return None;
399 }
400
401 Some(FixedSurface {
402 surface_type,
403 scale_factor: grib_i8(section_bytes[1]),
404 scaled_value: grib_i32(§ion_bytes[2..6])?,
405 })
406}
407
408#[cfg(test)]
409mod tests {
410 use super::{
411 AnalysisOrForecastTemplate, Identification, ProductDefinition, ProductDefinitionTemplate,
412 };
413 use crate::error::Error;
414 use crate::metadata::ReferenceTime;
415
416 #[test]
417 fn parses_identification_section() {
418 let section = valid_identification_section();
419
420 let id = Identification::parse(§ion).unwrap();
421 assert_eq!(id.center_id, 7);
422 assert_eq!(id.reference_year, 2026);
423 assert_eq!(id.reference_hour, 12);
424 }
425
426 #[test]
427 fn rejects_invalid_identification_reference_time() {
428 let mut section = valid_identification_section();
429 section[14] = 2;
430 section[15] = 29;
431 let err = Identification::parse(§ion).unwrap_err();
432 assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
433 assert!(err.to_string().contains("invalid reference timestamp"));
434
435 let mut section = valid_identification_section();
436 section[18] = 60;
437 let err = Identification::parse(§ion).unwrap_err();
438 assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
439 }
440
441 #[test]
442 fn parses_product_definition_template_zero_fields() {
443 let section = product_section_template_zero();
444
445 let product = ProductDefinition::parse(§ion).unwrap();
446 assert_eq!(product.parameter_category, 2);
447 assert_eq!(product.parameter_number, 3);
448 assert_eq!(product.template_number(), 0);
449 assert_eq!(product.forecast_time(), Some(6));
450 assert_eq!(product.first_surface().unwrap().scaled_value_f64(), 850.0);
451 assert_eq!(
452 product.template,
453 ProductDefinitionTemplate::AnalysisOrForecast(AnalysisOrForecastTemplate {
454 generating_process: 2,
455 forecast_time_unit: 1,
456 forecast_time: 6,
457 first_surface: product.first_surface().cloned(),
458 second_surface: None,
459 })
460 );
461 }
462
463 #[test]
464 fn parses_individual_ensemble_forecast_template() {
465 let mut section = product_section_template_zero();
466 section.resize(37, 0);
467 section[..4].copy_from_slice(&(37u32).to_be_bytes());
468 section[7..9].copy_from_slice(&1u16.to_be_bytes());
469 section[34] = 1;
470 section[35] = 2;
471 section[36] = 20;
472
473 let product = ProductDefinition::parse(§ion).unwrap();
474 assert_eq!(product.template_number(), 1);
475 assert_eq!(product.forecast_time(), Some(6));
476 match product.template {
477 ProductDefinitionTemplate::IndividualEnsembleForecast(template) => {
478 assert_eq!(template.type_of_ensemble_forecast, 1);
479 assert_eq!(template.perturbation_number, 2);
480 assert_eq!(template.number_of_forecasts_in_ensemble, 20);
481 assert_eq!(template.base.forecast_time, 6);
482 }
483 other => panic!("expected template 4.1, got {other:?}"),
484 }
485 }
486
487 #[test]
488 fn parses_statistical_process_template() {
489 let section = product_section_template_eight();
490
491 let product = ProductDefinition::parse(§ion).unwrap();
492 assert_eq!(product.template_number(), 8);
493 assert_eq!(product.forecast_time(), Some(6));
494 assert_eq!(
495 product.end_of_overall_time_interval(),
496 Some(ReferenceTime {
497 year: 2026,
498 month: 3,
499 day: 20,
500 hour: 18,
501 minute: 0,
502 second: 0,
503 })
504 );
505 match product.template {
506 ProductDefinitionTemplate::StatisticalProcess(template) => {
507 assert_eq!(template.time_ranges.len(), 1);
508 assert_eq!(template.time_ranges[0].type_of_statistical_processing, 1);
509 assert_eq!(template.time_ranges[0].time_range_length, 6);
510 }
511 other => panic!("expected template 4.8, got {other:?}"),
512 }
513 }
514
515 #[test]
516 fn parses_ensemble_statistical_process_template() {
517 let mut section = product_section_template_eight();
518 section.resize(61, 0);
519 section[..4].copy_from_slice(&(61u32).to_be_bytes());
520 section[7..9].copy_from_slice(&11u16.to_be_bytes());
521 section.copy_within(34..58, 37);
522 section[34] = 1;
523 section[35] = 3;
524 section[36] = 20;
525
526 let product = ProductDefinition::parse(§ion).unwrap();
527 assert_eq!(product.template_number(), 11);
528 assert_eq!(
529 product.end_of_overall_time_interval(),
530 Some(ReferenceTime {
531 year: 2026,
532 month: 3,
533 day: 20,
534 hour: 18,
535 minute: 0,
536 second: 0,
537 })
538 );
539 match product.template {
540 ProductDefinitionTemplate::EnsembleStatisticalProcess(template) => {
541 assert_eq!(template.ensemble.perturbation_number, 3);
542 assert_eq!(template.time_ranges.len(), 1);
543 }
544 other => panic!("expected template 4.11, got {other:?}"),
545 }
546 }
547
548 #[test]
549 fn rejects_invalid_statistical_process_end_time() {
550 let mut section = product_section_template_eight();
551 section[36] = 2;
552 section[37] = 29;
553
554 let err = ProductDefinition::parse(§ion).unwrap_err();
555 assert!(matches!(err, Error::InvalidSection { section: 4, .. }));
556 assert!(err.to_string().contains("invalid reference timestamp"));
557 }
558
559 #[test]
560 fn rejects_unsupported_product_definition_templates() {
561 let mut section = vec![0u8; 34];
562 section[..4].copy_from_slice(&(34u32).to_be_bytes());
563 section[4] = 4;
564 section[7..9].copy_from_slice(&99u16.to_be_bytes());
565 section[9] = 2;
566 section[10] = 3;
567
568 let err = ProductDefinition::parse(§ion).unwrap_err();
569 assert!(matches!(err, Error::UnsupportedProductTemplate(99)));
570 }
571
572 #[test]
573 fn rejects_truncated_template_zero_sections() {
574 let mut section = vec![0u8; 33];
575 section[..4].copy_from_slice(&(33u32).to_be_bytes());
576 section[4] = 4;
577 section[7..9].copy_from_slice(&0u16.to_be_bytes());
578 section[9] = 2;
579 section[10] = 3;
580
581 let err = ProductDefinition::parse(§ion).unwrap_err();
582 assert!(matches!(err, Error::InvalidSection { section: 4, .. }));
583 }
584
585 fn product_section_template_zero() -> Vec<u8> {
586 let mut section = vec![0u8; 34];
587 section[..4].copy_from_slice(&(34u32).to_be_bytes());
588 section[4] = 4;
589 section[7..9].copy_from_slice(&0u16.to_be_bytes());
590 section[9] = 2;
591 section[10] = 3;
592 section[11] = 2;
593 section[17] = 1;
594 section[18..22].copy_from_slice(&6u32.to_be_bytes());
595 section[22] = 103;
596 section[23] = 0;
597 section[24..28].copy_from_slice(&850u32.to_be_bytes());
598 section[28] = 255;
599 section
600 }
601
602 fn product_section_template_eight() -> Vec<u8> {
603 let mut section = product_section_template_zero();
604 section.resize(58, 0);
605 section[..4].copy_from_slice(&(58u32).to_be_bytes());
606 section[7..9].copy_from_slice(&8u16.to_be_bytes());
607 section[34..36].copy_from_slice(&2026u16.to_be_bytes());
608 section[36] = 3;
609 section[37] = 20;
610 section[38] = 18;
611 section[39] = 0;
612 section[40] = 0;
613 section[41] = 1;
614 section[46] = 1;
615 section[47] = 2;
616 section[48] = 1;
617 section[49..53].copy_from_slice(&6u32.to_be_bytes());
618 section[53] = 255;
619 section
620 }
621
622 fn valid_identification_section() -> Vec<u8> {
623 let mut section = vec![0u8; 21];
624 section[..4].copy_from_slice(&(21u32).to_be_bytes());
625 section[4] = 1;
626 section[5..7].copy_from_slice(&7u16.to_be_bytes());
627 section[7..9].copy_from_slice(&14u16.to_be_bytes());
628 section[9] = 35;
629 section[10] = 1;
630 section[11] = 1;
631 section[12..14].copy_from_slice(&2026u16.to_be_bytes());
632 section[14] = 3;
633 section[15] = 20;
634 section[16] = 12;
635 section[17] = 30;
636 section[18] = 45;
637 section[19] = 0;
638 section[20] = 1;
639 section
640 }
641}