1use crate::binary::{decode_ibm_f32, decode_wmo_i16, decode_wmo_i24};
4use crate::bit::read_bit;
5use crate::data::{
6 DataRepresentation, Grib1SecondOrderLayout, Grib1SecondOrderPackingParams, ScaledPackingParams,
7};
8use crate::error::{Error, Result};
9use crate::grid::{GridDefinition, LatLonGrid, MISSING_INCREMENT};
10use crate::metadata::{Parameter, ReferenceTime};
11use crate::parameter;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct ProductDefinition {
16 pub table_version: u8,
17 pub center_id: u8,
18 pub generating_process_id: u8,
19 pub grid_id: u8,
20 pub has_grid_definition: bool,
21 pub has_bitmap: bool,
22 pub parameter_number: u8,
23 pub level_type: u8,
24 pub level_value: u16,
25 pub reference_time: ReferenceTime,
26 pub forecast_time_unit: u8,
27 pub p1: u8,
28 pub p2: u8,
29 pub time_range_indicator: u8,
30 pub average_count: u16,
31 pub missing_count: u8,
32 pub century: u8,
33 pub subcenter_id: u8,
34 pub decimal_scale: i16,
35}
36
37impl ProductDefinition {
38 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
39 if section_bytes.len() < 28 {
40 return Err(Error::InvalidSection {
41 section: 1,
42 reason: format!("expected at least 28 bytes, got {}", section_bytes.len()),
43 });
44 }
45
46 Ok(Self {
47 table_version: section_bytes[3],
48 center_id: section_bytes[4],
49 generating_process_id: section_bytes[5],
50 grid_id: section_bytes[6],
51 has_grid_definition: section_bytes[7] & 0b1000_0000 != 0,
52 has_bitmap: section_bytes[7] & 0b0100_0000 != 0,
53 parameter_number: section_bytes[8],
54 level_type: section_bytes[9],
55 level_value: u16::from_be_bytes(section_bytes[10..12].try_into().unwrap()),
56 reference_time: parse_reference_time(section_bytes)?,
57 forecast_time_unit: section_bytes[17],
58 p1: section_bytes[18],
59 p2: section_bytes[19],
60 time_range_indicator: section_bytes[20],
61 average_count: u16::from_be_bytes(section_bytes[21..23].try_into().unwrap()),
62 missing_count: section_bytes[23],
63 century: section_bytes[24],
64 subcenter_id: section_bytes[25],
65 decimal_scale: decode_wmo_i16(§ion_bytes[26..28]).unwrap(),
66 })
67 }
68
69 pub fn parameter(&self) -> Parameter {
70 let short_name = parameter::grib1_parameter_name(self.table_version, self.parameter_number);
71 let description =
72 parameter::grib1_parameter_description(self.table_version, self.parameter_number);
73 Parameter::new_grib1(
74 self.table_version,
75 self.parameter_number,
76 short_name,
77 description,
78 )
79 }
80
81 pub fn forecast_time(&self) -> Option<u32> {
82 match self.time_range_indicator {
83 0 | 1 => Some(u32::from(self.p1)),
84 10 => Some(u32::from(u16::from_be_bytes([self.p1, self.p2]))),
85 _ => None,
86 }
87 }
88
89 pub fn valid_time_offset(&self) -> Option<u32> {
95 match self.time_range_indicator {
96 2..=5 => Some(u32::from(self.p2)),
97 _ => self.forecast_time(),
98 }
99 }
100}
101
102#[derive(Debug, Clone, PartialEq)]
103pub struct GridDescription {
104 pub nv: u8,
105 pub pv_or_pl: u8,
106 pub data_representation_type: u8,
107 pub grid: GridDefinition,
108}
109
110impl GridDescription {
111 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
112 if section_bytes.len() < 32 {
113 return Err(Error::InvalidSection {
114 section: 2,
115 reason: format!("expected at least 32 bytes, got {}", section_bytes.len()),
116 });
117 }
118
119 let data_representation_type = section_bytes[5];
120 let grid = match data_representation_type {
121 0 => parse_latlon_grid(section_bytes)?,
122 other => GridDefinition::Unsupported(u16::from(other)),
123 };
124
125 Ok(Self {
126 nv: section_bytes[3],
127 pv_or_pl: section_bytes[4],
128 data_representation_type,
129 grid,
130 })
131 }
132}
133
134#[derive(Debug, Clone, PartialEq)]
135pub struct BinaryDataSection {
136 pub flags: u8,
137 pub unused_bits: u8,
138 pub binary_scale: i16,
139 pub reference_value: f32,
140 pub bits_per_value: u8,
141}
142
143impl BinaryDataSection {
144 pub fn parse(
145 section_bytes: &[u8],
146 decimal_scale: i16,
147 encoded_values: usize,
148 ) -> Result<(Self, DataRepresentation)> {
149 if section_bytes.len() < 11 {
150 return Err(Error::InvalidSection {
151 section: 4,
152 reason: format!("expected at least 11 bytes, got {}", section_bytes.len()),
153 });
154 }
155
156 let data_flag = section_bytes[3];
157 let flags = data_flag >> 4;
158 if flags & 0b1000 != 0 {
159 return Err(Error::UnsupportedDataTemplate(1004));
160 }
161
162 let binary_scale = decode_wmo_i16(§ion_bytes[4..6]).unwrap();
163 let reference_value = decode_ibm_f32(section_bytes[6..10].try_into().unwrap());
164 let bits_per_value = section_bytes[10];
165 if bits_per_value > 64 {
166 return Err(Error::UnsupportedPackingWidth(bits_per_value));
167 }
168 let packing = ScaledPackingParams {
169 encoded_values,
170 reference_value,
171 binary_scale,
172 decimal_scale,
173 bits_per_value,
174 original_field_type: u8::from(flags & 0b0010 != 0),
175 };
176 let representation = if flags & 0b0100 != 0 {
177 parse_second_order_packing(section_bytes, packing, data_flag & 0x0f)?
178 } else {
179 if flags & 0b0001 != 0 {
180 return Err(Error::UnsupportedDataTemplate(1007));
181 }
182 DataRepresentation::SimplePacking(packing)
183 };
184
185 Ok((
186 Self {
187 flags,
188 unused_bits: data_flag & 0x0f,
189 binary_scale,
190 reference_value,
191 bits_per_value,
192 },
193 representation,
194 ))
195 }
196}
197
198fn parse_second_order_packing(
199 section: &[u8],
200 packing: ScaledPackingParams,
201 unused_bits: u8,
202) -> Result<DataRepresentation> {
203 if section.len() < 22 {
204 return Err(invalid_second_order(format!(
205 "requires at least 22 bytes, got {}",
206 section.len()
207 )));
208 }
209
210 let n1 = usize::from(u16::from_be_bytes(section[11..13].try_into().unwrap()));
211 let extended_flags = section[13];
212 let n2 = usize::from(u16::from_be_bytes(section[14..16].try_into().unwrap()));
213 let coded_group_count = usize::from(u16::from_be_bytes(section[16..18].try_into().unwrap()));
214 let encoded_values = usize::from(u16::from_be_bytes(section[18..20].try_into().unwrap()));
215 if section[20] != 0 {
216 return Err(invalid_second_order(format!(
217 "reserved octet 21 must be zero, got {}",
218 section[20]
219 )));
220 }
221 let group_count = coded_group_count;
222
223 if encoded_values != packing.encoded_values {
224 return Err(Error::DataLengthMismatch {
225 expected: packing.encoded_values,
226 actual: encoded_values,
227 });
228 }
229 if extended_flags & 0x80 != 0 {
230 return Err(invalid_second_order("reserved extended flag bit 1 is set"));
231 }
232 if extended_flags & 0x40 != 0 {
233 return Err(invalid_second_order(
234 "matrix-valued grid points are not supported",
235 ));
236 }
237 if extended_flags & 0x0f != 0 {
238 return Err(invalid_second_order(format!(
239 "non-WMO extended ordering or spatial differencing flags are set: 0x{:02x}",
240 extended_flags & 0x0f
241 )));
242 }
243
244 let has_secondary_bitmap = extended_flags & 0x20 != 0;
245 let has_variable_widths = extended_flags & 0x10 != 0;
246 let layout = match (has_secondary_bitmap, has_variable_widths) {
247 (false, true) => Grib1SecondOrderLayout::RowByRow,
248 (true, false) => Grib1SecondOrderLayout::ConstantWidth,
249 (true, true) => Grib1SecondOrderLayout::VariableWidth,
250 (false, false) => {
251 return Err(invalid_second_order(
252 "constant-width groups require a secondary bitmap",
253 ));
254 }
255 };
256
257 if encoded_values > 0 && group_count == 0 {
258 return Err(invalid_second_order(
259 "non-empty fields require at least one group",
260 ));
261 }
262
263 let width_count = match layout {
264 Grib1SecondOrderLayout::ConstantWidth => 1,
265 Grib1SecondOrderLayout::RowByRow | Grib1SecondOrderLayout::VariableWidth => group_count,
266 };
267 let descriptors_end = 21_usize
268 .checked_add(width_count)
269 .ok_or(Error::ArithmeticOverflow {
270 operation: "computing GRIB1 second-order width descriptor boundary",
271 })?;
272 let secondary_bitmap_bytes = if has_secondary_bitmap {
273 encoded_values.div_ceil(8)
274 } else {
275 0
276 };
277 let expected_first_order_index =
278 descriptors_end
279 .checked_add(secondary_bitmap_bytes)
280 .ok_or(Error::ArithmeticOverflow {
281 operation: "computing GRIB1 first-order value offset",
282 })?;
283 let first_order_index = n1
284 .checked_sub(1)
285 .ok_or_else(|| invalid_second_order("N1 must be a one-based octet position"))?;
286 if first_order_index != expected_first_order_index {
287 return Err(invalid_second_order(format!(
288 "N1 points to octet {n1}, expected {}",
289 expected_first_order_index + 1
290 )));
291 }
292 if expected_first_order_index > section.len() {
293 return Err(invalid_second_order(format!(
294 "width descriptors and secondary bitmap require {expected_first_order_index} bytes, got {}",
295 section.len()
296 )));
297 }
298
299 let width_bytes = §ion[21..descriptors_end];
300 if width_bytes.iter().any(|width| *width > 64) {
301 let width = width_bytes
302 .iter()
303 .copied()
304 .find(|width| *width > 64)
305 .expect("out-of-range width exists");
306 return Err(Error::UnsupportedPackingWidth(width));
307 }
308 let mut group_widths = Vec::new();
309 group_widths
310 .try_reserve_exact(width_count)
311 .map_err(|error| Error::allocation("GRIB1 group widths", width_count, error))?;
312 group_widths.extend_from_slice(width_bytes);
313
314 let group_lengths = if has_secondary_bitmap {
315 parse_secondary_group_lengths(
316 §ion[descriptors_end..expected_first_order_index],
317 encoded_values,
318 group_count,
319 )?
320 } else {
321 Vec::new()
322 };
323
324 let first_order_bits = group_count
325 .checked_mul(usize::from(packing.bits_per_value))
326 .ok_or(Error::BitOffsetOverflow)?;
327 let expected_second_order_index = expected_first_order_index
328 .checked_add(first_order_bits.div_ceil(8))
329 .ok_or(Error::ArithmeticOverflow {
330 operation: "computing GRIB1 second-order value offset",
331 })?;
332 let second_order_index = n2
333 .checked_sub(1)
334 .ok_or_else(|| invalid_second_order("N2 must be a one-based octet position"))?;
335 if second_order_index != expected_second_order_index {
336 return Err(invalid_second_order(format!(
337 "N2 points to octet {n2}, expected {}",
338 expected_second_order_index + 1
339 )));
340 }
341 if second_order_index > section.len() {
342 return Err(invalid_second_order(format!(
343 "first-order values require {second_order_index} bytes, got {}",
344 section.len()
345 )));
346 }
347
348 let first_order_values_offset = first_order_index - 11;
349 let second_order_values_offset = second_order_index - 11;
350 let payload_bits = section[11..]
351 .len()
352 .checked_mul(8)
353 .ok_or(Error::BitOffsetOverflow)?;
354 let usable_bits = payload_bits
355 .checked_sub(usize::from(unused_bits))
356 .ok_or_else(|| invalid_second_order("unused-bit count exceeds the BDS payload"))?;
357 let second_order_start = second_order_values_offset
358 .checked_mul(8)
359 .ok_or(Error::BitOffsetOverflow)?;
360
361 if layout != Grib1SecondOrderLayout::RowByRow {
362 let second_order_bits =
363 group_lengths
364 .iter()
365 .enumerate()
366 .try_fold(0usize, |total, (group_index, length)| {
367 let width = match layout {
368 Grib1SecondOrderLayout::ConstantWidth => group_widths[0],
369 Grib1SecondOrderLayout::VariableWidth => group_widths[group_index],
370 Grib1SecondOrderLayout::RowByRow => unreachable!(),
371 };
372 length
373 .checked_mul(usize::from(width))
374 .and_then(|bits| total.checked_add(bits))
375 .ok_or(Error::BitOffsetOverflow)
376 })?;
377 let expected_end = second_order_start
378 .checked_add(second_order_bits)
379 .ok_or(Error::BitOffsetOverflow)?;
380 if expected_end != usable_bits {
381 return Err(invalid_second_order(format!(
382 "packed values end at bit {expected_end}, but the usable BDS payload ends at bit {usable_bits}"
383 )));
384 }
385 } else if second_order_start > usable_bits {
386 return Err(invalid_second_order(
387 "N2 points beyond the usable BDS payload",
388 ));
389 }
390
391 Ok(DataRepresentation::Grib1SecondOrderPacking(
392 Grib1SecondOrderPackingParams {
393 packing,
394 layout,
395 group_count,
396 group_widths,
397 group_lengths,
398 first_order_values_offset,
399 second_order_values_offset,
400 unused_bits,
401 },
402 ))
403}
404
405fn parse_secondary_group_lengths(
406 bitmap: &[u8],
407 encoded_values: usize,
408 group_count: usize,
409) -> Result<Vec<usize>> {
410 if encoded_values == 0 {
411 if group_count == 0 {
412 return Ok(Vec::new());
413 }
414 return Err(invalid_second_order(format!(
415 "empty secondary bitmap cannot describe {group_count} groups"
416 )));
417 }
418 if !read_bit(bitmap, 0)? {
419 return Err(invalid_second_order(
420 "secondary bitmap must start with a group boundary",
421 ));
422 }
423
424 let mut lengths = Vec::new();
425 lengths
426 .try_reserve_exact(group_count)
427 .map_err(|error| Error::allocation("GRIB1 group lengths", group_count, error))?;
428 let mut group_start = 0usize;
429 for bit_index in 1..encoded_values {
430 if read_bit(bitmap, bit_index)? {
431 lengths.push(bit_index - group_start);
432 group_start = bit_index;
433 }
434 }
435 lengths.push(encoded_values - group_start);
436 if lengths.len() != group_count {
437 return Err(invalid_second_order(format!(
438 "secondary bitmap defines {} groups, but P1 declares {group_count}",
439 lengths.len()
440 )));
441 }
442 Ok(lengths)
443}
444
445fn invalid_second_order(reason: impl Into<String>) -> Error {
446 Error::InvalidSection {
447 section: 4,
448 reason: format!("invalid GRIB1 second-order packing: {}", reason.into()),
449 }
450}
451
452fn parse_reference_time(section_bytes: &[u8]) -> Result<ReferenceTime> {
453 let century = section_bytes[24];
454 let year_of_century = u16::from(section_bytes[12]);
455 let year = match century {
456 0 => year_of_century,
457 c => (u16::from(c) - 1) * 100 + year_of_century,
458 };
459
460 let reference_time = ReferenceTime {
461 year,
462 month: section_bytes[13],
463 day: section_bytes[14],
464 hour: section_bytes[15],
465 minute: section_bytes[16],
466 second: 0,
467 };
468 reference_time.validate_in_section(1)?;
469 Ok(reference_time)
470}
471
472fn parse_latlon_grid(section_bytes: &[u8]) -> Result<GridDefinition> {
473 let ni = u32::from(u16::from_be_bytes(section_bytes[6..8].try_into().unwrap()));
474 let nj = u32::from(u16::from_be_bytes(section_bytes[8..10].try_into().unwrap()));
475 let lat_first = parse_coordinate(§ion_bytes[10..13], 90_000_000, "first latitude")?;
476 let lon_first = parse_coordinate(§ion_bytes[13..16], 360_000_000, "first longitude")?;
477 let lat_last = parse_coordinate(§ion_bytes[17..20], 90_000_000, "last latitude")?;
478 let lon_last = parse_coordinate(§ion_bytes[20..23], 360_000_000, "last longitude")?;
479 let increments_given = section_bytes[16] & 0x80 != 0;
480 let di = parse_increment(§ion_bytes[23..25], increments_given);
481 let dj = parse_increment(§ion_bytes[25..27], increments_given);
482 let scanning_mode = section_bytes[27];
483
484 Ok(GridDefinition::LatLon(LatLonGrid {
485 ni,
486 nj,
487 lat_first,
488 lon_first,
489 lat_last,
490 lon_last,
491 di,
492 dj,
493 scanning_mode,
494 }))
495}
496
497fn parse_increment(bytes: &[u8], given: bool) -> u32 {
501 let raw = u16::from_be_bytes(bytes.try_into().unwrap());
502 if !given || raw == u16::MAX {
503 MISSING_INCREMENT
504 } else {
505 u32::from(raw) * 1_000
506 }
507}
508
509fn parse_coordinate(bytes: &[u8], absolute_limit: i64, name: &str) -> Result<i32> {
510 let millidegrees = decode_wmo_i24(bytes).ok_or_else(|| Error::InvalidSection {
511 section: 2,
512 reason: format!("truncated {name}"),
513 })?;
514 let microdegrees = i64::from(millidegrees) * 1_000;
515 if microdegrees.abs() > absolute_limit {
516 return Err(Error::InvalidSection {
517 section: 2,
518 reason: format!(
519 "{name} {microdegrees} microdegrees is outside the valid range -{absolute_limit}..={absolute_limit}"
520 ),
521 });
522 }
523
524 i32::try_from(microdegrees).map_err(|_| Error::InvalidSection {
525 section: 2,
526 reason: format!("{name} does not fit the internal microdegree representation"),
527 })
528}
529
530#[cfg(test)]
531mod tests {
532 use super::{BinaryDataSection, GridDescription, ProductDefinition};
533 use crate::data::{DataRepresentation, Grib1SecondOrderLayout};
534 use crate::error::Error;
535 use crate::grid::{GridDefinition, MISSING_INCREMENT};
536 use crate::metadata::ReferenceTime;
537
538 #[test]
539 fn decodes_indicator_ten_forecast_time_as_u16() {
540 let product = ProductDefinition {
541 table_version: 2,
542 center_id: 7,
543 generating_process_id: 255,
544 grid_id: 0,
545 has_grid_definition: true,
546 has_bitmap: false,
547 parameter_number: 11,
548 level_type: 100,
549 level_value: 850,
550 reference_time: ReferenceTime {
551 year: 2026,
552 month: 3,
553 day: 20,
554 hour: 12,
555 minute: 0,
556 second: 0,
557 },
558 forecast_time_unit: 1,
559 p1: 0x01,
560 p2: 0x2c,
561 time_range_indicator: 10,
562 average_count: 0,
563 missing_count: 0,
564 century: 21,
565 subcenter_id: 0,
566 decimal_scale: 0,
567 };
568
569 assert_eq!(product.forecast_time(), Some(300));
570 }
571
572 #[test]
573 fn rejects_invalid_product_definition_reference_time() {
574 let mut section = valid_product_definition_section();
575 section[13] = 2;
576 section[14] = 29;
577 let err = ProductDefinition::parse(§ion).unwrap_err();
578 assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
579 assert!(err.to_string().contains("invalid reference timestamp"));
580
581 let mut section = valid_product_definition_section();
582 section[15] = 24;
583 let err = ProductDefinition::parse(§ion).unwrap_err();
584 assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
585
586 let mut section = valid_product_definition_section();
587 section[16] = 60;
588 let err = ProductDefinition::parse(§ion).unwrap_err();
589 assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
590 }
591
592 #[test]
593 fn rejects_grib1_coordinates_outside_geographic_bounds_without_overflowing() {
594 let mut section = valid_grid_description_section();
595 section[10..13].copy_from_slice(&[0x7f, 0xff, 0xff]);
596
597 let err = GridDescription::parse(§ion).unwrap_err();
598 assert!(matches!(err, Error::InvalidSection { section: 2, .. }));
599 assert!(err.to_string().contains("first latitude"));
600
601 let mut section = valid_grid_description_section();
602 section[13..16].copy_from_slice(&[0xff, 0xff, 0xff]);
603
604 let err = GridDescription::parse(§ion).unwrap_err();
605 assert!(matches!(err, Error::InvalidSection { section: 2, .. }));
606 assert!(err.to_string().contains("first longitude"));
607 }
608
609 #[test]
610 fn maps_grib1_increments_not_given_to_missing_and_infers_axes() {
611 let grid_of = |section: &[u8]| match GridDescription::parse(section).unwrap().grid {
612 GridDefinition::LatLon(grid) => grid,
613 other => panic!("expected a latitude/longitude grid, got {other:?}"),
614 };
615
616 let mut section = valid_grid_description_section();
617 section[16] = 0x80;
618 let given = grid_of(§ion);
619 assert_eq!((given.di, given.dj), (1_000_000, 1_000_000));
620
621 section[23..25].copy_from_slice(&u16::MAX.to_be_bytes());
622 assert_eq!(grid_of(§ion).di, MISSING_INCREMENT);
623
624 let not_given = grid_of(&valid_grid_description_section());
625 assert_eq!(
626 (not_given.di, not_given.dj),
627 (MISSING_INCREMENT, MISSING_INCREMENT)
628 );
629 assert_eq!(not_given.longitudes().unwrap(), vec![0.0, 1.0]);
630 assert_eq!(not_given.latitudes().unwrap(), vec![0.0, -1.0]);
631 }
632
633 #[test]
634 fn parses_row_by_row_second_order_packing() {
635 let section = second_order_section(Grib1SecondOrderLayout::RowByRow);
636 let (_, representation) = BinaryDataSection::parse(§ion, 0, 6).unwrap();
637 let DataRepresentation::Grib1SecondOrderPacking(params) = representation else {
638 panic!("expected GRIB1 second-order packing");
639 };
640
641 assert_eq!(params.layout(), Grib1SecondOrderLayout::RowByRow);
642 assert_eq!(params.group_count(), 2);
643 assert_eq!(params.group_widths(), &[2, 2]);
644 assert!(params.group_lengths().is_empty());
645 assert_eq!(params.first_order_values_offset(), 12);
646 assert_eq!(params.second_order_values_offset(), 14);
647 }
648
649 #[test]
650 fn parses_secondary_bitmap_second_order_layouts() {
651 for layout in [
652 Grib1SecondOrderLayout::ConstantWidth,
653 Grib1SecondOrderLayout::VariableWidth,
654 ] {
655 let section = second_order_section(layout);
656 let (_, representation) = BinaryDataSection::parse(§ion, 0, 6).unwrap();
657 let DataRepresentation::Grib1SecondOrderPacking(params) = representation else {
658 panic!("expected GRIB1 second-order packing");
659 };
660
661 assert_eq!(params.layout(), layout);
662 assert_eq!(params.group_count(), 2);
663 assert_eq!(params.group_lengths(), &[3, 3]);
664 if layout == Grib1SecondOrderLayout::ConstantWidth {
665 assert_eq!(params.group_widths(), &[2]);
666 } else {
667 assert_eq!(params.group_widths(), &[2, 2]);
668 }
669 }
670 }
671
672 #[test]
673 fn rejects_second_order_secondary_bitmap_without_initial_boundary() {
674 let mut section = second_order_section(Grib1SecondOrderLayout::VariableWidth);
675 section[23] = 0b0001_0000;
676 let error = BinaryDataSection::parse(§ion, 0, 6).unwrap_err();
677 assert!(matches!(error, Error::InvalidSection { section: 4, .. }));
678 assert!(error
679 .to_string()
680 .contains("must start with a group boundary"));
681 }
682
683 #[test]
684 fn rejects_nonstandard_second_order_extensions_and_inconsistent_offsets() {
685 let mut section = second_order_section(Grib1SecondOrderLayout::RowByRow);
686 section[20] = 1;
687 let error = BinaryDataSection::parse(§ion, 0, 6).unwrap_err();
688 assert!(error.to_string().contains("reserved octet 21"));
689
690 let mut section = second_order_section(Grib1SecondOrderLayout::RowByRow);
691 section[13] |= 0x04;
692 let error = BinaryDataSection::parse(§ion, 0, 6).unwrap_err();
693 assert!(error.to_string().contains("non-WMO extended ordering"));
694
695 let mut section = second_order_section(Grib1SecondOrderLayout::VariableWidth);
696 section[14..16].copy_from_slice(&28u16.to_be_bytes());
697 let error = BinaryDataSection::parse(§ion, 0, 6).unwrap_err();
698 assert!(error.to_string().contains("N2 points to octet 28"));
699 }
700
701 #[test]
702 fn preserves_simple_integer_field_type() {
703 let mut section = vec![0u8; 12];
704 section[..3].copy_from_slice(&[0, 0, 12]);
705 section[3] = 0x20;
706 section[10] = 8;
707 let (_, representation) = BinaryDataSection::parse(§ion, 0, 1).unwrap();
708 let DataRepresentation::SimplePacking(params) = representation else {
709 panic!("expected simple packing");
710 };
711 assert_eq!(params.original_field_type, 1);
712 }
713
714 fn valid_product_definition_section() -> Vec<u8> {
715 let mut section = vec![0u8; 28];
716 section[..3].copy_from_slice(&[0, 0, 28]);
717 section[3] = 2;
718 section[4] = 7;
719 section[5] = 255;
720 section[6] = 0;
721 section[7] = 0b1000_0000;
722 section[8] = 11;
723 section[9] = 100;
724 section[10..12].copy_from_slice(&850u16.to_be_bytes());
725 section[12] = 26;
726 section[13] = 3;
727 section[14] = 20;
728 section[15] = 12;
729 section[16] = 0;
730 section[17] = 1;
731 section[18] = 0;
732 section[19] = 0;
733 section[20] = 0;
734 section[24] = 21;
735 section[25] = 0;
736 section
737 }
738
739 fn valid_grid_description_section() -> Vec<u8> {
740 let mut section = vec![0u8; 32];
741 section[..3].copy_from_slice(&[0, 0, 32]);
742 section[5] = 0;
743 section[6..8].copy_from_slice(&2u16.to_be_bytes());
744 section[8..10].copy_from_slice(&2u16.to_be_bytes());
745 section[10..13].copy_from_slice(&[0, 0, 0]);
746 section[13..16].copy_from_slice(&[0, 0, 0]);
747 section[17..20].copy_from_slice(&[0, 3, 0xe8]);
748 section[20..23].copy_from_slice(&[0, 3, 0xe8]);
749 section[23..25].copy_from_slice(&1_000u16.to_be_bytes());
750 section[25..27].copy_from_slice(&1_000u16.to_be_bytes());
751 section
752 }
753
754 fn second_order_section(layout: Grib1SecondOrderLayout) -> Vec<u8> {
755 let (length, extended_flags, n1, n2, widths, bitmap_offset) = match layout {
756 Grib1SecondOrderLayout::RowByRow => (27usize, 0x10, 24u16, 26u16, &[2, 2][..], None),
757 Grib1SecondOrderLayout::ConstantWidth => (27, 0x20, 24, 26, &[2][..], Some(22usize)),
758 Grib1SecondOrderLayout::VariableWidth => (28, 0x30, 25, 27, &[2, 2][..], Some(23usize)),
759 };
760 let mut section = vec![0u8; length];
761 section[..3].copy_from_slice(&[
762 ((length >> 16) & 0xff) as u8,
763 ((length >> 8) & 0xff) as u8,
764 (length & 0xff) as u8,
765 ]);
766 section[3] = 0x44;
767 section[10] = 5;
768 section[11..13].copy_from_slice(&n1.to_be_bytes());
769 section[13] = extended_flags;
770 section[14..16].copy_from_slice(&n2.to_be_bytes());
771 section[16..18].copy_from_slice(&2u16.to_be_bytes());
772 section[18..20].copy_from_slice(&6u16.to_be_bytes());
773 section[21..21 + widths.len()].copy_from_slice(widths);
774 if let Some(offset) = bitmap_offset {
775 section[offset] = 0b1001_0000;
776 }
777 let first_order_index = usize::from(n1) - 1;
778 section[first_order_index..first_order_index + 2].copy_from_slice(&[0x55, 0x00]);
779 let second_order_index = usize::from(n2) - 1;
780 section[second_order_index..second_order_index + 2].copy_from_slice(&[0x18, 0xb0]);
781 section
782 }
783}