Skip to main content

grib_reader/
sections.rs

1//! GRIB2 section scanning and logical field indexing.
2
3use crate::error::{Error, Result};
4
5/// A located section within a GRIB2 message.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct SectionRef {
8    pub number: u8,
9    pub offset: usize,
10    pub length: usize,
11}
12
13/// The sections that make up one logical field inside a GRIB2 message.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct FieldSections {
16    pub identification: SectionRef,
17    pub local_use: Option<SectionRef>,
18    pub grid: SectionRef,
19    pub product: SectionRef,
20    pub data_representation: SectionRef,
21    pub bitmap: Option<SectionRef>,
22    pub data: SectionRef,
23}
24
25/// Scan a GRIB2 message and return all sections, including the end section.
26pub fn scan_sections(msg_bytes: &[u8]) -> Result<Vec<SectionRef>> {
27    if msg_bytes.len() < 20 {
28        return Err(Error::InvalidMessage(format!(
29            "GRIB2 message too short: {} bytes",
30            msg_bytes.len()
31        )));
32    }
33
34    let mut sections = Vec::new();
35    let mut pos = 16;
36
37    while pos < msg_bytes.len() {
38        if pos + 4 <= msg_bytes.len() && &msg_bytes[pos..pos + 4] == b"7777" {
39            if pos != msg_bytes.len() - 4 {
40                return Err(Error::InvalidMessage(
41                    "end section 7777 encountered before message end".into(),
42                ));
43            }
44            sections.push(SectionRef {
45                number: 8,
46                offset: pos,
47                length: 4,
48            });
49            return Ok(sections);
50        }
51
52        if pos + 5 > msg_bytes.len() {
53            return Err(Error::Truncated { offset: pos as u64 });
54        }
55
56        let length = u32::from_be_bytes(msg_bytes[pos..pos + 4].try_into().unwrap()) as usize;
57        let number = msg_bytes[pos + 4];
58
59        if length < 5 {
60            return Err(Error::InvalidSection {
61                section: number,
62                reason: format!("section length {length} is smaller than the 5-byte header"),
63            });
64        }
65        let end = pos
66            .checked_add(length)
67            .filter(|end| *end <= msg_bytes.len())
68            .ok_or(Error::Truncated { offset: pos as u64 })?;
69
70        sections.push(SectionRef {
71            number,
72            offset: pos,
73            length,
74        });
75        pos = end;
76    }
77
78    Err(Error::InvalidMessage("missing end section 7777".into()))
79}
80
81/// Split a GRIB2 message into one or more logical fields.
82pub fn index_fields(msg_bytes: &[u8]) -> Result<Vec<FieldSections>> {
83    let sections = scan_sections(msg_bytes)?;
84    let identification = sections
85        .iter()
86        .copied()
87        .find(|section| section.number == 1)
88        .ok_or_else(|| Error::InvalidSectionOrder("missing identification section".into()))?;
89
90    let mut fields = Vec::new();
91    let mut current_local_use = None;
92    let mut current_grid = None;
93    let mut current_product = None;
94    let mut current_representation = None;
95    let mut current_bitmap = None;
96    let mut last_defined_bitmap = None;
97
98    for section in sections {
99        match section.number {
100            1 => {}
101            2 => {
102                if current_product.is_some() || current_representation.is_some() {
103                    return Err(Error::InvalidSectionOrder(
104                        "local use section encountered inside a field definition".into(),
105                    ));
106                }
107                current_local_use = Some(section);
108            }
109            3 => {
110                current_grid = Some(section);
111                current_product = None;
112                current_representation = None;
113                current_bitmap = None;
114            }
115            4 => {
116                if current_grid.is_none() {
117                    return Err(Error::InvalidSectionOrder(
118                        "product definition encountered before grid definition".into(),
119                    ));
120                }
121                current_product = Some(section);
122                current_representation = None;
123                current_bitmap = None;
124            }
125            5 => {
126                if current_product.is_none() {
127                    return Err(Error::InvalidSectionOrder(
128                        "data representation encountered before product definition".into(),
129                    ));
130                }
131                current_representation = Some(section);
132                current_bitmap = None;
133            }
134            6 => {
135                if current_representation.is_none() {
136                    return Err(Error::InvalidSectionOrder(
137                        "bitmap encountered before data representation".into(),
138                    ));
139                }
140                let bitmap_indicator =
141                    *msg_bytes
142                        .get(section.offset + 5)
143                        .ok_or(Error::InvalidSection {
144                            section: 6,
145                            reason: "bitmap section is missing its indicator".into(),
146                        })?;
147                match bitmap_indicator {
148                    0 => {
149                        current_bitmap = Some(section);
150                        last_defined_bitmap = Some(section);
151                    }
152                    254 => {
153                        current_bitmap = Some(last_defined_bitmap.ok_or(Error::MissingBitmap)?);
154                    }
155                    255 => current_bitmap = None,
156                    indicator => {
157                        return Err(Error::UnsupportedBitmapIndicator(u16::from(indicator)));
158                    }
159                }
160            }
161            7 => {
162                let grid = current_grid.ok_or_else(|| {
163                    Error::InvalidSectionOrder(
164                        "data section encountered before grid definition".into(),
165                    )
166                })?;
167                let product = current_product.ok_or_else(|| {
168                    Error::InvalidSectionOrder(
169                        "data section encountered before product definition".into(),
170                    )
171                })?;
172                let data_representation = current_representation.ok_or_else(|| {
173                    Error::InvalidSectionOrder(
174                        "data section encountered before data representation".into(),
175                    )
176                })?;
177                fields.push(FieldSections {
178                    identification,
179                    local_use: current_local_use,
180                    grid,
181                    product,
182                    data_representation,
183                    bitmap: current_bitmap,
184                    data: section,
185                });
186                current_product = None;
187                current_representation = None;
188                current_bitmap = None;
189            }
190            8 => break,
191            other => {
192                return Err(Error::InvalidSection {
193                    section: other,
194                    reason: "unexpected section number".into(),
195                });
196            }
197        }
198    }
199
200    if fields.is_empty() {
201        return Err(Error::InvalidSectionOrder(
202            "message did not contain a complete field".into(),
203        ));
204    }
205
206    Ok(fields)
207}
208
209#[cfg(test)]
210mod tests {
211    use crate::error::Error;
212
213    use super::{index_fields, scan_sections};
214
215    fn section(number: u8, payload_len: usize) -> Vec<u8> {
216        let len = (payload_len + 5) as u32;
217        let mut bytes = len.to_be_bytes().to_vec();
218        bytes.push(number);
219        bytes.resize(len as usize, 0);
220        bytes
221    }
222
223    fn bitmap_section(indicator: u8, bitmap: &[u8]) -> Vec<u8> {
224        let length = 6 + bitmap.len();
225        let mut bytes = (length as u32).to_be_bytes().to_vec();
226        bytes.push(6);
227        bytes.push(indicator);
228        bytes.extend_from_slice(bitmap);
229        bytes
230    }
231
232    #[test]
233    fn scan_minimal_sections() {
234        let mut data = vec![0u8; 16];
235        data.extend_from_slice(&section(1, 16));
236        data.extend_from_slice(&section(3, 20));
237        data.extend_from_slice(&section(4, 29));
238        data.extend_from_slice(&section(5, 16));
239        data.extend_from_slice(&section(7, 3));
240        data.extend_from_slice(b"7777");
241
242        let sections = scan_sections(&data).unwrap();
243        assert_eq!(sections.len(), 6);
244        assert_eq!(sections[0].number, 1);
245        assert_eq!(sections[4].number, 7);
246        assert_eq!(sections[5].number, 8);
247    }
248
249    #[test]
250    fn indexes_repeated_fields() {
251        let mut data = vec![0u8; 16];
252        data.extend_from_slice(&section(1, 16));
253        data.extend_from_slice(&section(3, 20));
254        data.extend_from_slice(&section(4, 29));
255        data.extend_from_slice(&section(5, 16));
256        data.extend_from_slice(&section(7, 3));
257        data.extend_from_slice(&section(4, 29));
258        data.extend_from_slice(&section(5, 16));
259        data.extend_from_slice(&section(7, 3));
260        data.extend_from_slice(b"7777");
261
262        let fields = index_fields(&data).unwrap();
263        assert_eq!(fields.len(), 2);
264        assert_eq!(fields[0].grid.number, 3);
265        assert_eq!(fields[0].local_use, None);
266        assert_eq!(fields[1].product.number, 4);
267    }
268
269    #[test]
270    fn associates_local_use_section_with_each_following_field() {
271        let mut data = vec![0u8; 16];
272        data.extend_from_slice(&section(1, 16));
273        data.extend_from_slice(&section(2, 3));
274        data.extend_from_slice(&section(3, 20));
275        data.extend_from_slice(&section(4, 29));
276        data.extend_from_slice(&section(5, 16));
277        data.extend_from_slice(&section(7, 3));
278        data.extend_from_slice(&section(4, 29));
279        data.extend_from_slice(&section(5, 16));
280        data.extend_from_slice(&section(7, 3));
281        data.extend_from_slice(b"7777");
282
283        let fields = index_fields(&data).unwrap();
284        assert_eq!(fields.len(), 2);
285        assert_eq!(fields[0].local_use.unwrap().number, 2);
286        assert_eq!(fields[1].local_use, fields[0].local_use);
287    }
288
289    #[test]
290    fn resolves_previously_defined_bitmap_indicator() {
291        let mut data = vec![0u8; 16];
292        data.extend_from_slice(&section(1, 16));
293        data.extend_from_slice(&section(3, 20));
294        data.extend_from_slice(&section(4, 29));
295        data.extend_from_slice(&section(5, 16));
296        data.extend_from_slice(&bitmap_section(0, &[0b1010_0000]));
297        data.extend_from_slice(&section(7, 3));
298        data.extend_from_slice(&section(4, 29));
299        data.extend_from_slice(&section(5, 16));
300        data.extend_from_slice(&bitmap_section(254, &[]));
301        data.extend_from_slice(&section(7, 3));
302        data.extend_from_slice(b"7777");
303
304        let fields = index_fields(&data).unwrap();
305        assert_eq!(fields.len(), 2);
306        assert_eq!(fields[1].bitmap, fields[0].bitmap);
307        assert_eq!(
308            data[fields[1].bitmap.unwrap().offset + 5],
309            0,
310            "resolved field must point to the defining bitmap"
311        );
312    }
313
314    #[test]
315    fn rejects_previous_bitmap_indicator_without_definition() {
316        let mut data = vec![0u8; 16];
317        data.extend_from_slice(&section(1, 16));
318        data.extend_from_slice(&section(3, 20));
319        data.extend_from_slice(&section(4, 29));
320        data.extend_from_slice(&section(5, 16));
321        data.extend_from_slice(&bitmap_section(254, &[]));
322        data.extend_from_slice(&section(7, 3));
323        data.extend_from_slice(b"7777");
324
325        assert!(matches!(index_fields(&data), Err(Error::MissingBitmap)));
326    }
327
328    #[test]
329    fn rejects_early_end_section_before_message_end() {
330        let mut data = vec![0u8; 16];
331        data.extend_from_slice(&section(1, 16));
332        data.extend_from_slice(b"7777");
333        data.extend_from_slice(&[0, 0, 0, 0]);
334
335        let err = scan_sections(&data).unwrap_err();
336        assert!(matches!(err, Error::InvalidMessage(_)));
337    }
338}