1use crate::data::{decode_field, 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 | 10 => Some(self.p1 as u32),
82 _ => None,
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq)]
88pub struct GridDescription {
89 pub nv: u8,
90 pub pv_or_pl: u8,
91 pub data_representation_type: u8,
92 pub grid: GridDefinition,
93}
94
95impl GridDescription {
96 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
97 if section_bytes.len() < 32 {
98 return Err(Error::InvalidSection {
99 section: 2,
100 reason: format!("expected at least 32 bytes, got {}", section_bytes.len()),
101 });
102 }
103
104 let data_representation_type = section_bytes[5];
105 let grid = match data_representation_type {
106 0 => parse_latlon_grid(section_bytes)?,
107 other => GridDefinition::Unsupported(other as u16),
108 };
109
110 Ok(Self {
111 nv: section_bytes[3],
112 pv_or_pl: section_bytes[4],
113 data_representation_type,
114 grid,
115 })
116 }
117}
118
119#[derive(Debug, Clone, PartialEq)]
120pub struct BinaryDataSection {
121 pub flags: u8,
122 pub unused_bits: u8,
123 pub binary_scale: i16,
124 pub reference_value: f32,
125 pub bits_per_value: u8,
126}
127
128impl BinaryDataSection {
129 pub fn parse(
130 section_bytes: &[u8],
131 decimal_scale: i16,
132 encoded_values: usize,
133 ) -> Result<(Self, DataRepresentation)> {
134 if section_bytes.len() < 11 {
135 return Err(Error::InvalidSection {
136 section: 4,
137 reason: format!("expected at least 11 bytes, got {}", section_bytes.len()),
138 });
139 }
140
141 let data_flag = section_bytes[3];
142 let flags = data_flag >> 4;
143 if flags & 0b1000 != 0 {
144 return Err(Error::UnsupportedDataTemplate(1004));
145 }
146 if flags & 0b0100 != 0 {
147 return Err(Error::UnsupportedDataTemplate(1005));
148 }
149 if flags & 0b0010 != 0 {
150 return Err(Error::UnsupportedDataTemplate(1006));
151 }
152 if flags & 0b0001 != 0 {
153 return Err(Error::UnsupportedDataTemplate(1007));
154 }
155
156 let binary_scale = grib_i16(§ion_bytes[4..6]).unwrap();
157 let reference_value = ibm_f32(section_bytes[6..10].try_into().unwrap());
158 let bits_per_value = section_bytes[10];
159 let simple = SimplePackingParams {
160 encoded_values,
161 reference_value,
162 binary_scale,
163 decimal_scale,
164 bits_per_value,
165 original_field_type: if flags & 0b0010_0000 != 0 { 1 } else { 0 },
166 };
167
168 Ok((
169 Self {
170 flags,
171 unused_bits: data_flag & 0x0f,
172 binary_scale,
173 reference_value,
174 bits_per_value,
175 },
176 DataRepresentation::SimplePacking(simple),
177 ))
178 }
179}
180
181pub fn bitmap_payload(section_bytes: &[u8]) -> Result<Option<&[u8]>> {
182 if section_bytes.len() < 6 {
183 return Err(Error::InvalidSection {
184 section: 3,
185 reason: format!("expected at least 6 bytes, got {}", section_bytes.len()),
186 });
187 }
188 let indicator = u16::from_be_bytes(section_bytes[4..6].try_into().unwrap());
189 if indicator == 0 {
190 Ok(Some(§ion_bytes[6..]))
191 } else {
192 Err(Error::UnsupportedBitmapIndicator(u8::MAX))
193 }
194}
195
196pub fn decode_simple_field(
197 data_section: &[u8],
198 representation: &DataRepresentation,
199 bitmap_section: Option<&[u8]>,
200 num_grid_points: usize,
201) -> Result<Vec<f64>> {
202 let mut wrapped = Vec::with_capacity(data_section.len() + 1);
203 wrapped.extend_from_slice(&[0, 0, 0, 0, 7]);
204 wrapped.extend_from_slice(data_section);
205 decode_field(&wrapped, representation, bitmap_section, num_grid_points)
206}
207
208pub fn parse_message_sections(message_bytes: &[u8]) -> Result<Grib1Sections> {
209 if message_bytes.len() < 8 + 28 + 11 + 4 {
210 return Err(Error::InvalidMessage(format!(
211 "GRIB1 message too short: {} bytes",
212 message_bytes.len()
213 )));
214 }
215
216 let payload_limit = message_bytes.len() - 4;
217 let pds = parse_section(message_bytes, 8, 1, payload_limit)?;
218 let pds_bytes = &message_bytes[pds.offset..pds.offset + pds.length];
219 let product = ProductDefinition::parse(pds_bytes)?;
220
221 let mut cursor = pds.offset + pds.length;
222 let grid = if product.has_grid_definition {
223 let section_ref = parse_section(message_bytes, cursor, 2, payload_limit)?;
224 cursor += section_ref.length;
225 Some(section_ref)
226 } else {
227 None
228 };
229
230 let bitmap = if product.has_bitmap {
231 let section_ref = parse_section(message_bytes, cursor, 3, payload_limit)?;
232 cursor += section_ref.length;
233 Some(section_ref)
234 } else {
235 None
236 };
237
238 let data = parse_section(message_bytes, cursor, 4, payload_limit)?;
239 if data.offset + data.length != payload_limit {
240 return Err(Error::InvalidMessage(
241 "GRIB1 message contains trailing bytes before end marker".into(),
242 ));
243 }
244
245 Ok(Grib1Sections {
246 product,
247 pds,
248 grid,
249 bitmap,
250 data,
251 })
252}
253
254#[derive(Debug, Clone)]
255pub struct Grib1Sections {
256 pub product: ProductDefinition,
257 pub pds: SectionRef,
258 pub grid: Option<SectionRef>,
259 pub bitmap: Option<SectionRef>,
260 pub data: SectionRef,
261}
262
263fn parse_reference_time(section_bytes: &[u8]) -> Result<ReferenceTime> {
264 let century = section_bytes[24];
265 let year_of_century = section_bytes[12] as u16;
266 let year = match century {
267 0 => year_of_century,
268 c => (c as u16 - 1) * 100 + year_of_century,
269 };
270
271 Ok(ReferenceTime {
272 year,
273 month: section_bytes[13],
274 day: section_bytes[14],
275 hour: section_bytes[15],
276 minute: section_bytes[16],
277 second: 0,
278 })
279}
280
281fn parse_latlon_grid(section_bytes: &[u8]) -> Result<GridDefinition> {
282 let ni = u16::from_be_bytes(section_bytes[6..8].try_into().unwrap()) as u32;
283 let nj = u16::from_be_bytes(section_bytes[8..10].try_into().unwrap()) as u32;
284 let lat_first = grib_i24(§ion_bytes[10..13]).unwrap() * 1_000;
285 let lon_first = grib_i24(§ion_bytes[13..16]).unwrap() * 1_000;
286 let lat_last = grib_i24(§ion_bytes[17..20]).unwrap() * 1_000;
287 let lon_last = grib_i24(§ion_bytes[20..23]).unwrap() * 1_000;
288 let di = u16::from_be_bytes(section_bytes[23..25].try_into().unwrap()) as u32 * 1_000;
289 let dj = u16::from_be_bytes(section_bytes[25..27].try_into().unwrap()) as u32 * 1_000;
290 let scanning_mode = section_bytes[27];
291
292 Ok(GridDefinition::LatLon(LatLonGrid {
293 ni,
294 nj,
295 lat_first,
296 lon_first,
297 lat_last,
298 lon_last,
299 di,
300 dj,
301 scanning_mode,
302 }))
303}
304
305fn read_u24(bytes: &[u8]) -> u32 {
306 ((bytes[0] as u32) << 16) | ((bytes[1] as u32) << 8) | (bytes[2] as u32)
307}
308
309fn parse_section(
310 message_bytes: &[u8],
311 offset: usize,
312 number: u8,
313 payload_limit: usize,
314) -> Result<SectionRef> {
315 let length_bytes = message_bytes
316 .get(offset..offset + 3)
317 .ok_or(Error::Truncated {
318 offset: offset as u64,
319 })?;
320 let length = read_u24(length_bytes) as usize;
321 if length < 3 {
322 return Err(Error::InvalidSection {
323 section: number,
324 reason: format!("section length {length} is smaller than the 3-byte header"),
325 });
326 }
327
328 let end = offset
329 .checked_add(length)
330 .ok_or_else(|| Error::InvalidMessage("GRIB1 section length overflow".into()))?;
331 if end > payload_limit {
332 return Err(Error::Truncated {
333 offset: offset as u64,
334 });
335 }
336
337 Ok(section(number, offset, length))
338}
339
340fn section(number: u8, offset: usize, length: usize) -> SectionRef {
341 SectionRef {
342 number,
343 offset,
344 length,
345 }
346}
347
348fn ibm_f32(bytes: [u8; 4]) -> f32 {
349 if bytes == [0, 0, 0, 0] {
350 return 0.0;
351 }
352
353 let sign = if bytes[0] & 0x80 == 0 { 1.0 } else { -1.0 };
354 let exponent = ((bytes[0] & 0x7f) as i32) - 64;
355 let mantissa = ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32);
356 let value = sign * (mantissa as f64) / 16_777_216.0 * 16f64.powi(exponent);
357 value as f32
358}
359
360#[cfg(test)]
361mod tests {
362 use super::{ibm_f32, parse_message_sections};
363
364 #[test]
365 fn decodes_zero_ibm_float() {
366 assert_eq!(ibm_f32([0, 0, 0, 0]), 0.0);
367 }
368
369 #[test]
370 fn parses_minimal_section_layout() {
371 let mut message = Vec::new();
372 message.extend_from_slice(b"GRIB");
373 message.extend_from_slice(&[0, 0, 64, 1]);
374 let mut pds = vec![0u8; 28];
375 pds[..3].copy_from_slice(&[0, 0, 28]);
376 pds[7] = 0b1000_0000;
377 pds[24] = 21;
378 message.extend_from_slice(&pds);
379 let mut gds = vec![0u8; 32];
380 gds[..3].copy_from_slice(&[0, 0, 32]);
381 message.extend_from_slice(&gds);
382 let mut bds = vec![0u8; 12];
383 bds[..3].copy_from_slice(&[0, 0, 12]);
384 message.extend_from_slice(&bds);
385 message.extend_from_slice(b"7777");
386
387 let sections = parse_message_sections(&message).unwrap();
388 assert!(sections.grid.is_some());
389 assert!(sections.bitmap.is_none());
390 assert_eq!(sections.data.length, 12);
391 }
392
393 #[test]
394 fn rejects_section_length_beyond_message_boundary() {
395 let mut message = Vec::new();
396 message.extend_from_slice(b"GRIB");
397 message.extend_from_slice(&[0, 0, 64, 1]);
398 let mut pds = vec![0u8; 28];
399 pds[..3].copy_from_slice(&[0, 0, 28]);
400 pds[7] = 0b1000_0000;
401 pds[24] = 21;
402 message.extend_from_slice(&pds);
403 let mut gds = vec![0u8; 32];
404 gds[..3].copy_from_slice(&[0, 0, 250]);
405 message.extend_from_slice(&gds);
406 let mut bds = vec![0u8; 12];
407 bds[..3].copy_from_slice(&[0, 0, 12]);
408 message.extend_from_slice(&bds);
409 message.extend_from_slice(b"7777");
410
411 assert!(parse_message_sections(&message).is_err());
412 }
413}