Skip to main content

grib_reader/
indicator.rs

1//! Section 0: Indicator Section parsing for GRIB1 and GRIB2.
2
3use grib_core::binary::read_u24_be;
4
5const GRIB1_LARGE_MESSAGE_FLAG: u32 = 0x80_0000;
6const GRIB1_LARGE_MESSAGE_BLOCK_BYTES: u64 = 120;
7
8/// Parsed Indicator Section (Section 0).
9#[derive(Debug, Clone)]
10pub struct Indicator {
11    /// GRIB edition number (1 or 2).
12    pub edition: u8,
13    /// Discipline (GRIB2 only): 0=Meteorological, 1=Hydrological, 2=Land surface, etc.
14    pub discipline: u8,
15    /// Total length of the GRIB message in bytes.
16    pub total_length: u64,
17}
18
19impl Indicator {
20    /// Read the GRIB edition byte without interpreting edition-specific length
21    /// fields.
22    pub fn edition(data: &[u8]) -> Option<u8> {
23        if data.len() < 8 || &data[0..4] != b"GRIB" {
24            return None;
25        }
26        Some(data[7])
27    }
28
29    /// Parse from the first bytes of a GRIB message.
30    pub fn parse(data: &[u8]) -> Option<Self> {
31        let edition = Self::edition(data)?;
32        match edition {
33            1 => {
34                let raw_length = read_u24_be(&data[4..7])?;
35                let length = resolve_grib1_large_message_length(data, raw_length)
36                    .unwrap_or(u64::from(raw_length));
37                Some(Self {
38                    edition,
39                    discipline: 0,
40                    total_length: length,
41                })
42            }
43            2 => {
44                if data.len() < 16 {
45                    return None;
46                }
47                let discipline = data[6];
48                let length = u64::from_be_bytes(data[8..16].try_into().ok()?);
49                Some(Self {
50                    edition,
51                    discipline,
52                    total_length: length,
53                })
54            }
55            _ => None,
56        }
57    }
58}
59
60pub(crate) fn grib1_section4_length(data: &[u8], offset: usize) -> Option<usize> {
61    let raw_total_length = read_u24_be(data.get(4..7)?)?;
62    let raw_section4_length = read_u24_be(data.get(offset..offset.checked_add(3)?)?)?;
63    let length = if uses_grib1_large_message_convention(raw_total_length, raw_section4_length) {
64        let total_length =
65            decode_grib1_large_message_length(raw_total_length, raw_section4_length)?;
66        total_length
67            .checked_sub(u64::try_from(offset).ok()?)?
68            .checked_sub(4)?
69    } else {
70        u64::from(raw_section4_length)
71    };
72    usize::try_from(length).ok()
73}
74
75fn resolve_grib1_large_message_length(data: &[u8], raw_length: u32) -> Option<u64> {
76    if raw_length & GRIB1_LARGE_MESSAGE_FLAG == 0 {
77        return None;
78    }
79
80    let section4_offset = locate_grib1_section4(data)?;
81    let raw_section4_length =
82        read_u24_be(data.get(section4_offset..section4_offset.checked_add(3)?)?)?;
83    if !uses_grib1_large_message_convention(raw_length, raw_section4_length) {
84        return None;
85    }
86    decode_grib1_large_message_length(raw_length, raw_section4_length)
87}
88
89fn uses_grib1_large_message_convention(raw_total_length: u32, raw_section4_length: u32) -> bool {
90    raw_total_length & GRIB1_LARGE_MESSAGE_FLAG != 0 && raw_section4_length < 120
91}
92
93fn decode_grib1_large_message_length(
94    raw_total_length: u32,
95    raw_section4_length: u32,
96) -> Option<u64> {
97    u64::from(raw_total_length & !GRIB1_LARGE_MESSAGE_FLAG)
98        .checked_mul(GRIB1_LARGE_MESSAGE_BLOCK_BYTES)?
99        .checked_sub(u64::from(raw_section4_length))?
100        .checked_add(4)
101}
102
103fn locate_grib1_section4(data: &[u8]) -> Option<usize> {
104    let product_offset = 8usize;
105    let product_length = section_length(data, product_offset)?;
106    if product_length < 8 {
107        return None;
108    }
109    let product_end = product_offset.checked_add(product_length)?;
110    let section_flags = *data.get(product_offset.checked_add(7)?)?;
111    let mut offset = product_end;
112
113    if section_flags & 0b1000_0000 != 0 {
114        offset = skip_section(data, offset)?;
115    }
116    if section_flags & 0b0100_0000 != 0 {
117        offset = skip_section(data, offset)?;
118    }
119    data.get(offset..offset.checked_add(3)?)?;
120    Some(offset)
121}
122
123fn skip_section(data: &[u8], offset: usize) -> Option<usize> {
124    let length = section_length(data, offset)?;
125    if length < 3 {
126        return None;
127    }
128    let end = offset.checked_add(length)?;
129    data.get(offset..end)?;
130    Some(end)
131}
132
133fn section_length(data: &[u8], offset: usize) -> Option<usize> {
134    let end = offset.checked_add(3)?;
135    usize::try_from(read_u24_be(data.get(offset..end)?)?).ok()
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn parse_grib2_indicator() {
144        let mut data = Vec::new();
145        data.extend_from_slice(b"GRIB");
146        data.extend_from_slice(&[0, 0]);
147        data.push(0); // discipline
148        data.push(2); // edition
149        data.extend_from_slice(&100u64.to_be_bytes());
150        let ind = Indicator::parse(&data).unwrap();
151        assert_eq!(ind.edition, 2);
152        assert_eq!(ind.discipline, 0);
153        assert_eq!(ind.total_length, 100);
154    }
155
156    #[test]
157    fn parse_grib1_indicator() {
158        let mut data = vec![0u8; 8];
159        data[0..4].copy_from_slice(b"GRIB");
160        data[4] = 0;
161        data[5] = 3;
162        data[6] = 232; // 1000
163        data[7] = 1;
164        let ind = Indicator::parse(&data).unwrap();
165        assert_eq!(ind.edition, 1);
166        assert_eq!(ind.total_length, 1000);
167    }
168
169    #[test]
170    fn resolves_grib1_large_message_convention_from_section4_padding() {
171        let units = 70_500u32;
172        let section4_padding = 17u32;
173        let mut data = vec![0u8; 8 + 28 + 32 + 3];
174        data[0..4].copy_from_slice(b"GRIB");
175        data[4..7].copy_from_slice(&(GRIB1_LARGE_MESSAGE_FLAG | units).to_be_bytes()[1..]);
176        data[7] = 1;
177        data[8..11].copy_from_slice(&28u32.to_be_bytes()[1..]);
178        data[15] = 0b1000_0000;
179        data[36..39].copy_from_slice(&32u32.to_be_bytes()[1..]);
180        data[68..71].copy_from_slice(&section4_padding.to_be_bytes()[1..]);
181
182        let indicator = Indicator::parse(&data).unwrap();
183        assert_eq!(
184            indicator.total_length,
185            u64::from(units) * GRIB1_LARGE_MESSAGE_BLOCK_BYTES - u64::from(section4_padding) + 4
186        );
187        assert!(indicator.total_length > u64::from(GRIB1_LARGE_MESSAGE_FLAG));
188    }
189
190    #[test]
191    fn preserves_ordinary_grib1_length_when_section4_is_not_padding() {
192        let raw_length = GRIB1_LARGE_MESSAGE_FLAG | 1_000;
193        let mut data = vec![0u8; 8 + 28 + 3];
194        data[0..4].copy_from_slice(b"GRIB");
195        data[4..7].copy_from_slice(&raw_length.to_be_bytes()[1..]);
196        data[7] = 1;
197        data[8..11].copy_from_slice(&28u32.to_be_bytes()[1..]);
198        data[36..39].copy_from_slice(&120u32.to_be_bytes()[1..]);
199
200        assert_eq!(
201            Indicator::parse(&data).unwrap().total_length,
202            u64::from(raw_length)
203        );
204    }
205
206    #[test]
207    fn reject_invalid_magic() {
208        assert!(Indicator::parse(b"NOPE1234").is_none());
209        assert_eq!(Indicator::edition(b"NOPE1234"), None);
210    }
211
212    #[test]
213    fn reads_unsupported_edition_without_parsing_indicator() {
214        let mut data = Vec::new();
215        data.extend_from_slice(b"GRIB");
216        data.extend_from_slice(&[0, 0]);
217        data.push(0);
218        data.push(3);
219        data.extend_from_slice(&20u64.to_be_bytes());
220
221        assert_eq!(Indicator::edition(&data), Some(3));
222        assert!(Indicator::parse(&data).is_none());
223    }
224}