Skip to main content

grib_reader/
grib1.rs

1//! GRIB Edition 1 parsing.
2
3use 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/// GRIB1 product definition metadata.
12#[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(&section_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(&section_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(&section_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(&section_bytes[10..13]).unwrap() * 1_000;
285    let lon_first = grib_i24(&section_bytes[13..16]).unwrap() * 1_000;
286    let lat_last = grib_i24(&section_bytes[17..20]).unwrap() * 1_000;
287    let lon_last = grib_i24(&section_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}