Skip to main content

grib_reader/
grib1.rs

1//! GRIB Edition 1 parsing.
2
3use 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/// 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(
193            if indicator <= u16::from(u8::MAX) {
194                indicator as u8
195            } else {
196                u8::MAX
197            },
198        ))
199    }
200}
201
202pub fn decode_simple_field(
203    data_section: &[u8],
204    representation: &DataRepresentation,
205    bitmap_section: Option<&[u8]>,
206    num_grid_points: usize,
207) -> Result<Vec<f64>> {
208    decode_payload(
209        data_section,
210        representation,
211        bitmap_section,
212        num_grid_points,
213    )
214}
215
216pub fn parse_message_sections(message_bytes: &[u8]) -> Result<Grib1Sections> {
217    if message_bytes.len() < 8 + 28 + 11 + 4 {
218        return Err(Error::InvalidMessage(format!(
219            "GRIB1 message too short: {} bytes",
220            message_bytes.len()
221        )));
222    }
223
224    let payload_limit = message_bytes.len() - 4;
225    let pds = parse_section(message_bytes, 8, 1, payload_limit)?;
226    let pds_bytes = &message_bytes[pds.offset..pds.offset + pds.length];
227    let product = ProductDefinition::parse(pds_bytes)?;
228
229    let mut cursor = pds.offset + pds.length;
230    let grid = if product.has_grid_definition {
231        let section_ref = parse_section(message_bytes, cursor, 2, payload_limit)?;
232        cursor += section_ref.length;
233        Some(section_ref)
234    } else {
235        None
236    };
237
238    let bitmap = if product.has_bitmap {
239        let section_ref = parse_section(message_bytes, cursor, 3, payload_limit)?;
240        cursor += section_ref.length;
241        Some(section_ref)
242    } else {
243        None
244    };
245
246    let data = parse_section(message_bytes, cursor, 4, payload_limit)?;
247    if data.offset + data.length != payload_limit {
248        return Err(Error::InvalidMessage(
249            "GRIB1 message contains trailing bytes before end marker".into(),
250        ));
251    }
252
253    Ok(Grib1Sections {
254        product,
255        pds,
256        grid,
257        bitmap,
258        data,
259    })
260}
261
262#[derive(Debug, Clone)]
263pub struct Grib1Sections {
264    pub product: ProductDefinition,
265    pub pds: SectionRef,
266    pub grid: Option<SectionRef>,
267    pub bitmap: Option<SectionRef>,
268    pub data: SectionRef,
269}
270
271fn parse_reference_time(section_bytes: &[u8]) -> Result<ReferenceTime> {
272    let century = section_bytes[24];
273    let year_of_century = section_bytes[12] as u16;
274    let year = match century {
275        0 => year_of_century,
276        c => (c as u16 - 1) * 100 + year_of_century,
277    };
278
279    Ok(ReferenceTime {
280        year,
281        month: section_bytes[13],
282        day: section_bytes[14],
283        hour: section_bytes[15],
284        minute: section_bytes[16],
285        second: 0,
286    })
287}
288
289fn parse_latlon_grid(section_bytes: &[u8]) -> Result<GridDefinition> {
290    let ni = u16::from_be_bytes(section_bytes[6..8].try_into().unwrap()) as u32;
291    let nj = u16::from_be_bytes(section_bytes[8..10].try_into().unwrap()) as u32;
292    let lat_first = grib_i24(&section_bytes[10..13]).unwrap() * 1_000;
293    let lon_first = grib_i24(&section_bytes[13..16]).unwrap() * 1_000;
294    let lat_last = grib_i24(&section_bytes[17..20]).unwrap() * 1_000;
295    let lon_last = grib_i24(&section_bytes[20..23]).unwrap() * 1_000;
296    let di = u16::from_be_bytes(section_bytes[23..25].try_into().unwrap()) as u32 * 1_000;
297    let dj = u16::from_be_bytes(section_bytes[25..27].try_into().unwrap()) as u32 * 1_000;
298    let scanning_mode = section_bytes[27];
299
300    Ok(GridDefinition::LatLon(LatLonGrid {
301        ni,
302        nj,
303        lat_first,
304        lon_first,
305        lat_last,
306        lon_last,
307        di,
308        dj,
309        scanning_mode,
310    }))
311}
312
313fn read_u24(bytes: &[u8]) -> u32 {
314    ((bytes[0] as u32) << 16) | ((bytes[1] as u32) << 8) | (bytes[2] as u32)
315}
316
317fn parse_section(
318    message_bytes: &[u8],
319    offset: usize,
320    number: u8,
321    payload_limit: usize,
322) -> Result<SectionRef> {
323    let length_bytes = message_bytes
324        .get(offset..offset + 3)
325        .ok_or(Error::Truncated {
326            offset: offset as u64,
327        })?;
328    let length = read_u24(length_bytes) as usize;
329    if length < 3 {
330        return Err(Error::InvalidSection {
331            section: number,
332            reason: format!("section length {length} is smaller than the 3-byte header"),
333        });
334    }
335
336    let end = offset
337        .checked_add(length)
338        .ok_or_else(|| Error::InvalidMessage("GRIB1 section length overflow".into()))?;
339    if end > payload_limit {
340        return Err(Error::Truncated {
341            offset: offset as u64,
342        });
343    }
344
345    Ok(section(number, offset, length))
346}
347
348fn section(number: u8, offset: usize, length: usize) -> SectionRef {
349    SectionRef {
350        number,
351        offset,
352        length,
353    }
354}
355
356fn ibm_f32(bytes: [u8; 4]) -> f32 {
357    if bytes == [0, 0, 0, 0] {
358        return 0.0;
359    }
360
361    let sign = if bytes[0] & 0x80 == 0 { 1.0 } else { -1.0 };
362    let exponent = ((bytes[0] & 0x7f) as i32) - 64;
363    let mantissa = ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32);
364    let value = sign * (mantissa as f64) / 16_777_216.0 * 16f64.powi(exponent);
365    value as f32
366}
367
368#[cfg(test)]
369mod tests {
370    use super::{bitmap_payload, ibm_f32, parse_message_sections};
371    use crate::error::Error;
372
373    #[test]
374    fn decodes_zero_ibm_float() {
375        assert_eq!(ibm_f32([0, 0, 0, 0]), 0.0);
376    }
377
378    #[test]
379    fn parses_minimal_section_layout() {
380        let mut message = Vec::new();
381        message.extend_from_slice(b"GRIB");
382        message.extend_from_slice(&[0, 0, 64, 1]);
383        let mut pds = vec![0u8; 28];
384        pds[..3].copy_from_slice(&[0, 0, 28]);
385        pds[7] = 0b1000_0000;
386        pds[24] = 21;
387        message.extend_from_slice(&pds);
388        let mut gds = vec![0u8; 32];
389        gds[..3].copy_from_slice(&[0, 0, 32]);
390        message.extend_from_slice(&gds);
391        let mut bds = vec![0u8; 12];
392        bds[..3].copy_from_slice(&[0, 0, 12]);
393        message.extend_from_slice(&bds);
394        message.extend_from_slice(b"7777");
395
396        let sections = parse_message_sections(&message).unwrap();
397        assert!(sections.grid.is_some());
398        assert!(sections.bitmap.is_none());
399        assert_eq!(sections.data.length, 12);
400    }
401
402    #[test]
403    fn rejects_section_length_beyond_message_boundary() {
404        let mut message = Vec::new();
405        message.extend_from_slice(b"GRIB");
406        message.extend_from_slice(&[0, 0, 64, 1]);
407        let mut pds = vec![0u8; 28];
408        pds[..3].copy_from_slice(&[0, 0, 28]);
409        pds[7] = 0b1000_0000;
410        pds[24] = 21;
411        message.extend_from_slice(&pds);
412        let mut gds = vec![0u8; 32];
413        gds[..3].copy_from_slice(&[0, 0, 250]);
414        message.extend_from_slice(&gds);
415        let mut bds = vec![0u8; 12];
416        bds[..3].copy_from_slice(&[0, 0, 12]);
417        message.extend_from_slice(&bds);
418        message.extend_from_slice(b"7777");
419
420        assert!(parse_message_sections(&message).is_err());
421    }
422
423    #[test]
424    fn reports_small_predefined_bitmap_indicator() {
425        let err = bitmap_payload(&[0, 0, 6, 0, 0, 5]).unwrap_err();
426        assert!(matches!(err, Error::UnsupportedBitmapIndicator(5)));
427    }
428}