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
5/// Parsed Indicator Section (Section 0).
6#[derive(Debug, Clone)]
7pub struct Indicator {
8    /// GRIB edition number (1 or 2).
9    pub edition: u8,
10    /// Discipline (GRIB2 only): 0=Meteorological, 1=Hydrological, 2=Land surface, etc.
11    pub discipline: u8,
12    /// Total length of the GRIB message in bytes.
13    pub total_length: u64,
14}
15
16impl Indicator {
17    /// Read the GRIB edition byte without interpreting edition-specific length
18    /// fields.
19    pub fn edition(data: &[u8]) -> Option<u8> {
20        if data.len() < 8 || &data[0..4] != b"GRIB" {
21            return None;
22        }
23        Some(data[7])
24    }
25
26    /// Parse from the first bytes of a GRIB message.
27    pub fn parse(data: &[u8]) -> Option<Self> {
28        let edition = Self::edition(data)?;
29        match edition {
30            1 => {
31                let length = u64::from(read_u24_be(&data[4..7])?);
32                Some(Self {
33                    edition,
34                    discipline: 0,
35                    total_length: length,
36                })
37            }
38            2 => {
39                if data.len() < 16 {
40                    return None;
41                }
42                let discipline = data[6];
43                let length = u64::from_be_bytes(data[8..16].try_into().ok()?);
44                Some(Self {
45                    edition,
46                    discipline,
47                    total_length: length,
48                })
49            }
50            _ => None,
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn parse_grib2_indicator() {
61        let mut data = Vec::new();
62        data.extend_from_slice(b"GRIB");
63        data.extend_from_slice(&[0, 0]);
64        data.push(0); // discipline
65        data.push(2); // edition
66        data.extend_from_slice(&100u64.to_be_bytes());
67        let ind = Indicator::parse(&data).unwrap();
68        assert_eq!(ind.edition, 2);
69        assert_eq!(ind.discipline, 0);
70        assert_eq!(ind.total_length, 100);
71    }
72
73    #[test]
74    fn parse_grib1_indicator() {
75        let mut data = vec![0u8; 8];
76        data[0..4].copy_from_slice(b"GRIB");
77        data[4] = 0;
78        data[5] = 3;
79        data[6] = 232; // 1000
80        data[7] = 1;
81        let ind = Indicator::parse(&data).unwrap();
82        assert_eq!(ind.edition, 1);
83        assert_eq!(ind.total_length, 1000);
84    }
85
86    #[test]
87    fn reject_invalid_magic() {
88        assert!(Indicator::parse(b"NOPE1234").is_none());
89        assert_eq!(Indicator::edition(b"NOPE1234"), None);
90    }
91
92    #[test]
93    fn reads_unsupported_edition_without_parsing_indicator() {
94        let mut data = Vec::new();
95        data.extend_from_slice(b"GRIB");
96        data.extend_from_slice(&[0, 0]);
97        data.push(0);
98        data.push(3);
99        data.extend_from_slice(&20u64.to_be_bytes());
100
101        assert_eq!(Indicator::edition(&data), Some(3));
102        assert!(Indicator::parse(&data).is_none());
103    }
104}