1use crate::data::{decode_payload, DataRepresentation, SimplePackingParams};
4use crate::error::{Error, Result};
5use crate::grid::{GridDefinition, LatLonGrid};
6use crate::metadata::{Parameter, ReferenceTime};
7use crate::parameter;
8use crate::sections::SectionRef;
9use crate::util::{grib_i16, grib_i24};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ProductDefinition {
14 pub table_version: u8,
15 pub center_id: u8,
16 pub generating_process_id: u8,
17 pub grid_id: u8,
18 pub has_grid_definition: bool,
19 pub has_bitmap: bool,
20 pub parameter_number: u8,
21 pub level_type: u8,
22 pub level_value: u16,
23 pub reference_time: ReferenceTime,
24 pub forecast_time_unit: u8,
25 pub p1: u8,
26 pub p2: u8,
27 pub time_range_indicator: u8,
28 pub average_count: u16,
29 pub missing_count: u8,
30 pub century: u8,
31 pub subcenter_id: u8,
32 pub decimal_scale: i16,
33}
34
35impl ProductDefinition {
36 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
37 if section_bytes.len() < 28 {
38 return Err(Error::InvalidSection {
39 section: 1,
40 reason: format!("expected at least 28 bytes, got {}", section_bytes.len()),
41 });
42 }
43
44 Ok(Self {
45 table_version: section_bytes[3],
46 center_id: section_bytes[4],
47 generating_process_id: section_bytes[5],
48 grid_id: section_bytes[6],
49 has_grid_definition: section_bytes[7] & 0b1000_0000 != 0,
50 has_bitmap: section_bytes[7] & 0b0100_0000 != 0,
51 parameter_number: section_bytes[8],
52 level_type: section_bytes[9],
53 level_value: u16::from_be_bytes(section_bytes[10..12].try_into().unwrap()),
54 reference_time: parse_reference_time(section_bytes)?,
55 forecast_time_unit: section_bytes[17],
56 p1: section_bytes[18],
57 p2: section_bytes[19],
58 time_range_indicator: section_bytes[20],
59 average_count: u16::from_be_bytes(section_bytes[21..23].try_into().unwrap()),
60 missing_count: section_bytes[23],
61 century: section_bytes[24],
62 subcenter_id: section_bytes[25],
63 decimal_scale: grib_i16(§ion_bytes[26..28]).unwrap(),
64 })
65 }
66
67 pub fn parameter(&self) -> Parameter {
68 let short_name = parameter::grib1_parameter_name(self.table_version, self.parameter_number);
69 let description =
70 parameter::grib1_parameter_description(self.table_version, self.parameter_number);
71 Parameter::new_grib1(
72 self.table_version,
73 self.parameter_number,
74 short_name,
75 description,
76 )
77 }
78
79 pub fn forecast_time(&self) -> Option<u32> {
80 match self.time_range_indicator {
81 0 | 1 => Some(self.p1 as u32),
82 10 => Some(u16::from_be_bytes([self.p1, self.p2]) as u32),
83 _ => None,
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq)]
89pub struct GridDescription {
90 pub nv: u8,
91 pub pv_or_pl: u8,
92 pub data_representation_type: u8,
93 pub grid: GridDefinition,
94}
95
96impl GridDescription {
97 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
98 if section_bytes.len() < 32 {
99 return Err(Error::InvalidSection {
100 section: 2,
101 reason: format!("expected at least 32 bytes, got {}", section_bytes.len()),
102 });
103 }
104
105 let data_representation_type = section_bytes[5];
106 let grid = match data_representation_type {
107 0 => parse_latlon_grid(section_bytes)?,
108 other => GridDefinition::Unsupported(other as u16),
109 };
110
111 Ok(Self {
112 nv: section_bytes[3],
113 pv_or_pl: section_bytes[4],
114 data_representation_type,
115 grid,
116 })
117 }
118}
119
120#[derive(Debug, Clone, PartialEq)]
121pub struct BinaryDataSection {
122 pub flags: u8,
123 pub unused_bits: u8,
124 pub binary_scale: i16,
125 pub reference_value: f32,
126 pub bits_per_value: u8,
127}
128
129impl BinaryDataSection {
130 pub fn parse(
131 section_bytes: &[u8],
132 decimal_scale: i16,
133 encoded_values: usize,
134 ) -> Result<(Self, DataRepresentation)> {
135 if section_bytes.len() < 11 {
136 return Err(Error::InvalidSection {
137 section: 4,
138 reason: format!("expected at least 11 bytes, got {}", section_bytes.len()),
139 });
140 }
141
142 let data_flag = section_bytes[3];
143 let flags = data_flag >> 4;
144 if flags & 0b1000 != 0 {
145 return Err(Error::UnsupportedDataTemplate(1004));
146 }
147 if flags & 0b0100 != 0 {
148 return Err(Error::UnsupportedDataTemplate(1005));
149 }
150 if flags & 0b0010 != 0 {
151 return Err(Error::UnsupportedDataTemplate(1006));
152 }
153 if flags & 0b0001 != 0 {
154 return Err(Error::UnsupportedDataTemplate(1007));
155 }
156
157 let binary_scale = grib_i16(§ion_bytes[4..6]).unwrap();
158 let reference_value = ibm_f32(section_bytes[6..10].try_into().unwrap());
159 let bits_per_value = section_bytes[10];
160 let simple = SimplePackingParams {
161 encoded_values,
162 reference_value,
163 binary_scale,
164 decimal_scale,
165 bits_per_value,
166 original_field_type: if flags & 0b0010_0000 != 0 { 1 } else { 0 },
167 };
168
169 Ok((
170 Self {
171 flags,
172 unused_bits: data_flag & 0x0f,
173 binary_scale,
174 reference_value,
175 bits_per_value,
176 },
177 DataRepresentation::SimplePacking(simple),
178 ))
179 }
180}
181
182pub fn bitmap_payload(section_bytes: &[u8]) -> Result<Option<&[u8]>> {
183 if section_bytes.len() < 6 {
184 return Err(Error::InvalidSection {
185 section: 3,
186 reason: format!("expected at least 6 bytes, got {}", section_bytes.len()),
187 });
188 }
189 let indicator = u16::from_be_bytes(section_bytes[4..6].try_into().unwrap());
190 if indicator == 0 {
191 Ok(Some(§ion_bytes[6..]))
192 } else {
193 Err(Error::UnsupportedBitmapIndicator(
194 if indicator <= u16::from(u8::MAX) {
195 indicator as u8
196 } else {
197 u8::MAX
198 },
199 ))
200 }
201}
202
203pub fn decode_simple_field(
204 data_section: &[u8],
205 representation: &DataRepresentation,
206 bitmap_section: Option<&[u8]>,
207 num_grid_points: usize,
208) -> Result<Vec<f64>> {
209 decode_payload(
210 data_section,
211 representation,
212 bitmap_section,
213 num_grid_points,
214 )
215}
216
217pub fn parse_message_sections(message_bytes: &[u8]) -> Result<Grib1Sections> {
218 if message_bytes.len() < 8 + 28 + 11 + 4 {
219 return Err(Error::InvalidMessage(format!(
220 "GRIB1 message too short: {} bytes",
221 message_bytes.len()
222 )));
223 }
224
225 let payload_limit = message_bytes.len() - 4;
226 let pds = parse_section(message_bytes, 8, 1, payload_limit)?;
227 let pds_bytes = &message_bytes[pds.offset..pds.offset + pds.length];
228 let product = ProductDefinition::parse(pds_bytes)?;
229
230 let mut cursor = pds.offset + pds.length;
231 let grid = if product.has_grid_definition {
232 let section_ref = parse_section(message_bytes, cursor, 2, payload_limit)?;
233 cursor += section_ref.length;
234 Some(section_ref)
235 } else {
236 None
237 };
238
239 let bitmap = if product.has_bitmap {
240 let section_ref = parse_section(message_bytes, cursor, 3, payload_limit)?;
241 cursor += section_ref.length;
242 Some(section_ref)
243 } else {
244 None
245 };
246
247 let data = parse_section(message_bytes, cursor, 4, payload_limit)?;
248 if data.offset + data.length != payload_limit {
249 return Err(Error::InvalidMessage(
250 "GRIB1 message contains trailing bytes before end marker".into(),
251 ));
252 }
253
254 Ok(Grib1Sections {
255 product,
256 pds,
257 grid,
258 bitmap,
259 data,
260 })
261}
262
263#[derive(Debug, Clone)]
264pub struct Grib1Sections {
265 pub product: ProductDefinition,
266 pub pds: SectionRef,
267 pub grid: Option<SectionRef>,
268 pub bitmap: Option<SectionRef>,
269 pub data: SectionRef,
270}
271
272fn parse_reference_time(section_bytes: &[u8]) -> Result<ReferenceTime> {
273 let century = section_bytes[24];
274 let year_of_century = section_bytes[12] as u16;
275 let year = match century {
276 0 => year_of_century,
277 c => (c as u16 - 1) * 100 + year_of_century,
278 };
279
280 Ok(ReferenceTime {
281 year,
282 month: section_bytes[13],
283 day: section_bytes[14],
284 hour: section_bytes[15],
285 minute: section_bytes[16],
286 second: 0,
287 })
288}
289
290fn parse_latlon_grid(section_bytes: &[u8]) -> Result<GridDefinition> {
291 let ni = u16::from_be_bytes(section_bytes[6..8].try_into().unwrap()) as u32;
292 let nj = u16::from_be_bytes(section_bytes[8..10].try_into().unwrap()) as u32;
293 let lat_first = grib_i24(§ion_bytes[10..13]).unwrap() * 1_000;
294 let lon_first = grib_i24(§ion_bytes[13..16]).unwrap() * 1_000;
295 let lat_last = grib_i24(§ion_bytes[17..20]).unwrap() * 1_000;
296 let lon_last = grib_i24(§ion_bytes[20..23]).unwrap() * 1_000;
297 let di = u16::from_be_bytes(section_bytes[23..25].try_into().unwrap()) as u32 * 1_000;
298 let dj = u16::from_be_bytes(section_bytes[25..27].try_into().unwrap()) as u32 * 1_000;
299 let scanning_mode = section_bytes[27];
300
301 Ok(GridDefinition::LatLon(LatLonGrid {
302 ni,
303 nj,
304 lat_first,
305 lon_first,
306 lat_last,
307 lon_last,
308 di,
309 dj,
310 scanning_mode,
311 }))
312}
313
314fn read_u24(bytes: &[u8]) -> u32 {
315 ((bytes[0] as u32) << 16) | ((bytes[1] as u32) << 8) | (bytes[2] as u32)
316}
317
318fn parse_section(
319 message_bytes: &[u8],
320 offset: usize,
321 number: u8,
322 payload_limit: usize,
323) -> Result<SectionRef> {
324 let length_bytes = message_bytes
325 .get(offset..offset + 3)
326 .ok_or(Error::Truncated {
327 offset: offset as u64,
328 })?;
329 let length = read_u24(length_bytes) as usize;
330 if length < 3 {
331 return Err(Error::InvalidSection {
332 section: number,
333 reason: format!("section length {length} is smaller than the 3-byte header"),
334 });
335 }
336
337 let end = offset
338 .checked_add(length)
339 .ok_or_else(|| Error::InvalidMessage("GRIB1 section length overflow".into()))?;
340 if end > payload_limit {
341 return Err(Error::Truncated {
342 offset: offset as u64,
343 });
344 }
345
346 Ok(section(number, offset, length))
347}
348
349fn section(number: u8, offset: usize, length: usize) -> SectionRef {
350 SectionRef {
351 number,
352 offset,
353 length,
354 }
355}
356
357fn ibm_f32(bytes: [u8; 4]) -> f32 {
358 if bytes == [0, 0, 0, 0] {
359 return 0.0;
360 }
361
362 let sign = if bytes[0] & 0x80 == 0 { 1.0 } else { -1.0 };
363 let exponent = ((bytes[0] & 0x7f) as i32) - 64;
364 let mantissa = ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32);
365 let value = sign * (mantissa as f64) / 16_777_216.0 * 16f64.powi(exponent);
366 value as f32
367}
368
369#[cfg(test)]
370mod tests {
371 use super::{bitmap_payload, ibm_f32, parse_message_sections, ProductDefinition};
372 use crate::error::Error;
373 use crate::metadata::ReferenceTime;
374
375 #[test]
376 fn decodes_zero_ibm_float() {
377 assert_eq!(ibm_f32([0, 0, 0, 0]), 0.0);
378 }
379
380 #[test]
381 fn parses_minimal_section_layout() {
382 let mut message = Vec::new();
383 message.extend_from_slice(b"GRIB");
384 message.extend_from_slice(&[0, 0, 64, 1]);
385 let mut pds = vec![0u8; 28];
386 pds[..3].copy_from_slice(&[0, 0, 28]);
387 pds[7] = 0b1000_0000;
388 pds[24] = 21;
389 message.extend_from_slice(&pds);
390 let mut gds = vec![0u8; 32];
391 gds[..3].copy_from_slice(&[0, 0, 32]);
392 message.extend_from_slice(&gds);
393 let mut bds = vec![0u8; 12];
394 bds[..3].copy_from_slice(&[0, 0, 12]);
395 message.extend_from_slice(&bds);
396 message.extend_from_slice(b"7777");
397
398 let sections = parse_message_sections(&message).unwrap();
399 assert!(sections.grid.is_some());
400 assert!(sections.bitmap.is_none());
401 assert_eq!(sections.data.length, 12);
402 }
403
404 #[test]
405 fn rejects_section_length_beyond_message_boundary() {
406 let mut message = Vec::new();
407 message.extend_from_slice(b"GRIB");
408 message.extend_from_slice(&[0, 0, 64, 1]);
409 let mut pds = vec![0u8; 28];
410 pds[..3].copy_from_slice(&[0, 0, 28]);
411 pds[7] = 0b1000_0000;
412 pds[24] = 21;
413 message.extend_from_slice(&pds);
414 let mut gds = vec![0u8; 32];
415 gds[..3].copy_from_slice(&[0, 0, 250]);
416 message.extend_from_slice(&gds);
417 let mut bds = vec![0u8; 12];
418 bds[..3].copy_from_slice(&[0, 0, 12]);
419 message.extend_from_slice(&bds);
420 message.extend_from_slice(b"7777");
421
422 assert!(parse_message_sections(&message).is_err());
423 }
424
425 #[test]
426 fn reports_small_predefined_bitmap_indicator() {
427 let err = bitmap_payload(&[0, 0, 6, 0, 0, 5]).unwrap_err();
428 assert!(matches!(err, Error::UnsupportedBitmapIndicator(5)));
429 }
430
431 #[test]
432 fn decodes_indicator_ten_forecast_time_as_u16() {
433 let product = ProductDefinition {
434 table_version: 2,
435 center_id: 7,
436 generating_process_id: 255,
437 grid_id: 0,
438 has_grid_definition: true,
439 has_bitmap: false,
440 parameter_number: 11,
441 level_type: 100,
442 level_value: 850,
443 reference_time: ReferenceTime {
444 year: 2026,
445 month: 3,
446 day: 20,
447 hour: 12,
448 minute: 0,
449 second: 0,
450 },
451 forecast_time_unit: 1,
452 p1: 0x01,
453 p2: 0x2c,
454 time_range_indicator: 10,
455 average_count: 0,
456 missing_count: 0,
457 century: 21,
458 subcenter_id: 0,
459 decimal_scale: 0,
460 };
461
462 assert_eq!(product.forecast_time(), Some(300));
463 }
464}