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};
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 di = u32::from(u16::from_be_bytes(
480 section_bytes[23..25].try_into().unwrap(),
481 )) * 1_000;
482 let dj = u32::from(u16::from_be_bytes(
483 section_bytes[25..27].try_into().unwrap(),
484 )) * 1_000;
485 let scanning_mode = section_bytes[27];
486
487 Ok(GridDefinition::LatLon(LatLonGrid {
488 ni,
489 nj,
490 lat_first,
491 lon_first,
492 lat_last,
493 lon_last,
494 di,
495 dj,
496 scanning_mode,
497 }))
498}
499
500fn parse_coordinate(bytes: &[u8], absolute_limit: i64, name: &str) -> Result<i32> {
501 let millidegrees = decode_wmo_i24(bytes).ok_or_else(|| Error::InvalidSection {
502 section: 2,
503 reason: format!("truncated {name}"),
504 })?;
505 let microdegrees = i64::from(millidegrees) * 1_000;
506 if microdegrees.abs() > absolute_limit {
507 return Err(Error::InvalidSection {
508 section: 2,
509 reason: format!(
510 "{name} {microdegrees} microdegrees is outside the valid range -{absolute_limit}..={absolute_limit}"
511 ),
512 });
513 }
514
515 i32::try_from(microdegrees).map_err(|_| Error::InvalidSection {
516 section: 2,
517 reason: format!("{name} does not fit the internal microdegree representation"),
518 })
519}
520
521#[cfg(test)]
522mod tests {
523 use super::{BinaryDataSection, GridDescription, ProductDefinition};
524 use crate::data::{DataRepresentation, Grib1SecondOrderLayout};
525 use crate::error::Error;
526 use crate::metadata::ReferenceTime;
527
528 #[test]
529 fn decodes_indicator_ten_forecast_time_as_u16() {
530 let product = ProductDefinition {
531 table_version: 2,
532 center_id: 7,
533 generating_process_id: 255,
534 grid_id: 0,
535 has_grid_definition: true,
536 has_bitmap: false,
537 parameter_number: 11,
538 level_type: 100,
539 level_value: 850,
540 reference_time: ReferenceTime {
541 year: 2026,
542 month: 3,
543 day: 20,
544 hour: 12,
545 minute: 0,
546 second: 0,
547 },
548 forecast_time_unit: 1,
549 p1: 0x01,
550 p2: 0x2c,
551 time_range_indicator: 10,
552 average_count: 0,
553 missing_count: 0,
554 century: 21,
555 subcenter_id: 0,
556 decimal_scale: 0,
557 };
558
559 assert_eq!(product.forecast_time(), Some(300));
560 }
561
562 #[test]
563 fn rejects_invalid_product_definition_reference_time() {
564 let mut section = valid_product_definition_section();
565 section[13] = 2;
566 section[14] = 29;
567 let err = ProductDefinition::parse(§ion).unwrap_err();
568 assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
569 assert!(err.to_string().contains("invalid reference timestamp"));
570
571 let mut section = valid_product_definition_section();
572 section[15] = 24;
573 let err = ProductDefinition::parse(§ion).unwrap_err();
574 assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
575
576 let mut section = valid_product_definition_section();
577 section[16] = 60;
578 let err = ProductDefinition::parse(§ion).unwrap_err();
579 assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
580 }
581
582 #[test]
583 fn rejects_grib1_coordinates_outside_geographic_bounds_without_overflowing() {
584 let mut section = valid_grid_description_section();
585 section[10..13].copy_from_slice(&[0x7f, 0xff, 0xff]);
586
587 let err = GridDescription::parse(§ion).unwrap_err();
588 assert!(matches!(err, Error::InvalidSection { section: 2, .. }));
589 assert!(err.to_string().contains("first latitude"));
590
591 let mut section = valid_grid_description_section();
592 section[13..16].copy_from_slice(&[0xff, 0xff, 0xff]);
593
594 let err = GridDescription::parse(§ion).unwrap_err();
595 assert!(matches!(err, Error::InvalidSection { section: 2, .. }));
596 assert!(err.to_string().contains("first longitude"));
597 }
598
599 #[test]
600 fn parses_row_by_row_second_order_packing() {
601 let section = second_order_section(Grib1SecondOrderLayout::RowByRow);
602 let (_, representation) = BinaryDataSection::parse(§ion, 0, 6).unwrap();
603 let DataRepresentation::Grib1SecondOrderPacking(params) = representation else {
604 panic!("expected GRIB1 second-order packing");
605 };
606
607 assert_eq!(params.layout(), Grib1SecondOrderLayout::RowByRow);
608 assert_eq!(params.group_count(), 2);
609 assert_eq!(params.group_widths(), &[2, 2]);
610 assert!(params.group_lengths().is_empty());
611 assert_eq!(params.first_order_values_offset(), 12);
612 assert_eq!(params.second_order_values_offset(), 14);
613 }
614
615 #[test]
616 fn parses_secondary_bitmap_second_order_layouts() {
617 for layout in [
618 Grib1SecondOrderLayout::ConstantWidth,
619 Grib1SecondOrderLayout::VariableWidth,
620 ] {
621 let section = second_order_section(layout);
622 let (_, representation) = BinaryDataSection::parse(§ion, 0, 6).unwrap();
623 let DataRepresentation::Grib1SecondOrderPacking(params) = representation else {
624 panic!("expected GRIB1 second-order packing");
625 };
626
627 assert_eq!(params.layout(), layout);
628 assert_eq!(params.group_count(), 2);
629 assert_eq!(params.group_lengths(), &[3, 3]);
630 if layout == Grib1SecondOrderLayout::ConstantWidth {
631 assert_eq!(params.group_widths(), &[2]);
632 } else {
633 assert_eq!(params.group_widths(), &[2, 2]);
634 }
635 }
636 }
637
638 #[test]
639 fn rejects_second_order_secondary_bitmap_without_initial_boundary() {
640 let mut section = second_order_section(Grib1SecondOrderLayout::VariableWidth);
641 section[23] = 0b0001_0000;
642 let error = BinaryDataSection::parse(§ion, 0, 6).unwrap_err();
643 assert!(matches!(error, Error::InvalidSection { section: 4, .. }));
644 assert!(error
645 .to_string()
646 .contains("must start with a group boundary"));
647 }
648
649 #[test]
650 fn rejects_nonstandard_second_order_extensions_and_inconsistent_offsets() {
651 let mut section = second_order_section(Grib1SecondOrderLayout::RowByRow);
652 section[20] = 1;
653 let error = BinaryDataSection::parse(§ion, 0, 6).unwrap_err();
654 assert!(error.to_string().contains("reserved octet 21"));
655
656 let mut section = second_order_section(Grib1SecondOrderLayout::RowByRow);
657 section[13] |= 0x04;
658 let error = BinaryDataSection::parse(§ion, 0, 6).unwrap_err();
659 assert!(error.to_string().contains("non-WMO extended ordering"));
660
661 let mut section = second_order_section(Grib1SecondOrderLayout::VariableWidth);
662 section[14..16].copy_from_slice(&28u16.to_be_bytes());
663 let error = BinaryDataSection::parse(§ion, 0, 6).unwrap_err();
664 assert!(error.to_string().contains("N2 points to octet 28"));
665 }
666
667 #[test]
668 fn preserves_simple_integer_field_type() {
669 let mut section = vec![0u8; 12];
670 section[..3].copy_from_slice(&[0, 0, 12]);
671 section[3] = 0x20;
672 section[10] = 8;
673 let (_, representation) = BinaryDataSection::parse(§ion, 0, 1).unwrap();
674 let DataRepresentation::SimplePacking(params) = representation else {
675 panic!("expected simple packing");
676 };
677 assert_eq!(params.original_field_type, 1);
678 }
679
680 fn valid_product_definition_section() -> Vec<u8> {
681 let mut section = vec![0u8; 28];
682 section[..3].copy_from_slice(&[0, 0, 28]);
683 section[3] = 2;
684 section[4] = 7;
685 section[5] = 255;
686 section[6] = 0;
687 section[7] = 0b1000_0000;
688 section[8] = 11;
689 section[9] = 100;
690 section[10..12].copy_from_slice(&850u16.to_be_bytes());
691 section[12] = 26;
692 section[13] = 3;
693 section[14] = 20;
694 section[15] = 12;
695 section[16] = 0;
696 section[17] = 1;
697 section[18] = 0;
698 section[19] = 0;
699 section[20] = 0;
700 section[24] = 21;
701 section[25] = 0;
702 section
703 }
704
705 fn valid_grid_description_section() -> Vec<u8> {
706 let mut section = vec![0u8; 32];
707 section[..3].copy_from_slice(&[0, 0, 32]);
708 section[5] = 0;
709 section[6..8].copy_from_slice(&2u16.to_be_bytes());
710 section[8..10].copy_from_slice(&2u16.to_be_bytes());
711 section[10..13].copy_from_slice(&[0, 0, 0]);
712 section[13..16].copy_from_slice(&[0, 0, 0]);
713 section[17..20].copy_from_slice(&[0, 3, 0xe8]);
714 section[20..23].copy_from_slice(&[0, 3, 0xe8]);
715 section[23..25].copy_from_slice(&1_000u16.to_be_bytes());
716 section[25..27].copy_from_slice(&1_000u16.to_be_bytes());
717 section
718 }
719
720 fn second_order_section(layout: Grib1SecondOrderLayout) -> Vec<u8> {
721 let (length, extended_flags, n1, n2, widths, bitmap_offset) = match layout {
722 Grib1SecondOrderLayout::RowByRow => (27usize, 0x10, 24u16, 26u16, &[2, 2][..], None),
723 Grib1SecondOrderLayout::ConstantWidth => (27, 0x20, 24, 26, &[2][..], Some(22usize)),
724 Grib1SecondOrderLayout::VariableWidth => (28, 0x30, 25, 27, &[2, 2][..], Some(23usize)),
725 };
726 let mut section = vec![0u8; length];
727 section[..3].copy_from_slice(&[
728 ((length >> 16) & 0xff) as u8,
729 ((length >> 8) & 0xff) as u8,
730 (length & 0xff) as u8,
731 ]);
732 section[3] = 0x44;
733 section[10] = 5;
734 section[11..13].copy_from_slice(&n1.to_be_bytes());
735 section[13] = extended_flags;
736 section[14..16].copy_from_slice(&n2.to_be_bytes());
737 section[16..18].copy_from_slice(&2u16.to_be_bytes());
738 section[18..20].copy_from_slice(&6u16.to_be_bytes());
739 section[21..21 + widths.len()].copy_from_slice(widths);
740 if let Some(offset) = bitmap_offset {
741 section[offset] = 0b1001_0000;
742 }
743 let first_order_index = usize::from(n1) - 1;
744 section[first_order_index..first_order_index + 2].copy_from_slice(&[0x55, 0x00]);
745 let second_order_index = usize::from(n2) - 1;
746 section[second_order_index..second_order_index + 2].copy_from_slice(&[0x18, 0xb0]);
747 section
748 }
749}