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