1use 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#[derive(Debug, Clone)]
10pub struct Indicator {
11 pub edition: u8,
13 pub discipline: u8,
15 pub total_length: u64,
17}
18
19impl Indicator {
20 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 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); data.push(2); 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; 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(§ion4_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}