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 grid: SectionRef,
18    pub product: SectionRef,
19    pub data_representation: SectionRef,
20    pub bitmap: Option<SectionRef>,
21    pub data: SectionRef,
22}
23
24/// Scan a GRIB2 message and return all sections, including the end section.
25pub fn scan_sections(msg_bytes: &[u8]) -> Result<Vec<SectionRef>> {
26    if msg_bytes.len() < 20 {
27        return Err(Error::InvalidMessage(format!(
28            "GRIB2 message too short: {} bytes",
29            msg_bytes.len()
30        )));
31    }
32
33    let mut sections = Vec::new();
34    let mut pos = 16;
35
36    while pos < msg_bytes.len() {
37        if pos + 4 <= msg_bytes.len() && &msg_bytes[pos..pos + 4] == b"7777" {
38            if pos != msg_bytes.len() - 4 {
39                return Err(Error::InvalidMessage(
40                    "end section 7777 encountered before message end".into(),
41                ));
42            }
43            sections.push(SectionRef {
44                number: 8,
45                offset: pos,
46                length: 4,
47            });
48            return Ok(sections);
49        }
50
51        if pos + 5 > msg_bytes.len() {
52            return Err(Error::Truncated { offset: pos as u64 });
53        }
54
55        let length = u32::from_be_bytes(msg_bytes[pos..pos + 4].try_into().unwrap()) as usize;
56        let number = msg_bytes[pos + 4];
57
58        if length < 5 {
59            return Err(Error::InvalidSection {
60                section: number,
61                reason: format!("section length {length} is smaller than the 5-byte header"),
62            });
63        }
64        if pos + length > msg_bytes.len() {
65            return Err(Error::Truncated { offset: pos as u64 });
66        }
67
68        sections.push(SectionRef {
69            number,
70            offset: pos,
71            length,
72        });
73        pos += length;
74    }
75
76    Err(Error::InvalidMessage("missing end section 7777".into()))
77}
78
79/// Split a GRIB2 message into one or more logical fields.
80pub fn index_fields(msg_bytes: &[u8]) -> Result<Vec<FieldSections>> {
81    let sections = scan_sections(msg_bytes)?;
82    let identification = sections
83        .iter()
84        .copied()
85        .find(|section| section.number == 1)
86        .ok_or_else(|| Error::InvalidSectionOrder("missing identification section".into()))?;
87
88    let mut fields = Vec::new();
89    let mut current_grid = None;
90    let mut current_product = None;
91    let mut current_representation = None;
92    let mut current_bitmap = None;
93
94    for section in sections {
95        match section.number {
96            1 | 2 => {}
97            3 => {
98                current_grid = Some(section);
99                current_product = None;
100                current_representation = None;
101                current_bitmap = None;
102            }
103            4 => {
104                if current_grid.is_none() {
105                    return Err(Error::InvalidSectionOrder(
106                        "product definition encountered before grid definition".into(),
107                    ));
108                }
109                current_product = Some(section);
110                current_representation = None;
111                current_bitmap = None;
112            }
113            5 => {
114                if current_product.is_none() {
115                    return Err(Error::InvalidSectionOrder(
116                        "data representation encountered before product definition".into(),
117                    ));
118                }
119                current_representation = Some(section);
120                current_bitmap = None;
121            }
122            6 => {
123                if current_representation.is_none() {
124                    return Err(Error::InvalidSectionOrder(
125                        "bitmap encountered before data representation".into(),
126                    ));
127                }
128                current_bitmap = Some(section);
129            }
130            7 => {
131                let grid = current_grid.ok_or_else(|| {
132                    Error::InvalidSectionOrder(
133                        "data section encountered before grid definition".into(),
134                    )
135                })?;
136                let product = current_product.ok_or_else(|| {
137                    Error::InvalidSectionOrder(
138                        "data section encountered before product definition".into(),
139                    )
140                })?;
141                let data_representation = current_representation.ok_or_else(|| {
142                    Error::InvalidSectionOrder(
143                        "data section encountered before data representation".into(),
144                    )
145                })?;
146                fields.push(FieldSections {
147                    identification,
148                    grid,
149                    product,
150                    data_representation,
151                    bitmap: current_bitmap,
152                    data: section,
153                });
154                current_product = None;
155                current_representation = None;
156                current_bitmap = None;
157            }
158            8 => break,
159            other => {
160                return Err(Error::InvalidSection {
161                    section: other,
162                    reason: "unexpected section number".into(),
163                });
164            }
165        }
166    }
167
168    if fields.is_empty() {
169        return Err(Error::InvalidSectionOrder(
170            "message did not contain a complete field".into(),
171        ));
172    }
173
174    Ok(fields)
175}
176
177#[cfg(test)]
178mod tests {
179    use crate::error::Error;
180
181    use super::{index_fields, scan_sections};
182
183    fn section(number: u8, payload_len: usize) -> Vec<u8> {
184        let len = (payload_len + 5) as u32;
185        let mut bytes = len.to_be_bytes().to_vec();
186        bytes.push(number);
187        bytes.resize(len as usize, 0);
188        bytes
189    }
190
191    #[test]
192    fn scan_minimal_sections() {
193        let mut data = vec![0u8; 16];
194        data.extend_from_slice(&section(1, 16));
195        data.extend_from_slice(&section(3, 20));
196        data.extend_from_slice(&section(4, 29));
197        data.extend_from_slice(&section(5, 16));
198        data.extend_from_slice(&section(7, 3));
199        data.extend_from_slice(b"7777");
200
201        let sections = scan_sections(&data).unwrap();
202        assert_eq!(sections.len(), 6);
203        assert_eq!(sections[0].number, 1);
204        assert_eq!(sections[4].number, 7);
205        assert_eq!(sections[5].number, 8);
206    }
207
208    #[test]
209    fn indexes_repeated_fields() {
210        let mut data = vec![0u8; 16];
211        data.extend_from_slice(&section(1, 16));
212        data.extend_from_slice(&section(3, 20));
213        data.extend_from_slice(&section(4, 29));
214        data.extend_from_slice(&section(5, 16));
215        data.extend_from_slice(&section(7, 3));
216        data.extend_from_slice(&section(4, 29));
217        data.extend_from_slice(&section(5, 16));
218        data.extend_from_slice(&section(7, 3));
219        data.extend_from_slice(b"7777");
220
221        let fields = index_fields(&data).unwrap();
222        assert_eq!(fields.len(), 2);
223        assert_eq!(fields[0].grid.number, 3);
224        assert_eq!(fields[1].product.number, 4);
225    }
226
227    #[test]
228    fn rejects_early_end_section_before_message_end() {
229        let mut data = vec![0u8; 16];
230        data.extend_from_slice(&section(1, 16));
231        data.extend_from_slice(b"7777");
232        data.extend_from_slice(&[0, 0, 0, 0]);
233
234        let err = scan_sections(&data).unwrap_err();
235        assert!(matches!(err, Error::InvalidMessage(_)));
236    }
237}